|
<?php |
|
|
|
namespace App; |
|
|
|
use BadMethodCallException; |
|
use Closure; |
|
use Illuminate\Database\Eloquent\Builder; |
|
use Illuminate\Database\Eloquent\RelationNotFoundException; |
|
use Illuminate\Database\Eloquent\Relations\Relation; |
|
use Illuminate\Support\Str; |
|
|
|
/** |
|
* Class CustomEloquentBuilder |
|
* |
|
* Allows for calling params through a with on relationship methods |
|
* |
|
* @see Builder |
|
*/ |
|
class CustomEloquentBuilder extends Builder |
|
{ |
|
/** |
|
* 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 (mb_strpos($name, '.') === false) { |
|
if (is_array($constraints)) { |
|
$models = $this->eagerLoadRelationCustom($models, $name, $constraints); |
|
} else { |
|
$models = $this->eagerLoadRelation($models, $name, $constraints); |
|
} |
|
} |
|
} |
|
|
|
return $models; |
|
} |
|
|
|
/** |
|
* Get the relation instance for the given relation name. |
|
* |
|
* @param string $name |
|
* @param array $params |
|
* @return \Illuminate\Database\Eloquent\Relations\Relation |
|
*/ |
|
public function getRelationCustom($name, array $params) |
|
{ |
|
// 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 error prone. We don't want constraints because we add eager ones. |
|
$relation = Relation::noConstraints(function () use ($name, $params) { |
|
try { |
|
return $this->getModel()->newInstance()->$name(...$params); |
|
} catch (BadMethodCallException $e) { |
|
throw RelationNotFoundException::make($this->getModel(), $name); |
|
} |
|
}); |
|
|
|
$nested = $this->relationsNestedUnder($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; |
|
} |
|
|
|
/** |
|
* Eagerly load the relationship on a set of models. |
|
* |
|
* @param array $models |
|
* @param string $name |
|
* @param \Closure $constraints |
|
* @return array |
|
*/ |
|
protected function eagerLoadRelation(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. |
|
|
|
// We'll also catch the RelationNotFoundException and just return the models |
|
try { |
|
$relation = $this->getRelation($name); |
|
} catch (RelationNotFoundException $e) { |
|
return $models; |
|
} |
|
$relation->addEagerConstraints($models); |
|
|
|
$constraints($relation); |
|
|
|
// 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. |
|
return $relation->match( |
|
$relation->initRelation($models, $name), |
|
$relation->getEager(), |
|
$name |
|
); |
|
} |
|
|
|
/** |
|
* Eagerly load the relationship on a set of models. |
|
* |
|
* @param array $models |
|
* @param string $name |
|
* @param array $constraints |
|
* @return array |
|
*/ |
|
protected function eagerLoadRelationCustom(array $models, $name, $constraints) |
|
{ |
|
// First we will shave off the extra digit and pipe symbol |
|
if (mb_strpos($name, '|') !== false) { |
|
$name = mb_substr($name, 0, mb_strpos($name, '|')); |
|
} |
|
// Then we will separate the custom params from the constraint closure |
|
if (isset($constraints['params'])) { |
|
$params = $constraints['params']; |
|
} |
|
if (isset($constraints['as'])) { |
|
$as = $constraints['as']; |
|
} |
|
if (isset($constraints['constraints'])) { |
|
$constraints = $constraints['constraints']; |
|
} else { |
|
$constraints = function ($query) { |
|
}; |
|
} |
|
// Then we will "back up" the existing where conditions on the query so we can |
|
// add our eager constraints. |
|
// We will call either the custom getRelation function which accepts |
|
// parameters for the relationship function on the model. |
|
if (isset($params)) { |
|
try { |
|
$relation = $this->getRelationCustom($name, $params); |
|
} catch (RelationNotFoundException $e) { |
|
return $models; |
|
} |
|
} |
|
// Or the original getRelation function |
|
if (!isset($relation)) { |
|
try { |
|
$relation = $this->getRelation($name); |
|
} catch (RelationNotFoundException $e) { |
|
return $models; |
|
} |
|
} |
|
|
|
// Then we will merge the wheres that were on the |
|
// query back to it in order that any where conditions might be specified. |
|
$relation->addEagerConstraints($models); |
|
|
|
$constraints($relation); |
|
|
|
// 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. |
|
return $relation->match( |
|
$relation->initRelation($models, $as ?? $name), |
|
$relation->getEager(), |
|
$as ?? $name |
|
); |
|
} |
|
|
|
/** |
|
* 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 "name" value is a numeric key, we can assume that no |
|
// constraints have been specified. We'll just put an empty |
|
// Closure there, so that we can treat them all the same. |
|
if (is_numeric($name)) { |
|
$name = $constraints; |
|
|
|
list($name, $constraints) = Str::contains($name, ':') |
|
? $this->createSelectWithConstraint($name) |
|
: [$name, function () { |
|
}]; |
|
} |
|
|
|
if (is_array($constraints) && count($constraints) > 0 && is_numeric(key($constraints))) { |
|
// Here we assume there are no nested withs in withs that have parameters supplied |
|
// We check if there are multiple param arrays in the same constraint and split them accordingly |
|
$i = 0; |
|
foreach ($constraints as $constraint) { |
|
$results[$name . '|' . $i++] = $constraint; |
|
} |
|
} elseif (is_array($constraints) && count($constraints) > 0 && !is_numeric(key($constraints))) { |
|
// Here we assume the same thing, but we don't split the array since it only contains one constraint |
|
$results[$name] = $constraints; |
|
} else { |
|
// 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->addNestedWiths($name, $results); |
|
|
|
$results[$name] = $constraints; |
|
} |
|
} |
|
|
|
return $results; |
|
} |
|
} |