Viewing file: BuildsQueries.php (18.08 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Illuminate\Database\Concerns;
use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\MultipleRecordsFoundException; use Illuminate\Database\Query\Expression; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Pagination\Cursor; use Illuminate\Pagination\CursorPaginator; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Conditionable; use InvalidArgumentException; use RuntimeException;
trait BuildsQueries { use Conditionable;
/** * Chunk the results of the query. * * @param int $count * @param callable $callback * @return bool */ public function chunk($count, callable $callback) { $this->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) { return $this->orderedChunkById($count, $callback, $column, $alias); }
/** * Chunk the results of a query by comparing IDs in descending order. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @return bool */ public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null) { return $this->orderedChunkById($count, $callback, $column, $alias, descending: true); }
/** * Chunk the results of a query by comparing IDs in a given order. * * @param int $count * @param callable $callback * @param string|null $column * @param string|null $alias * @param bool $descending * @return bool */ public function orderedChunkById($count, callable $callback, $column = null, $alias = null, $descending = false) { $column ??= $this->defaultKeyName();
$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. if ($descending) { $results = $clone->forPageBeforeId($count, $lastId, $column)->get(); } else { $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 = data_get($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 $chunkSize * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazyById($chunkSize = 1000, $column = null, $alias = null) { return $this->orderedLazyById($chunkSize, $column, $alias); }
/** * Query lazily, by chunking the results of a query by comparing IDs in descending order. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) { return $this->orderedLazyById($chunkSize, $column, $alias, true); }
/** * Query lazily, by chunking the results of a query by comparing IDs in a given order. * * @param int $chunkSize * @param string|null $column * @param string|null $alias * @param bool $descending * @return \Illuminate\Support\LazyCollection * * @throws \InvalidArgumentException */ protected function orderedLazyById($chunkSize = 1000, $column = null, $alias = null, $descending = false) { if ($chunkSize < 1) { throw new InvalidArgumentException('The chunk size should be at least 1'); }
$column ??= $this->defaultKeyName();
$alias ??= $column;
return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) { $lastId = null;
while (true) { $clone = clone $this;
if ($descending) { $results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get(); } else { $results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get(); }
foreach ($results as $result) { yield $result; }
if ($results->count() < $chunkSize) { return; }
$lastId = $results->last()->{$alias};
if ($lastId === null) { throw new RuntimeException("The lazyById operation was aborted because the [{$alias}] column is not present in the query result."); } } }); }
/** * 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);
$count = $result->count();
if ($count === 0) { throw new RecordsNotFoundException; }
if ($count > 1) { throw new MultipleRecordsFoundException($count); }
return $result->first(); }
/** * Paginate the given query using a cursor paginator. * * @param int $perPage * @param array|string $columns * @param string $cursorName * @param \Illuminate\Pagination\Cursor|string|null $cursor * @return \Illuminate\Contracts\Pagination\CursorPaginator */ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = 'cursor', $cursor = null) { if (! $cursor instanceof Cursor) { $cursor = is_string($cursor) ? Cursor::fromEncoded($cursor) : CursorPaginator::resolveCurrentCursor($cursorName, $cursor); }
$orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems());
if (! is_null($cursor)) { // Reset the union bindings so we can add the cursor where in the correct position... $this->setBindings([], 'union');
$addCursorConditions = function (self $builder, $previousColumn, $originalColumn, $i) use (&$addCursorConditions, $cursor, $orders) { $unionBuilders = $builder->getUnionBuilders();
if (! is_null($previousColumn)) { $originalColumn ??= $this->getOriginalColumnNameForCursorPagination($this, $previousColumn);
$builder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, '=', $cursor->parameter($previousColumn) );
$unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) { $unionBuilder->where( $this->getOriginalColumnNameForCursorPagination($unionBuilder, $previousColumn), '=', $cursor->parameter($previousColumn) );
$this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); }); }
$builder->where(function (self $secondBuilder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) { ['column' => $column, 'direction' => $direction] = $orders[$i];
$originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column);
$secondBuilder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, $direction === 'asc' ? '>' : '<', $cursor->parameter($column) );
if ($i < $orders->count() - 1) { $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $originalColumn, $i) { $addCursorConditions($thirdBuilder, $column, $originalColumn, $i + 1); }); }
$unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { $unionWheres = $unionBuilder->getRawBindings()['where'];
$originalColumn = $this->getOriginalColumnNameForCursorPagination($unionBuilder, $column); $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $originalColumn, $unionWheres) { $unionBuilder->where( $originalColumn, $direction === 'asc' ? '>' : '<', $cursor->parameter($column) );
if ($i < $orders->count() - 1) { $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $originalColumn, $i) { $addCursorConditions($fourthBuilder, $column, $originalColumn, $i + 1); }); }
$this->addBinding($unionWheres, 'union'); $this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); }); }); }); };
$addCursorConditions($this, null, null, 0); }
$this->limit($perPage + 1);
return $this->cursorPaginator($this->get($columns), $perPage, $cursor, [ 'path' => Paginator::resolveCurrentPath(), 'cursorName' => $cursorName, 'parameters' => $orders->pluck('column')->toArray(), ]); }
/** * Get the original column name of the given column, without any aliasing. * * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder * @param string $parameter * @return string */ protected function getOriginalColumnNameForCursorPagination($builder, string $parameter) { $columns = $builder instanceof Builder ? $builder->getQuery()->getColumns() : $builder->getColumns();
if (! is_null($columns)) { foreach ($columns as $column) { if (($position = strripos($column, ' as ')) !== false) { $original = substr($column, 0, $position);
$alias = substr($column, $position + 4);
if ($parameter === $alias || $builder->getGrammar()->wrap($parameter) === $alias) { return $original; } } } }
return $parameter; }
/** * 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\CursorPaginator */ protected function cursorPaginator($items, $perPage, $cursor, $options) { return Container::getInstance()->makeWith(CursorPaginator::class, compact( 'items', 'perPage', 'cursor', 'options' )); }
/** * Pass the query to a given callback. * * @param callable $callback * @return $this */ public function tap($callback) { $callback($this);
return $this; } }
|