Viewing file: MorphTo.php (10.61 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Illuminate\Database\Eloquent\Relations;
use BadMethodCallException; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
class MorphTo extends BelongsTo { use InteractsWithDictionary;
/** * The type of the polymorphic relation. * * @var string */ protected $morphType;
/** * The models whose relations are being eager loaded. * * @var \Illuminate\Database\Eloquent\Collection */ protected $models;
/** * All of the models keyed by ID. * * @var array */ protected $dictionary = [];
/** * A buffer of dynamic calls to query macros. * * @var array */ protected $macroBuffer = [];
/** * A map of relations to load for each individual morph type. * * @var array */ protected $morphableEagerLoads = [];
/** * A map of relationship counts to load for each individual morph type. * * @var array */ protected $morphableEagerLoadCounts = [];
/** * A map of constraints to apply for each individual morph type. * * @var array */ protected $morphableConstraints = [];
/** * Create a new morph to relationship instance. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Model $parent * @param string $foreignKey * @param string $ownerKey * @param string $type * @param string $relation * @return void */ public function __construct(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation) { $this->morphType = $type;
parent::__construct($query, $parent, $foreignKey, $ownerKey, $relation); }
/** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { $this->buildDictionary($this->models = Collection::make($models)); }
/** * Build a dictionary with the models. * * @param \Illuminate\Database\Eloquent\Collection $models * @return void */ protected function buildDictionary(Collection $models) { foreach ($models as $model) { if ($model->{$this->morphType}) { $morphTypeKey = $this->getDictionaryKey($model->{$this->morphType}); $foreignKeyKey = $this->getDictionaryKey($model->{$this->foreignKey});
$this->dictionary[$morphTypeKey][$foreignKeyKey][] = $model; } } }
/** * Get the results of the relationship. * * Called via eager load method of Eloquent query builder. * * @return mixed */ public function getEager() { foreach (array_keys($this->dictionary) as $type) { $this->matchToMorphParents($type, $this->getResultsByType($type)); }
return $this->models; }
/** * Get all of the relation results for a type. * * @param string $type * @return \Illuminate\Database\Eloquent\Collection */ protected function getResultsByType($type) { $instance = $this->createModelByType($type);
$ownerKey = $this->ownerKey ?? $instance->getKeyName();
$query = $this->replayMacros($instance->newQuery()) ->mergeConstraintsFrom($this->getQuery()) ->with(array_merge( $this->getQuery()->getEagerLoads(), (array) ($this->morphableEagerLoads[get_class($instance)] ?? []) )) ->withCount( (array) ($this->morphableEagerLoadCounts[get_class($instance)] ?? []) );
if ($callback = ($this->morphableConstraints[get_class($instance)] ?? null)) { $callback($query); }
$whereIn = $this->whereInMethod($instance, $ownerKey);
return $query->{$whereIn}( $instance->getTable().'.'.$ownerKey, $this->gatherKeysByType($type, $instance->getKeyType()) )->get(); }
/** * Gather all of the foreign keys for a given type. * * @param string $type * @param string $keyType * @return array */ protected function gatherKeysByType($type, $keyType) { return $keyType !== 'string' ? array_keys($this->dictionary[$type]) : array_map(function ($modelId) { return (string) $modelId; }, array_filter(array_keys($this->dictionary[$type]))); }
/** * Create a new model instance by type. * * @param string $type * @return \Illuminate\Database\Eloquent\Model */ public function createModelByType($type) { $class = Model::getActualClassNameForMorph($type);
return tap(new $class, function ($instance) { if (! $instance->getConnectionName()) { $instance->setConnection($this->getConnection()->getName()); } }); }
/** * Match the eagerly loaded results to their parents. * * @param array $models * @param \Illuminate\Database\Eloquent\Collection $results * @param string $relation * @return array */ public function match(array $models, Collection $results, $relation) { return $models; }
/** * Match the results for a given type to their parents. * * @param string $type * @param \Illuminate\Database\Eloquent\Collection $results * @return void */ protected function matchToMorphParents($type, Collection $results) { foreach ($results as $result) { $ownerKey = ! is_null($this->ownerKey) ? $this->getDictionaryKey($result->{$this->ownerKey}) : $result->getKey();
if (isset($this->dictionary[$type][$ownerKey])) { foreach ($this->dictionary[$type][$ownerKey] as $model) { $model->setRelation($this->relationName, $result); } } } }
/** * Associate the model instance to the given parent. * * @param \Illuminate\Database\Eloquent\Model $model * @return \Illuminate\Database\Eloquent\Model */ public function associate($model) { $this->parent->setAttribute( $this->foreignKey, $model instanceof Model ? $model->{$this->ownerKey ?: $model->getKeyName()} : null );
$this->parent->setAttribute( $this->morphType, $model instanceof Model ? $model->getMorphClass() : null );
return $this->parent->setRelation($this->relationName, $model); }
/** * Dissociate previously associated model from the given parent. * * @return \Illuminate\Database\Eloquent\Model */ public function dissociate() { $this->parent->setAttribute($this->foreignKey, null);
$this->parent->setAttribute($this->morphType, null);
return $this->parent->setRelation($this->relationName, null); }
/** * Touch all of the related models for the relationship. * * @return void */ public function touch() { if (! is_null($this->child->{$this->foreignKey})) { parent::touch(); } }
/** * Make a new related instance for the given model. * * @param \Illuminate\Database\Eloquent\Model $parent * @return \Illuminate\Database\Eloquent\Model */ protected function newRelatedInstanceFor(Model $parent) { return $parent->{$this->getRelationName()}()->getRelated()->newInstance(); }
/** * Get the foreign key "type" name. * * @return string */ public function getMorphType() { return $this->morphType; }
/** * Get the dictionary used by the relationship. * * @return array */ public function getDictionary() { return $this->dictionary; }
/** * Specify which relations to load for a given morph type. * * @param array $with * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function morphWith(array $with) { $this->morphableEagerLoads = array_merge( $this->morphableEagerLoads, $with );
return $this; }
/** * Specify which relationship counts to load for a given morph type. * * @param array $withCount * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function morphWithCount(array $withCount) { $this->morphableEagerLoadCounts = array_merge( $this->morphableEagerLoadCounts, $withCount );
return $this; }
/** * Specify constraints on the query for a given morph type. * * @param array $callbacks * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function constrain(array $callbacks) { $this->morphableConstraints = array_merge( $this->morphableConstraints, $callbacks );
return $this; }
/** * Replay stored macro calls on the actual related instance. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ protected function replayMacros(Builder $query) { foreach ($this->macroBuffer as $macro) { $query->{$macro['method']}(...$macro['parameters']); }
return $query; }
/** * Handle dynamic method calls to the relationship. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { try { $result = parent::__call($method, $parameters);
if (in_array($method, ['select', 'selectRaw', 'selectSub', 'addSelect', 'withoutGlobalScopes'])) { $this->macroBuffer[] = compact('method', 'parameters'); }
return $result; }
// If we tried to call a method that does not exist on the parent Builder instance, // we'll assume that we want to call a query macro (e.g. withTrashed) that only // exists on related models. We will just store the call and replay it later. catch (BadMethodCallException $e) { $this->macroBuffer[] = compact('method', 'parameters');
return $this; } } }
|