feat: add lazyEach to EntityControlTrait - #271
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a lazyEach() helper to EntityControlTrait to iterate through models lazily (by ID) while preserving the trait’s state-reset behavior after query execution.
Changes:
- Added
EntityControlTrait::lazyEach()built on EloquentlazyById(), returning the number of iterated records. - Extended SQL mocking helpers with a dedicated mock for the lazy-by-id query shape.
- Added PHPUnit coverage for
lazyEach()with both non-empty and empty result sets.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Traits/EntityControlTrait.php |
Introduces lazyEach() using lazyById() and resets settable properties afterward. |
tests/support/Traits/SqlMockTrait.php |
Adds SQL mock helper for the expected lazyById select + eager-load query. |
tests/EntityControlTraitTest.php |
Adds tests validating return count and settable-property reset for lazyEach(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $this | ||
| ->getQuery($where) | ||
| ->lazyById($chunkSize) | ||
| ->tapEach(function () use (&$count) { | ||
| $count++; | ||
| }) | ||
| ->each($callback); | ||
|
|
||
| $this->postQueryHook(); | ||
|
|
There was a problem hiding this comment.
lazyEach() calls postQueryHook() only after iterating successfully. If the provided callback throws an exception during iteration, the hook will never run and the repository can leak state (withTrashed/onlyTrashed/force/with/withCount) into subsequent calls. Consider wrapping the iteration in a try { ... } finally { $this->postQueryHook(); } to guarantee reset even on failure.
| $this | |
| ->getQuery($where) | |
| ->lazyById($chunkSize) | |
| ->tapEach(function () use (&$count) { | |
| $count++; | |
| }) | |
| ->each($callback); | |
| $this->postQueryHook(); | |
| try { | |
| $this | |
| ->getQuery($where) | |
| ->lazyById($chunkSize) | |
| ->tapEach(function () use (&$count) { | |
| $count++; | |
| }) | |
| ->each($callback); | |
| } finally { | |
| $this->postQueryHook(); | |
| } |
| $this->postQueryHook(); | ||
| } | ||
|
|
||
| public function lazyEach(Closure $callback, array $where = [], int $chunkSize = 500): int |
There was a problem hiding this comment.
The new lazyEach() method’s parameter order (callback, where, chunkSize) is inconsistent with chunk() (limit, callback, where) and with most other methods in this trait that take $where as the last optional argument. This inconsistency makes the public API harder to remember/use (especially without named args). Consider aligning the signature/argument order with existing methods (or adding a PHPDoc showing intended usage).
| public function lazyEach(Closure $callback, array $where = [], int $chunkSize = 500): int | |
| public function lazyEach(int $chunkSize, Closure $callback, array $where = []): int |
| $this->postQueryHook(); | ||
| } | ||
|
|
||
| public function lazyEach(Closure $callback, array $where = [], int $chunkSize = 500): int |
There was a problem hiding this comment.
| public function lazyEach(Closure $callback, array $where = [], int $chunkSize = 500): int | |
| public function lazyEach(Closure $callback, array $where = [], int $chunkSize = 500): void |
| $count = 0; | ||
|
|
||
| $this | ||
| ->getQuery($where) | ||
| ->lazyById($chunkSize) | ||
| ->tapEach(function () use (&$count) { | ||
| $count++; | ||
| }) | ||
| ->each($callback); | ||
|
|
||
| $this->postQueryHook(); | ||
|
|
||
| return $count; |
There was a problem hiding this comment.
tapEach is required?
| $count = 0; | |
| $this | |
| ->getQuery($where) | |
| ->lazyById($chunkSize) | |
| ->tapEach(function () use (&$count) { | |
| $count++; | |
| }) | |
| ->each($callback); | |
| $this->postQueryHook(); | |
| return $count; | |
| $this | |
| ->getQuery($where) | |
| ->lazyById($chunkSize) | |
| ->each($callback); | |
| $this->postQueryHook(); |
using
$count = 0;
->lazyEach(function ($item) use (&$count) {
$count++;
});There was a problem hiding this comment.
yeah, i think we can skip "counting" since its a helper package and "counting" can be implemented in a $callback
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@neellii could you please fix tests for the new tests |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bb90e1fce
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| public function testLazyEachEmptyResult() | ||
| { | ||
| $this->mockSelect( | ||
| 'select "test_models".* from "test_models" where "test_models"."deleted_at" is not null order by "id" asc limit 500', |
There was a problem hiding this comment.
Align lazyEach empty-result SQL mock with repository state
testLazyEachEmptyResult mocks a query with "deleted_at" is not null, but the test calls self::$testRepositoryClass->lazyEach(...) without setting onlyTrashed(). In EntityControlTrait::getQuery(), that predicate is only applied when the onlyTrashed flag is enabled, so this expectation does not match the actual query path and makes the new empty-result test fail/misrepresent behavior.
Useful? React with 👍 / 👎.
…nk coverage and fixture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…of version check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… ordering issues Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…it < 10.5.30 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…3 compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ravel version Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3b757eaad
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| $assertionFailureProp->setValue($handler, null); | ||
| foreach ($class->getProperty('mockObjects')->getValue($this) as $entry) { | ||
| if ($isNewPhpunit) { | ||
| $handler = $entry['mockObject']->__phpunit_getInvocationHandler(); |
There was a problem hiding this comment.
Treat PHPUnit 11 mock entries as objects
When the suite runs with PHPUnit 11 or newer, this branch tries to index each mockObjects entry as an array, but PHPUnit's TestCase::registerMockObject() still appends the mock object itself in 11/12; with the unconstrained phpunit/phpunit >=10.5.30 dev requirement, installing a current PHPUnit makes any test that calls clearMockAssertionFailures() fail with an object-as-array error before it can clear the handler state.
Useful? React with 👍 / 👎.
refs: RonasIT/laravel-media#85 (comment)