Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
framework/src/Illuminate/Database/Eloquent/Builder.php /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
1327 lines (1134 sloc)
37.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Illuminate\Database\Eloquent; | |
| use Closure; | |
| use Illuminate\Support\Arr; | |
| use Illuminate\Support\Str; | |
| use Illuminate\Pagination\Paginator; | |
| use Illuminate\Database\Query\Expression; | |
| use Illuminate\Pagination\LengthAwarePaginator; | |
| use Illuminate\Database\Eloquent\Relations\Relation; | |
| use Illuminate\Database\Query\Builder as QueryBuilder; | |
| class Builder | |
| { | |
| /** | |
| * The base query builder instance. | |
| * | |
| * @var \Illuminate\Database\Query\Builder | |
| */ | |
| protected $query; | |
| /** | |
| * The model being queried. | |
| * | |
| * @var \Illuminate\Database\Eloquent\Model | |
| */ | |
| protected $model; | |
| /** | |
| * The relationships that should be eager loaded. | |
| * | |
| * @var array | |
| */ | |
| protected $eagerLoad = []; | |
| /** | |
| * All of the registered builder macros. | |
| * | |
| * @var array | |
| */ | |
| protected $macros = []; | |
| /** | |
| * A replacement for the typical delete function. | |
| * | |
| * @var \Closure | |
| */ | |
| protected $onDelete; | |
| /** | |
| * The methods that should be returned from query builder. | |
| * | |
| * @var array | |
| */ | |
| protected $passthru = [ | |
| 'insert', 'insertGetId', 'getBindings', 'toSql', | |
| 'exists', 'count', 'min', 'max', 'avg', 'sum', 'getConnection', | |
| ]; | |
| /** | |
| * Applied global scopes. | |
| * | |
| * @var array | |
| */ | |
| protected $scopes = []; | |
| /** | |
| * Create a new Eloquent query builder instance. | |
| * | |
| * @param \Illuminate\Database\Query\Builder $query | |
| * @return void | |
| */ | |
| public function __construct(QueryBuilder $query) | |
| { | |
| $this->query = $query; | |
| } | |
| /** | |
| * Register a new global scope. | |
| * | |
| * @param string $identifier | |
| * @param \Illuminate\Database\Eloquent\Scope|\Closure $scope | |
| * @return $this | |
| */ | |
| public function withGlobalScope($identifier, $scope) | |
| { | |
| $this->scopes[$identifier] = $scope; | |
| if (method_exists($scope, 'extend')) { | |
| $scope->extend($this); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Remove a registered global scope. | |
| * | |
| * @param \Illuminate\Database\Eloquent\Scope|string $scope | |
| * @return $this | |
| */ | |
| public function withoutGlobalScope($scope) | |
| { | |
| if (is_string($scope)) { | |
| unset($this->scopes[$scope]); | |
| return $this; | |
| } | |
| foreach ($this->scopes as $key => $value) { | |
| if ($scope instanceof $value) { | |
| unset($this->scopes[$key]); | |
| } | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Remove all or passed registered global scopes. | |
| * | |
| * @param array|null $scopes | |
| * @return $this | |
| */ | |
| public function withoutGlobalScopes(array $scopes = null) | |
| { | |
| if (is_array($scopes)) { | |
| foreach ($scopes as $scope) { | |
| $this->withoutGlobalScope($scope); | |
| } | |
| } else { | |
| $this->scopes = []; | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Find a model by its primary key. | |
| * | |
| * @param mixed $id | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null | |
| */ | |
| public function find($id, $columns = ['*']) | |
| { | |
| if (is_array($id)) { | |
| return $this->findMany($id, $columns); | |
| } | |
| $this->query->where($this->model->getQualifiedKeyName(), '=', $id); | |
| return $this->first($columns); | |
| } | |
| /** | |
| * Find a model by its primary key. | |
| * | |
| * @param array $ids | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Collection | |
| */ | |
| public function findMany($ids, $columns = ['*']) | |
| { | |
| if (empty($ids)) { | |
| return $this->model->newCollection(); | |
| } | |
| $this->query->whereIn($this->model->getQualifiedKeyName(), $ids); | |
| return $this->get($columns); | |
| } | |
| /** | |
| * Find a model by its primary key or throw an exception. | |
| * | |
| * @param mixed $id | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection | |
| * | |
| * @throws \Illuminate\Database\Eloquent\ModelNotFoundException | |
| */ | |
| public function findOrFail($id, $columns = ['*']) | |
| { | |
| $result = $this->find($id, $columns); | |
| if (is_array($id)) { | |
| if (count($result) == count(array_unique($id))) { | |
| return $result; | |
| } | |
| } elseif (! is_null($result)) { | |
| return $result; | |
| } | |
| throw (new ModelNotFoundException)->setModel(get_class($this->model)); | |
| } | |
| /** | |
| * Find a model by its primary key or return fresh model instance. | |
| * | |
| * @param mixed $id | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Model | |
| */ | |
| public function findOrNew($id, $columns = ['*']) | |
| { | |
| if (! is_null($model = $this->find($id, $columns))) { | |
| return $model; | |
| } | |
| return $this->model->newInstance(); | |
| } | |
| /** | |
| * Get the first record matching the attributes or instantiate it. | |
| * | |
| * @param array $attributes | |
| * @return \Illuminate\Database\Eloquent\Model | |
| */ | |
| public function firstOrNew(array $attributes) | |
| { | |
| if (! is_null($instance = $this->where($attributes)->first())) { | |
| return $instance; | |
| } | |
| return $this->model->newInstance($attributes); | |
| } | |
| /** | |
| * Get the first record matching the attributes or create it. | |
| * | |
| * @param array $attributes | |
| * @return \Illuminate\Database\Eloquent\Model | |
| */ | |
| public function firstOrCreate(array $attributes) | |
| { | |
| if (! is_null($instance = $this->where($attributes)->first())) { | |
| return $instance; | |
| } | |
| $instance = $this->model->newInstance($attributes); | |
| $instance->save(); | |
| return $instance; | |
| } | |
| /** | |
| * Create or update a record matching the attributes, and fill it with values. | |
| * | |
| * @param array $attributes | |
| * @param array $values | |
| * @return \Illuminate\Database\Eloquent\Model | |
| */ | |
| public function updateOrCreate(array $attributes, array $values = []) | |
| { | |
| $instance = $this->firstOrNew($attributes); | |
| $instance->fill($values)->save(); | |
| return $instance; | |
| } | |
| /** | |
| * Execute the query and get the first result. | |
| * | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Model|static|null | |
| */ | |
| public function first($columns = ['*']) | |
| { | |
| return $this->take(1)->get($columns)->first(); | |
| } | |
| /** | |
| * Execute the query and get the first result or throw an exception. | |
| * | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Model|static | |
| * | |
| * @throws \Illuminate\Database\Eloquent\ModelNotFoundException | |
| */ | |
| public function firstOrFail($columns = ['*']) | |
| { | |
| if (! is_null($model = $this->first($columns))) { | |
| return $model; | |
| } | |
| throw (new ModelNotFoundException)->setModel(get_class($this->model)); | |
| } | |
| /** | |
| * Execute the query as a "select" statement. | |
| * | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Collection|static[] | |
| */ | |
| public function get($columns = ['*']) | |
| { | |
| $builder = $this->applyScopes(); | |
| $models = $builder->getModels($columns); | |
| // If we actually found models we will also eager load any relationships that | |
| // have been specified as needing to be eager loaded, which will solve the | |
| // n+1 query issue for the developers to avoid running a lot of queries. | |
| if (count($models) > 0) { | |
| $models = $builder->eagerLoadRelations($models); | |
| } | |
| return $builder->getModel()->newCollection($models); | |
| } | |
| /** | |
| * Get a single column's value from the first result of a query. | |
| * | |
| * @param string $column | |
| * @return mixed | |
| */ | |
| public function value($column) | |
| { | |
| $result = $this->first([$column]); | |
| if ($result) { | |
| return $result->{$column}; | |
| } | |
| } | |
| /** | |
| * Chunk the results of the query. | |
| * | |
| * @param int $count | |
| * @param callable $callback | |
| * @return bool | |
| */ | |
| public function chunk($count, callable $callback) | |
| { | |
| $results = $this->forPage($page = 1, $count)->get(); | |
| while (count($results) > 0) { | |
| // 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 (call_user_func($callback, $results) === false) { | |
| return false; | |
| } | |
| $page++; | |
| $results = $this->forPage($page, $count)->get(); | |
| } | |
| return true; | |
| } | |
| /** | |
| * Execute a callback over each item while chunking. | |
| * | |
| * @param callable $callback | |
| * @param int $count | |
| * @return bool | |
| */ | |
| public function each(callable $callback, $count = 1000) | |
| { | |
| if (is_null($this->query->orders) && is_null($this->query->unionOrders)) { | |
| $this->orderBy($this->model->getQualifiedKeyName(), 'asc'); | |
| } | |
| return $this->chunk($count, function ($results) use ($callback) { | |
| foreach ($results as $key => $value) { | |
| if ($callback($value, $key) === false) { | |
| return false; | |
| } | |
| } | |
| }); | |
| } | |
| /** | |
| * Get an array with the values of a given column. | |
| * | |
| * @param string $column | |
| * @param string|null $key | |
| * @return \Illuminate\Support\Collection | |
| */ | |
| public function pluck($column, $key = null) | |
| { | |
| $results = $this->toBase()->pluck($column, $key); | |
| // If the model has a mutator for the requested column, we will spin through | |
| // the results and mutate the values so that the mutated version of these | |
| // columns are returned as you would expect from these Eloquent models. | |
| if ($this->model->hasGetMutator($column)) { | |
| foreach ($results as $key => &$value) { | |
| $fill = [$column => $value]; | |
| $value = $this->model->newFromBuilder($fill)->$column; | |
| } | |
| } | |
| return collect($results); | |
| } | |
| /** | |
| * Alias for the "pluck" method. | |
| * | |
| * @param string $column | |
| * @param string $key | |
| * @return \Illuminate\Support\Collection | |
| * | |
| * @deprecated since version 5.2. Use the "pluck" method directly. | |
| */ | |
| public function lists($column, $key = null) | |
| { | |
| return $this->pluck($column, $key); | |
| } | |
| /** | |
| * Paginate the given query. | |
| * | |
| * @param int $perPage | |
| * @param array $columns | |
| * @param string $pageName | |
| * @param int|null $page | |
| * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator | |
| * | |
| * @throws \InvalidArgumentException | |
| */ | |
| public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) | |
| { | |
| $query = $this->toBase(); | |
| $total = $query->getCountForPagination(); | |
| $this->forPage( | |
| $page = $page ?: Paginator::resolveCurrentPage($pageName), | |
| $perPage = $perPage ?: $this->model->getPerPage() | |
| ); | |
| return new LengthAwarePaginator($this->get($columns), $total, $perPage, $page, [ | |
| 'path' => Paginator::resolveCurrentPath(), | |
| 'pageName' => $pageName, | |
| ]); | |
| } | |
| /** | |
| * Paginate the given query into a simple paginator. | |
| * | |
| * @param int $perPage | |
| * @param array $columns | |
| * @param string $pageName | |
| * @return \Illuminate\Contracts\Pagination\Paginator | |
| */ | |
| public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page') | |
| { | |
| $page = Paginator::resolveCurrentPage($pageName); | |
| $perPage = $perPage ?: $this->model->getPerPage(); | |
| $this->skip(($page - 1) * $perPage)->take($perPage + 1); | |
| return new Paginator($this->get($columns), $perPage, $page, [ | |
| 'path' => Paginator::resolveCurrentPath(), | |
| 'pageName' => $pageName, | |
| ]); | |
| } | |
| /** | |
| * Update a record in the database. | |
| * | |
| * @param array $values | |
| * @return int | |
| */ | |
| public function update(array $values) | |
| { | |
| return $this->toBase()->update($this->addUpdatedAtColumn($values)); | |
| } | |
| /** | |
| * Increment a column's value by a given amount. | |
| * | |
| * @param string $column | |
| * @param int $amount | |
| * @param array $extra | |
| * @return int | |
| */ | |
| public function increment($column, $amount = 1, array $extra = []) | |
| { | |
| $extra = $this->addUpdatedAtColumn($extra); | |
| return $this->toBase()->increment($column, $amount, $extra); | |
| } | |
| /** | |
| * Decrement a column's value by a given amount. | |
| * | |
| * @param string $column | |
| * @param int $amount | |
| * @param array $extra | |
| * @return int | |
| */ | |
| public function decrement($column, $amount = 1, array $extra = []) | |
| { | |
| $extra = $this->addUpdatedAtColumn($extra); | |
| return $this->toBase()->decrement($column, $amount, $extra); | |
| } | |
| /** | |
| * Add the "updated at" column to an array of values. | |
| * | |
| * @param array $values | |
| * @return array | |
| */ | |
| protected function addUpdatedAtColumn(array $values) | |
| { | |
| if (! $this->model->usesTimestamps()) { | |
| return $values; | |
| } | |
| $column = $this->model->getUpdatedAtColumn(); | |
| return Arr::add($values, $column, $this->model->freshTimestampString()); | |
| } | |
| /** | |
| * Delete a record from the database. | |
| * | |
| * @return mixed | |
| */ | |
| public function delete() | |
| { | |
| if (isset($this->onDelete)) { | |
| return call_user_func($this->onDelete, $this); | |
| } | |
| return $this->toBase()->delete(); | |
| } | |
| /** | |
| * Run the default delete function on the builder. | |
| * | |
| * @return mixed | |
| */ | |
| public function forceDelete() | |
| { | |
| return $this->query->delete(); | |
| } | |
| /** | |
| * Register a replacement for the default delete function. | |
| * | |
| * @param \Closure $callback | |
| * @return void | |
| */ | |
| public function onDelete(Closure $callback) | |
| { | |
| $this->onDelete = $callback; | |
| } | |
| /** | |
| * Get the hydrated models without eager loading. | |
| * | |
| * @param array $columns | |
| * @return \Illuminate\Database\Eloquent\Model[] | |
| */ | |
| public function getModels($columns = ['*']) | |
| { | |
| $results = $this->query->get($columns); | |
| $connection = $this->model->getConnectionName(); | |
| return $this->model->hydrate($results, $connection)->all(); | |
| } | |
| /** | |
| * Eager load the relationships for the models. | |
| * | |
| * @param array $models | |
| * @return array | |
| */ | |
| public function eagerLoadRelations(array $models) | |
| { | |
| foreach ($this->eagerLoad as $name => $constraints) { | |
| // For nested eager loads we'll skip loading them here and they will be set as an | |
| // eager load on the query to retrieve the relation so that they will be eager | |
| // loaded on that query, because that is where they get hydrated as models. | |
| if (strpos($name, '.') === false) { | |
| $models = $this->loadRelation($models, $name, $constraints); | |
| } | |
| } | |
| return $models; | |
| } | |
| /** | |
| * Eagerly load the relationship on a set of models. | |
| * | |
| * @param array $models | |
| * @param string $name | |
| * @param \Closure $constraints | |
| * @return array | |
| */ | |
| protected function loadRelation(array $models, $name, Closure $constraints) | |
| { | |
| // First we will "back up" the existing where conditions on the query so we can | |
| // add our eager constraints. Then we will merge the wheres that were on the | |
| // query back to it in order that any where conditions might be specified. | |
| $relation = $this->getRelation($name); | |
| $relation->addEagerConstraints($models); | |
| call_user_func($constraints, $relation); | |
| $models = $relation->initRelation($models, $name); | |
| // Once we have the results, we just match those back up to their parent models | |
| // using the relationship instance. Then we just return the finished arrays | |
| // of models which have been eagerly hydrated and are readied for return. | |
| $results = $relation->getEager(); | |
| return $relation->match($models, $results, $name); | |
| } | |
| /** | |
| * Get the relation instance for the given relation name. | |
| * | |
| * @param string $name | |
| * @return \Illuminate\Database\Eloquent\Relations\Relation | |
| */ | |
| public function getRelation($name) | |
| { | |
| // We want to run a relationship query without any constrains so that we will | |
| // not have to remove these where clauses manually which gets really hacky | |
| // and is error prone while we remove the developer's own where clauses. | |
| $relation = Relation::noConstraints(function () use ($name) { | |
| return $this->getModel()->$name(); | |
| }); | |
| $nested = $this->nestedRelations($name); | |
| // If there are nested relationships set on the query, we will put those onto | |
| // the query instances so that they can be handled after this relationship | |
| // is loaded. In this way they will all trickle down as they are loaded. | |
| if (count($nested) > 0) { | |
| $relation->getQuery()->with($nested); | |
| } | |
| return $relation; | |
| } | |
| /** | |
| * Get the deeply nested relations for a given top-level relation. | |
| * | |
| * @param string $relation | |
| * @return array | |
| */ | |
| protected function nestedRelations($relation) | |
| { | |
| $nested = []; | |
| // We are basically looking for any relationships that are nested deeper than | |
| // the given top-level relationship. We will just check for any relations | |
| // that start with the given top relations and adds them to our arrays. | |
| foreach ($this->eagerLoad as $name => $constraints) { | |
| if ($this->isNested($name, $relation)) { | |
| $nested[substr($name, strlen($relation.'.'))] = $constraints; | |
| } | |
| } | |
| return $nested; | |
| } | |
| /** | |
| * Determine if the relationship is nested. | |
| * | |
| * @param string $name | |
| * @param string $relation | |
| * @return bool | |
| */ | |
| protected function isNested($name, $relation) | |
| { | |
| $dots = Str::contains($name, '.'); | |
| return $dots && Str::startsWith($name, $relation.'.'); | |
| } | |
| /** | |
| * Add a basic where clause to the query. | |
| * | |
| * @param string $column | |
| * @param string $operator | |
| * @param mixed $value | |
| * @param string $boolean | |
| * @return $this | |
| */ | |
| public function where($column, $operator = null, $value = null, $boolean = 'and') | |
| { | |
| if ($column instanceof Closure) { | |
| $query = $this->model->newQueryWithoutScopes(); | |
| call_user_func($column, $query); | |
| $this->query->addNestedWhereQuery($query->getQuery(), $boolean); | |
| } else { | |
| call_user_func_array([$this->query, 'where'], func_get_args()); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Add an "or where" clause to the query. | |
| * | |
| * @param string $column | |
| * @param string $operator | |
| * @param mixed $value | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function orWhere($column, $operator = null, $value = null) | |
| { | |
| return $this->where($column, $operator, $value, 'or'); | |
| } | |
| /** | |
| * Add a relationship count / exists condition to the query. | |
| * | |
| * @param string $relation | |
| * @param string $operator | |
| * @param int $count | |
| * @param string $boolean | |
| * @param \Closure|null $callback | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) | |
| { | |
| if (strpos($relation, '.') !== false) { | |
| return $this->hasNested($relation, $operator, $count, $boolean, $callback); | |
| } | |
| $relation = $this->getHasRelationQuery($relation); | |
| // If we only need to check for the existence of the relation, then we can | |
| // optimize the subquery to only run a "where exists" clause instead of | |
| // the full "count" clause. This will make the query run much faster. | |
| $queryType = $this->shouldRunExistsQuery($operator, $count) | |
| ? 'getRelationQuery' : 'getRelationCountQuery'; | |
| $query = $relation->{$queryType}($relation->getRelated()->newQuery(), $this); | |
| if ($callback) { | |
| call_user_func($callback, $query); | |
| } | |
| return $this->addHasWhere( | |
| $query, $relation, $operator, $count, $boolean | |
| ); | |
| } | |
| /** | |
| * Add nested relationship count / exists conditions to the query. | |
| * | |
| * @param string $relations | |
| * @param string $operator | |
| * @param int $count | |
| * @param string $boolean | |
| * @param \Closure|null $callback | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) | |
| { | |
| $relations = explode('.', $relations); | |
| // In order to nest "has", we need to add count relation constraints on the | |
| // callback Closure. We'll do this by simply passing the Closure its own | |
| // reference to itself so it calls itself recursively on each segment. | |
| $closure = function ($q) use (&$closure, &$relations, $operator, $count, $boolean, $callback) { | |
| if (count($relations) > 1) { | |
| $q->whereHas(array_shift($relations), $closure); | |
| } else { | |
| $q->has(array_shift($relations), $operator, $count, 'and', $callback); | |
| } | |
| }; | |
| return $this->has(array_shift($relations), '>=', 1, $boolean, $closure); | |
| } | |
| /** | |
| * Add a relationship count / exists condition to the query. | |
| * | |
| * @param string $relation | |
| * @param string $boolean | |
| * @param \Closure|null $callback | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function doesntHave($relation, $boolean = 'and', Closure $callback = null) | |
| { | |
| return $this->has($relation, '<', 1, $boolean, $callback); | |
| } | |
| /** | |
| * Add a relationship count / exists condition to the query with where clauses. | |
| * | |
| * @param string $relation | |
| * @param \Closure $callback | |
| * @param string $operator | |
| * @param int $count | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1) | |
| { | |
| return $this->has($relation, $operator, $count, 'and', $callback); | |
| } | |
| /** | |
| * Add a relationship count / exists condition to the query with where clauses. | |
| * | |
| * @param string $relation | |
| * @param \Closure|null $callback | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function whereDoesntHave($relation, Closure $callback = null) | |
| { | |
| return $this->doesntHave($relation, 'and', $callback); | |
| } | |
| /** | |
| * Add a relationship count / exists condition to the query with an "or". | |
| * | |
| * @param string $relation | |
| * @param string $operator | |
| * @param int $count | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function orHas($relation, $operator = '>=', $count = 1) | |
| { | |
| return $this->has($relation, $operator, $count, 'or'); | |
| } | |
| /** | |
| * Add a relationship count / exists condition to the query with where clauses and an "or". | |
| * | |
| * @param string $relation | |
| * @param \Closure $callback | |
| * @param string $operator | |
| * @param int $count | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function orWhereHas($relation, Closure $callback, $operator = '>=', $count = 1) | |
| { | |
| return $this->has($relation, $operator, $count, 'or', $callback); | |
| } | |
| /** | |
| * Add the "has" condition where clause to the query. | |
| * | |
| * @param \Illuminate\Database\Eloquent\Builder $hasQuery | |
| * @param \Illuminate\Database\Eloquent\Relations\Relation $relation | |
| * @param string $operator | |
| * @param int $count | |
| * @param string $boolean | |
| * @return \Illuminate\Database\Eloquent\Builder | |
| */ | |
| protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean) | |
| { | |
| $this->mergeModelDefinedRelationWheresToHasQuery($hasQuery, $relation); | |
| if ($this->shouldRunExistsQuery($operator, $count)) { | |
| $not = ($operator === '<' && $count === 1); | |
| return $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $not); | |
| } | |
| return $this->whereCountQuery($hasQuery->toBase(), $operator, $count, $boolean); | |
| } | |
| /** | |
| * Check if we can run an "exists" query to optimize performance. | |
| * | |
| * @param string $operator | |
| * @param int $count | |
| * @return bool | |
| */ | |
| protected function shouldRunExistsQuery($operator, $count) | |
| { | |
| return ($operator === '>=' && $count === 1) || ($operator === '<' && $count === 1); | |
| } | |
| /** | |
| * Add a sub query count clause to the query. | |
| * | |
| * @param \Illuminate\Database\Query\Builder $query | |
| * @param string $operator | |
| * @param int $count | |
| * @param string $boolean | |
| * @return $this | |
| */ | |
| protected function whereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and') | |
| { | |
| if (is_numeric($count)) { | |
| $count = new Expression($count); | |
| } | |
| $this->query->addBinding($query->getBindings(), 'where'); | |
| return $this->where(new Expression('('.$query->toSql().')'), $operator, $count, $boolean); | |
| } | |
| /** | |
| * Merge the "wheres" from a relation query to a has query. | |
| * | |
| * @param \Illuminate\Database\Eloquent\Builder $hasQuery | |
| * @param \Illuminate\Database\Eloquent\Relations\Relation $relation | |
| * @return void | |
| */ | |
| protected function mergeModelDefinedRelationWheresToHasQuery(Builder $hasQuery, Relation $relation) | |
| { | |
| // Here we have the "has" query and the original relation. We need to copy over any | |
| // where clauses the developer may have put in the relationship function over to | |
| // the has query, and then copy the bindings from the "has" query to the main. | |
| $relationQuery = $relation->toBase(); | |
| $hasQuery->withoutGlobalScopes()->mergeWheres( | |
| $relationQuery->wheres, $relationQuery->getBindings() | |
| ); | |
| } | |
| /** | |
| * Get the "has relation" base query instance. | |
| * | |
| * @param string $relation | |
| * @return \Illuminate\Database\Eloquent\Relations\Relation | |
| */ | |
| protected function getHasRelationQuery($relation) | |
| { | |
| return Relation::noConstraints(function () use ($relation) { | |
| return $this->getModel()->$relation(); | |
| }); | |
| } | |
| /** | |
| * Set the relationships that should be eager loaded. | |
| * | |
| * @param mixed $relations | |
| * @return $this | |
| */ | |
| public function with($relations) | |
| { | |
| if (is_string($relations)) { | |
| $relations = func_get_args(); | |
| } | |
| $eagers = $this->parseWithRelations($relations); | |
| $this->eagerLoad = array_merge($this->eagerLoad, $eagers); | |
| return $this; | |
| } | |
| /** | |
| * Parse a list of relations into individuals. | |
| * | |
| * @param array $relations | |
| * @return array | |
| */ | |
| protected function parseWithRelations(array $relations) | |
| { | |
| $results = []; | |
| foreach ($relations as $name => $constraints) { | |
| // If the "relation" value is actually a numeric key, we can assume that no | |
| // constraints have been specified for the eager load and we'll just put | |
| // an empty Closure with the loader so that we can treat all the same. | |
| if (is_numeric($name)) { | |
| $f = function () { | |
| // | |
| }; | |
| list($name, $constraints) = [$constraints, $f]; | |
| } | |
| // We need to separate out any nested includes. Which allows the developers | |
| // to load deep relationships using "dots" without stating each level of | |
| // the relationship with its own key in the array of eager load names. | |
| $results = $this->parseNestedWith($name, $results); | |
| $results[$name] = $constraints; | |
| } | |
| return $results; | |
| } | |
| /** | |
| * Parse the nested relationships in a relation. | |
| * | |
| * @param string $name | |
| * @param array $results | |
| * @return array | |
| */ | |
| protected function parseNestedWith($name, $results) | |
| { | |
| $progress = []; | |
| // If the relation has already been set on the result array, we will not set it | |
| // again, since that would override any constraints that were already placed | |
| // on the relationships. We will only set the ones that are not specified. | |
| foreach (explode('.', $name) as $segment) { | |
| $progress[] = $segment; | |
| if (! isset($results[$last = implode('.', $progress)])) { | |
| $results[$last] = function () { | |
| // | |
| }; | |
| } | |
| } | |
| return $results; | |
| } | |
| /** | |
| * Call the given model scope on the underlying model. | |
| * | |
| * @param string $scope | |
| * @param array $parameters | |
| * @return \Illuminate\Database\Query\Builder | |
| */ | |
| protected function callScope($scope, $parameters) | |
| { | |
| array_unshift($parameters, $this); | |
| $query = $this->getQuery(); | |
| // We will keep track of how many wheres are on the query before running the | |
| // scope so that we can properly group the added scope constraints in the | |
| // query as their own isolated nested where statement and avoid issues. | |
| $originalWhereCount = count($query->wheres); | |
| $result = call_user_func_array([$this->model, $scope], $parameters) ?: $this; | |
| if ($this->shouldNestWheresForScope($query, $originalWhereCount)) { | |
| $this->nestWheresForScope($query, $originalWhereCount); | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Apply the scopes to the Eloquent builder instance and return it. | |
| * | |
| * @return \Illuminate\Database\Eloquent\Builder|static | |
| */ | |
| public function applyScopes() | |
| { | |
| if (! $this->scopes) { | |
| return $this; | |
| } | |
| $builder = clone $this; | |
| $query = $builder->getQuery(); | |
| // We will keep track of how many wheres are on the query before running the | |
| // scope so that we can properly group the added scope constraints in the | |
| // query as their own isolated nested where statement and avoid issues. | |
| $originalWhereCount = count($query->wheres); | |
| $whereCounts = [$originalWhereCount]; | |
| foreach ($this->scopes as $scope) { | |
| $this->applyScope($scope, $builder); | |
| // Again, we will keep track of the count each time we add where clauses so that | |
| // we will properly isolate each set of scope constraints inside of their own | |
| // nested where clause to avoid any conflicts or issues with logical order. | |
| $whereCounts[] = count($query->wheres); | |
| } | |
| if ($this->shouldNestWheresForScope($query, $originalWhereCount)) { | |
| $this->nestWheresForScope($query, $whereCounts); | |
| } | |
| return $builder; | |
| } | |
| /** | |
| * Apply a single scope on the given builder instance. | |
| * | |
| * @param \Illuminate\Database\Eloquent\Scope|\Closure $scope | |
| * @param \Illuminate\Database\Eloquent\Builder $builder | |
| * @return void | |
| */ | |
| protected function applyScope($scope, $builder) | |
| { | |
| if ($scope instanceof Closure) { | |
| $scope($builder); | |
| } elseif ($scope instanceof Scope) { | |
| $scope->apply($builder, $this->getModel()); | |
| } | |
| } | |
| /** | |
| * Determine if the scope added after the given offset should be nested. | |
| * | |
| * @param \Illuminate\Database\Query\Builder $query | |
| * @param int $originalWhereCount | |
| * @return bool | |
| */ | |
| protected function shouldNestWheresForScope(QueryBuilder $query, $originalWhereCount) | |
| { | |
| return $originalWhereCount && count($query->wheres) > $originalWhereCount; | |
| } | |
| /** | |
| * Nest where conditions by slicing them at the given where count. | |
| * | |
| * @param \Illuminate\Database\Query\Builder $query | |
| * @param int|array $whereCounts | |
| * @return void | |
| */ | |
| protected function nestWheresForScope(QueryBuilder $query, $whereCounts) | |
| { | |
| // Here, we totally remove all of the where clauses since we are going to | |
| // rebuild them as nested queries by slicing the groups of wheres into | |
| // their own sections. This is to prevent any confusing logic order. | |
| $allWheres = $query->wheres; | |
| $query->wheres = []; | |
| // We will construct where offsets by adding the outer most offsets to the | |
| // collection (0 and total where count) while also flattening the array | |
| // and extracting unique values, ensuring that all wheres are sliced. | |
| $whereOffsets = collect([0, $whereCounts, count($allWheres)]) | |
| ->flatten()->unique(); | |
| $sliceFrom = $whereOffsets->shift(); | |
| foreach ($whereOffsets as $sliceTo) { | |
| $this->sliceWhereConditions( | |
| $query, $allWheres, $sliceFrom, $sliceTo | |
| ); | |
| $sliceFrom = $sliceTo; | |
| } | |
| } | |
| /** | |
| * Create a slice of where conditions at the given offsets and nest them if needed. | |
| * | |
| * @param \Illuminate\Database\Query\Builder $query | |
| * @param array $wheres | |
| * @param int $sliceFrom | |
| * @param int $sliceTo | |
| * @return void | |
| */ | |
| protected function sliceWhereConditions(QueryBuilder $query, array $wheres, $sliceFrom, $sliceTo) | |
| { | |
| $whereSlice = array_slice($wheres, $sliceFrom, $sliceTo - $sliceFrom); | |
| $whereBooleans = collect($whereSlice)->pluck('boolean'); | |
| // Here we'll check if the given subset of where clauses contains any "or" | |
| // booleans and in this case create a nested where expression. That way | |
| // we don't add any unnecessary nesting thus keeping the query clean. | |
| if ($whereBooleans->contains('or')) { | |
| $query->wheres[] = $this->nestWhereSlice($whereSlice); | |
| } else { | |
| $query->wheres = array_merge($query->wheres, $whereSlice); | |
| } | |
| } | |
| /** | |
| * Create a where array with nested where conditions. | |
| * | |
| * @param array $whereSlice | |
| * @return array | |
| */ | |
| protected function nestWhereSlice($whereSlice) | |
| { | |
| $whereGroup = $this->getQuery()->forNestedWhere(); | |
| $whereGroup->wheres = $whereSlice; | |
| return ['type' => 'Nested', 'query' => $whereGroup, 'boolean' => 'and']; | |
| } | |
| /** | |
| * Get the underlying query builder instance. | |
| * | |
| * @return \Illuminate\Database\Query\Builder|static | |
| */ | |
| public function getQuery() | |
| { | |
| return $this->query; | |
| } | |
| /** | |
| * Get a base query builder instance. | |
| * | |
| * @return \Illuminate\Database\Query\Builder | |
| */ | |
| public function toBase() | |
| { | |
| return $this->applyScopes()->getQuery(); | |
| } | |
| /** | |
| * Set the underlying query builder instance. | |
| * | |
| * @param \Illuminate\Database\Query\Builder $query | |
| * @return $this | |
| */ | |
| public function setQuery($query) | |
| { | |
| $this->query = $query; | |
| return $this; | |
| } | |
| /** | |
| * Get the relationships being eagerly loaded. | |
| * | |
| * @return array | |
| */ | |
| public function getEagerLoads() | |
| { | |
| return $this->eagerLoad; | |
| } | |
| /** | |
| * Set the relationships being eagerly loaded. | |
| * | |
| * @param array $eagerLoad | |
| * @return $this | |
| */ | |
| public function setEagerLoads(array $eagerLoad) | |
| { | |
| $this->eagerLoad = $eagerLoad; | |
| return $this; | |
| } | |
| /** | |
| * Get the model instance being queried. | |
| * | |
| * @return \Illuminate\Database\Eloquent\Model | |
| */ | |
| public function getModel() | |
| { | |
| return $this->model; | |
| } | |
| /** | |
| * Set a model instance for the model being queried. | |
| * | |
| * @param \Illuminate\Database\Eloquent\Model $model | |
| * @return $this | |
| */ | |
| public function setModel(Model $model) | |
| { | |
| $this->model = $model; | |
| $this->query->from($model->getTable()); | |
| return $this; | |
| } | |
| /** | |
| * Extend the builder with a given callback. | |
| * | |
| * @param string $name | |
| * @param \Closure $callback | |
| * @return void | |
| */ | |
| public function macro($name, Closure $callback) | |
| { | |
| $this->macros[$name] = $callback; | |
| } | |
| /** | |
| * Get the given macro by name. | |
| * | |
| * @param string $name | |
| * @return \Closure | |
| */ | |
| public function getMacro($name) | |
| { | |
| return Arr::get($this->macros, $name); | |
| } | |
| /** | |
| * Dynamically handle calls into the query instance. | |
| * | |
| * @param string $method | |
| * @param array $parameters | |
| * @return mixed | |
| */ | |
| public function __call($method, $parameters) | |
| { | |
| if (isset($this->macros[$method])) { | |
| array_unshift($parameters, $this); | |
| return call_user_func_array($this->macros[$method], $parameters); | |
| } | |
| if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) { | |
| return $this->callScope($scope, $parameters); | |
| } | |
| if (in_array($method, $this->passthru)) { | |
| return call_user_func_array([$this->toBase(), $method], $parameters); | |
| } | |
| call_user_func_array([$this->query, $method], $parameters); | |
| return $this; | |
| } | |
| /** | |
| * Force a clone of the underlying query builder when cloning. | |
| * | |
| * @return void | |
| */ | |
| public function __clone() | |
| { | |
| $this->query = clone $this->query; | |
| } | |
| } |