enforceOrderBy(); $page = 1; do { // We'll execute the query for the given page and get the results. If there are // no results we can just break and return from here. When there are results // we will call the callback with the current chunk of these results here. $results = $this->forPage($page, $count)->get(); $countResults = $results->count(); if ($countResults == 0) { break; } // On each chunk result set, we will pass them to the callback and then let the // developer take care of everything within the callback, which allows us to // keep the memory low for spinning through large result sets for working. if ($callback($results, $page) === false) { return false; } unset($results); $page++; } while ($countResults == $count); return true; } /** * Run a map over each item while chunking. * * @param callable $callback * @param int $count * @return \Illuminate\Support\Collection */ public function chunkMap(callable $callback, $count = 1000) { $collection = Collection::make(); $this->chunk($count, function ($items) use ($collection, $callback) { $items->each(function ($item) use ($collection, $callback) { $collection->push($callback($item)); }); }); return $collection; } /** * Execute a callback over each item while chunking. * * @param callable $callback * @param int $count * @return bool * * @throws \RuntimeException */ public function each(callable $callback, $count = 1000) { return $this->chunk($count, function ($results) use ($callback) { foreach ($results as $key => $value) { if ($callback($value, $key) === false) { return false; } } }); } /** * Chunk the results of a query by comparing IDs. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @return bool */ public function chunkById($count, callable $callback, $column = null, $alias = null) { $column = $column ?? $this->defaultKeyName(); $alias = $alias ?? $column; $lastId = null; $page = 1; do { $clone = clone $this; // We'll execute the query for the given page and get the results. If there are // no results we can just break and return from here. When there are results // we will call the callback with the current chunk of these results here. $results = $clone->forPageAfterId($count, $lastId, $column)->get(); $countResults = $results->count(); if ($countResults == 0) { break; } // On each chunk result set, we will pass them to the callback and then let the // developer take care of everything within the callback, which allows us to // keep the memory low for spinning through large result sets for working. if ($callback($results, $page) === false) { return false; } $lastId = $results->last()->{$alias}; if ($lastId === null) { throw new RuntimeException("The chunkById operation was aborted because the [{$alias}] column is not present in the query result."); } unset($results); $page++; } while ($countResults == $count); return true; } /** * Execute a callback over each item while chunking by ID. * * @param callable $callback * @param int $count * @param string|null $column * @param string|null $alias * @return bool */ public function eachById(callable $callback, $count = 1000, $column = null, $alias = null) { return $this->chunkById($count, function ($results, $page) use ($callback, $count) { foreach ($results as $key => $value) { if ($callback($value, (($page - 1) * $count) + $key) === false) { return false; } } }, $column, $alias); } /** * Query lazily, by chunks of the given size. * * @param int $chunkSize * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazy($chunkSize = 1000) { if ($chunkSize < 1) { throw new InvalidArgumentException('The chunk size should be at least 1'); } $this->enforceOrderBy(); return LazyCollection::make(function () use ($chunkSize) { $page = 1; while (true) { $results = $this->forPage($page++, $chunkSize)->get(); foreach ($results as $result) { yield $result; } if ($results->count() < $chunkSize) { return; } } }); } /** * Query lazily, by chunking the results of a query by comparing IDs. * * @param int $count * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { if ($chunkSize < 1) { throw new InvalidArgumentException('The chunk size should be at least 1'); } $column = $column ?? $this->defaultKeyName(); $alias = $alias ?? $column; return LazyCollection::make(function () use ($chunkSize, $column, $alias) { $lastId = null; while (true) { $clone = clone $this; $results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get(); foreach ($results as $result) { yield $result; } if ($results->count() < $chunkSize) { return; } $lastId = $results->last()->{$alias}; } }); } /** * Execute the query and get the first result. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|object|static|null */ public function first($columns = ['*']) { return $this->take(1)->get($columns)->first(); } /** * Execute the query and get the first result if it's the sole matching record. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|object|static|null * * @throws \Illuminate\Database\RecordsNotFoundException * @throws \Illuminate\Database\MultipleRecordsFoundException */ public function sole($columns = ['*']) { $result = $this->take(2)->get($columns); if ($result->isEmpty()) { throw new RecordsNotFoundException; } if ($result->count() > 1) { throw new MultipleRecordsFoundException; } return $result->first(); } /** * Apply the callback's query changes if the given "value" is true. * * @param mixed $value * @param callable $callback * @param callable|null $default * @return mixed|$this */ public function when($value, $callback, $default = null) { if ($value) { return $callback($this, $value) ?: $this; } elseif ($default) { return $default($this, $value) ?: $this; } return $this; } /** * Pass the query to a given callback. * * @param callable $callback * @return $this */ public function tap($callback) { return $this->when(true, $callback); } /** * Apply the callback's query changes if the given "value" is false. * * @param mixed $value * @param callable $callback * @param callable|null $default * @return mixed|$this */ public function unless($value, $callback, $default = null) { if (! $value) { return $callback($this, $value) ?: $this; } elseif ($default) { return $default($this, $value) ?: $this; } return $this; } /** * Create a new length-aware paginator instance. * * @param \Illuminate\Support\Collection $items * @param int $total * @param int $perPage * @param int $currentPage * @param array $options * @return \Illuminate\Pagination\LengthAwarePaginator */ protected function paginator($items, $total, $perPage, $currentPage, $options) { return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact( 'items', 'total', 'perPage', 'currentPage', 'options' )); } /** * Create a new simple paginator instance. * * @param \Illuminate\Support\Collection $items * @param int $perPage * @param int $currentPage * @param array $options * @return \Illuminate\Pagination\Paginator */ protected function simplePaginator($items, $perPage, $currentPage, $options) { return Container::getInstance()->makeWith(Paginator::class, compact( 'items', 'perPage', 'currentPage', 'options' )); } /** * Create a new cursor paginator instance. * * @param \Illuminate\Support\Collection $items * @param int $perPage * @param \Illuminate\Pagination\Cursor $cursor * @param array $options * @return \Illuminate\Pagination\Paginator */ protected function cursorPaginator($items, $perPage, $cursor, $options) { return Container::getInstance()->makeWith(CursorPaginator::class, compact( 'items', 'perPage', 'cursor', 'options' )); } }