diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 1a3c84ddf1fe..d20b872cf750 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -354,6 +354,37 @@ public function chunk($count, callable $callback) return true; } + /** + * Chunk the results of a query, when the ID to query by is known. + * + * Because if we know the ID to key by, then we can get through each page much faster, + * especially in larger sets of data. + * + * @param $count + * @param callable $callback + * @param $idField + * @return bool + */ + public function chunkById($count, callable $callback, $idField) + { + $lastId = null; + $results = $this->pageAfterId($count, $idField)->get(); + + while (! $results->isEmpty()) { + if (call_user_func($callback, $results) === false) { + return false; + } + + if ($idField) { + $lastId = last($results->all())->{$idField}; + } + + $results = $this->pageAfterId($count, $idField, $lastId)->get(); + } + + return true; + } + /** * Execute a callback over each item while chunking. * diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 4707b0c6fb73..671872dbf06a 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -1314,6 +1314,23 @@ public function forPage($page, $perPage = 15) return $this->skip(($page - 1) * $perPage)->take($perPage); } + /** + * Set the limit and query for the next set of results, given the + * last scene ID in a set. + * + * @param int $perPage + * @param $idField + * @param int $lastId + * @return Builder|static + */ + public function pageAfterId($perPage = 15, $idField = 'id', $lastId = 0) + { + return $this->select($idField) + ->where($idField, '>', $lastId) + ->orderBy($idField, 'asc') + ->take($perPage); + } + /** * Add a union statement to the query. * @@ -1611,6 +1628,37 @@ public function chunk($count, callable $callback) return true; } + /** + * Chunk the results of a query, when the ID to query by is known. + * + * Because if we know the ID to key by, then we can get through each page much faster, + * especially in larger sets of data. + * + * @param $count + * @param callable $callback + * @param $idField + * @return bool + */ + public function chunkById($count, callable $callback, $idField) + { + $lastId = null; + $results = $this->pageAfterId($count, $idField)->get(); + + while (! $results->isEmpty()) { + if (call_user_func($callback, $results) === false) { + return false; + } + + if ($idField) { + $lastId = last($results->all())->{$idField}; + } + + $results = $this->pageAfterId($count, $idField, $lastId)->get(); + } + + return true; + } + /** * Execute a callback over each item while chunking. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index c4456ac631af..bd82295a8a8d 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -194,6 +194,22 @@ public function testChunkCanBeStoppedByReturningFalse() }); } + public function testChunkPaginatesUsingWhereBetweenIds() + { + $builder = m::mock('Illuminate\Database\Eloquent\Builder[pageAfterId,get]', [$this->getMockQueryBuilder()]); + $builder->shouldReceive('pageAfterId')->once()->with(2, 'someIdField')->andReturn($builder); + $builder->shouldReceive('pageAfterId')->once()->with(2, 'someIdField', 2)->andReturn($builder); + $builder->shouldReceive('pageAfterId')->once()->with(2, 'someIdField', 10)->andReturn($builder); + + $builder->shouldReceive('get')->times(3)->andReturn( + new Collection([(object) ['someIdField' => 1], (object) ['someIdField' => 2]]), + new Collection([(object) ['someIdField' => 10]]), + new Collection([]) + ); + + $builder->chunkById(2, function ($results) {}, 'someIdField'); + } + public function testPluckReturnsTheMutatedAttributesOfAModel() { $builder = $this->getBuilder();