379 lines
11 KiB
PHP
379 lines
11 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Illuminate\Database\Concerns;
|
||
|
|
||
|
use Illuminate\Container\Container;
|
||
|
use Illuminate\Database\MultipleRecordsFoundException;
|
||
|
use Illuminate\Database\RecordsNotFoundException;
|
||
|
use Illuminate\Pagination\CursorPaginator;
|
||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||
|
use Illuminate\Pagination\Paginator;
|
||
|
use Illuminate\Support\Collection;
|
||
|
use Illuminate\Support\LazyCollection;
|
||
|
use InvalidArgumentException;
|
||
|
use RuntimeException;
|
||
|
|
||
|
trait BuildsQueries
|
||
|
{
|
||
|
/**
|
||
|
* 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)
|
||
|
{
|
||
|
$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'
|
||
|
));
|
||
|
}
|
||
|
}
|