123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758 |
- <?php
-
- namespace Illuminate\Database\Eloquent\Concerns;
-
- use Illuminate\Support\Arr;
- use Illuminate\Support\Str;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Database\Eloquent\Builder;
- use Illuminate\Database\Eloquent\Collection;
- use Illuminate\Database\Eloquent\Relations\HasOne;
- use Illuminate\Database\Eloquent\Relations\HasMany;
- use Illuminate\Database\Eloquent\Relations\MorphTo;
- use Illuminate\Database\Eloquent\Relations\MorphOne;
- use Illuminate\Database\Eloquent\Relations\Relation;
- use Illuminate\Database\Eloquent\Relations\BelongsTo;
- use Illuminate\Database\Eloquent\Relations\MorphMany;
- use Illuminate\Database\Eloquent\Relations\MorphToMany;
- use Illuminate\Database\Eloquent\Relations\BelongsToMany;
- use Illuminate\Database\Eloquent\Relations\HasManyThrough;
-
- trait HasRelationships
- {
- /**
- * The loaded relationships for the model.
- *
- * @var array
- */
- protected $relations = [];
-
- /**
- * The relationships that should be touched on save.
- *
- * @var array
- */
- protected $touches = [];
-
- /**
- * The many to many relationship methods.
- *
- * @var array
- */
- public static $manyMethods = [
- 'belongsToMany', 'morphToMany', 'morphedByMany',
- 'guessBelongsToManyRelation', 'findFirstMethodThatIsntRelation',
- ];
-
- /**
- * Define a one-to-one relationship.
- *
- * @param string $related
- * @param string $foreignKey
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\HasOne
- */
- public function hasOne($related, $foreignKey = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- $foreignKey = $foreignKey ?: $this->getForeignKey();
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newHasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
- }
-
- /**
- * Instantiate a new HasOne relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $foreignKey
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\HasOne
- */
- protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
- {
- return new HasOne($query, $parent, $foreignKey, $localKey);
- }
-
- /**
- * Define a polymorphic one-to-one relationship.
- *
- * @param string $related
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphOne
- */
- public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- list($type, $id) = $this->getMorphs($name, $type, $id);
-
- $table = $instance->getTable();
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
- }
-
- /**
- * Instantiate a new MorphOne relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $type
- * @param string $id
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphOne
- */
- protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey)
- {
- return new MorphOne($query, $parent, $type, $id, $localKey);
- }
-
- /**
- * Define an inverse one-to-one or many relationship.
- *
- * @param string $related
- * @param string $foreignKey
- * @param string $ownerKey
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
- */
- public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
- {
- // If no relation name was given, we will use this debug backtrace to extract
- // the calling method's name and use that as the relationship name as most
- // of the time this will be what we desire to use for the relationships.
- if (is_null($relation)) {
- $relation = $this->guessBelongsToRelation();
- }
-
- $instance = $this->newRelatedInstance($related);
-
- // If no foreign key was supplied, we can use a backtrace to guess the proper
- // foreign key name by using the name of the relationship function, which
- // when combined with an "_id" should conventionally match the columns.
- if (is_null($foreignKey)) {
- $foreignKey = Str::snake($relation).'_'.$instance->getKeyName();
- }
-
- // Once we have the foreign key names, we'll just create a new Eloquent query
- // for the related models and returns the relationship instance which will
- // actually be responsible for retrieving and hydrating every relations.
- $ownerKey = $ownerKey ?: $instance->getKeyName();
-
- return $this->newBelongsTo(
- $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation
- );
- }
-
- /**
- * Instantiate a new BelongsTo relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $child
- * @param string $foreignKey
- * @param string $ownerKey
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
- */
- protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
- {
- return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation);
- }
-
- /**
- * Define a polymorphic, inverse one-to-one or many relationship.
- *
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string $ownerKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo
- */
- public function morphTo($name = null, $type = null, $id = null, $ownerKey = null)
- {
- // If no name is provided, we will use the backtrace to get the function name
- // since that is most likely the name of the polymorphic interface. We can
- // use that to get both the class and foreign key that will be utilized.
- $name = $name ?: $this->guessBelongsToRelation();
-
- list($type, $id) = $this->getMorphs(
- Str::snake($name), $type, $id
- );
-
- // If the type value is null it is probably safe to assume we're eager loading
- // the relationship. In this case we'll just pass in a dummy query where we
- // need to remove any eager loads that may already be defined on a model.
- return empty($class = $this->{$type})
- ? $this->morphEagerTo($name, $type, $id, $ownerKey)
- : $this->morphInstanceTo($class, $name, $type, $id, $ownerKey);
- }
-
- /**
- * Define a polymorphic, inverse one-to-one or many relationship.
- *
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string $ownerKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo
- */
- protected function morphEagerTo($name, $type, $id, $ownerKey)
- {
- return $this->newMorphTo(
- $this->newQuery()->setEagerLoads([]), $this, $id, $ownerKey, $type, $name
- );
- }
-
- /**
- * Define a polymorphic, inverse one-to-one or many relationship.
- *
- * @param string $target
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string $ownerKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo
- */
- protected function morphInstanceTo($target, $name, $type, $id, $ownerKey)
- {
- $instance = $this->newRelatedInstance(
- static::getActualClassNameForMorph($target)
- );
-
- return $this->newMorphTo(
- $instance->newQuery(), $this, $id, $ownerKey ?? $instance->getKeyName(), $type, $name
- );
- }
-
- /**
- * Instantiate a new MorphTo relationship.
- *
- * @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 \Illuminate\Database\Eloquent\Relations\MorphTo
- */
- protected function newMorphTo(Builder $query, Model $parent, $foreignKey, $ownerKey, $type, $relation)
- {
- return new MorphTo($query, $parent, $foreignKey, $ownerKey, $type, $relation);
- }
-
- /**
- * Retrieve the actual class name for a given morph class.
- *
- * @param string $class
- * @return string
- */
- public static function getActualClassNameForMorph($class)
- {
- return Arr::get(Relation::morphMap() ?: [], $class, $class);
- }
-
- /**
- * Guess the "belongs to" relationship name.
- *
- * @return string
- */
- protected function guessBelongsToRelation()
- {
- list($one, $two, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
-
- return $caller['function'];
- }
-
- /**
- * Define a one-to-many relationship.
- *
- * @param string $related
- * @param string $foreignKey
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\HasMany
- */
- public function hasMany($related, $foreignKey = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- $foreignKey = $foreignKey ?: $this->getForeignKey();
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newHasMany(
- $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
- );
- }
-
- /**
- * Instantiate a new HasMany relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $foreignKey
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\HasMany
- */
- protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
- {
- return new HasMany($query, $parent, $foreignKey, $localKey);
- }
-
- /**
- * Define a has-many-through relationship.
- *
- * @param string $related
- * @param string $through
- * @param string|null $firstKey
- * @param string|null $secondKey
- * @param string|null $localKey
- * @param string|null $secondLocalKey
- * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
- */
- public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
- {
- $through = new $through;
-
- $firstKey = $firstKey ?: $this->getForeignKey();
-
- $secondKey = $secondKey ?: $through->getForeignKey();
-
- return $this->newHasManyThrough(
- $this->newRelatedInstance($related)->newQuery(), $this, $through,
- $firstKey, $secondKey, $localKey ?: $this->getKeyName(),
- $secondLocalKey ?: $through->getKeyName()
- );
- }
-
- /**
- * Instantiate a new HasManyThrough relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $farParent
- * @param \Illuminate\Database\Eloquent\Model $throughParent
- * @param string $firstKey
- * @param string $secondKey
- * @param string $localKey
- * @param string $secondLocalKey
- * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
- */
- protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
- {
- return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
- }
-
- /**
- * Define a polymorphic one-to-many relationship.
- *
- * @param string $related
- * @param string $name
- * @param string $type
- * @param string $id
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphMany
- */
- public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
- {
- $instance = $this->newRelatedInstance($related);
-
- // Here we will gather up the morph type and ID for the relationship so that we
- // can properly query the intermediate table of a relation. Finally, we will
- // get the table and create the relationship instances for the developers.
- list($type, $id) = $this->getMorphs($name, $type, $id);
-
- $table = $instance->getTable();
-
- $localKey = $localKey ?: $this->getKeyName();
-
- return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
- }
-
- /**
- * Instantiate a new MorphMany relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $type
- * @param string $id
- * @param string $localKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphMany
- */
- protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
- {
- return new MorphMany($query, $parent, $type, $id, $localKey);
- }
-
- /**
- * Define a many-to-many relationship.
- *
- * @param string $related
- * @param string $table
- * @param string $foreignPivotKey
- * @param string $relatedPivotKey
- * @param string $parentKey
- * @param string $relatedKey
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
- $parentKey = null, $relatedKey = null, $relation = null)
- {
- // If no relationship name was passed, we will pull backtraces to get the
- // name of the calling function. We will use that function name as the
- // title of this relation since that is a great convention to apply.
- if (is_null($relation)) {
- $relation = $this->guessBelongsToManyRelation();
- }
-
- // First, we'll need to determine the foreign key and "other key" for the
- // relationship. Once we have determined the keys we'll make the query
- // instances as well as the relationship instances we need for this.
- $instance = $this->newRelatedInstance($related);
-
- $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
-
- $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
-
- // If no table name was provided, we can guess it by concatenating the two
- // models using underscores in alphabetical order. The two model names
- // are transformed to snake case from their default CamelCase also.
- if (is_null($table)) {
- $table = $this->joiningTable($related);
- }
-
- return $this->newBelongsToMany(
- $instance->newQuery(), $this, $table, $foreignPivotKey,
- $relatedPivotKey, $parentKey ?: $this->getKeyName(),
- $relatedKey ?: $instance->getKeyName(), $relation
- );
- }
-
- /**
- * Instantiate a new BelongsToMany relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $table
- * @param string $foreignPivotKey
- * @param string $relatedPivotKey
- * @param string $parentKey
- * @param string $relatedKey
- * @param string $relationName
- * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
- */
- protected function newBelongsToMany(Builder $query, Model $parent, $table, $foreignPivotKey, $relatedPivotKey,
- $parentKey, $relatedKey, $relationName = null)
- {
- return new BelongsToMany($query, $parent, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName);
- }
-
- /**
- * Define a polymorphic many-to-many relationship.
- *
- * @param string $related
- * @param string $name
- * @param string $table
- * @param string $foreignPivotKey
- * @param string $relatedPivotKey
- * @param string $parentKey
- * @param string $relatedKey
- * @param bool $inverse
- * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
- */
- public function morphToMany($related, $name, $table = null, $foreignPivotKey = null,
- $relatedPivotKey = null, $parentKey = null,
- $relatedKey = null, $inverse = false)
- {
- $caller = $this->guessBelongsToManyRelation();
-
- // First, we will need to determine the foreign key and "other key" for the
- // relationship. Once we have determined the keys we will make the query
- // instances, as well as the relationship instances we need for these.
- $instance = $this->newRelatedInstance($related);
-
- $foreignPivotKey = $foreignPivotKey ?: $name.'_id';
-
- $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
-
- // Now we're ready to create a new query builder for this related model and
- // the relationship instances for this relation. This relations will set
- // appropriate query constraints then entirely manages the hydrations.
- $table = $table ?: Str::plural($name);
-
- return $this->newMorphToMany(
- $instance->newQuery(), $this, $name, $table,
- $foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(),
- $relatedKey ?: $instance->getKeyName(), $caller, $inverse
- );
- }
-
- /**
- * Instantiate a new HasManyThrough relationship.
- *
- * @param \Illuminate\Database\Eloquent\Builder $query
- * @param \Illuminate\Database\Eloquent\Model $parent
- * @param string $name
- * @param string $table
- * @param string $foreignPivotKey
- * @param string $relatedPivotKey
- * @param string $parentKey
- * @param string $relatedKey
- * @param string $relationName
- * @param bool $inverse
- * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
- */
- protected function newMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey,
- $relatedPivotKey, $parentKey, $relatedKey,
- $relationName = null, $inverse = false)
- {
- return new MorphToMany($query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey,
- $relationName, $inverse);
- }
-
- /**
- * Define a polymorphic, inverse many-to-many relationship.
- *
- * @param string $related
- * @param string $name
- * @param string $table
- * @param string $foreignPivotKey
- * @param string $relatedPivotKey
- * @param string $parentKey
- * @param string $relatedKey
- * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
- */
- public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null,
- $relatedPivotKey = null, $parentKey = null, $relatedKey = null)
- {
- $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey();
-
- // For the inverse of the polymorphic many-to-many relations, we will change
- // the way we determine the foreign and other keys, as it is the opposite
- // of the morph-to-many method since we're figuring out these inverses.
- $relatedPivotKey = $relatedPivotKey ?: $name.'_id';
-
- return $this->morphToMany(
- $related, $name, $table, $foreignPivotKey,
- $relatedPivotKey, $parentKey, $relatedKey, true
- );
- }
-
- /**
- * Get the relationship name of the belongs to many.
- *
- * @return string
- */
- protected function guessBelongsToManyRelation()
- {
- $caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
- return ! in_array($trace['function'], Model::$manyMethods);
- });
-
- return ! is_null($caller) ? $caller['function'] : null;
- }
-
- /**
- * Get the joining table name for a many-to-many relation.
- *
- * @param string $related
- * @return string
- */
- public function joiningTable($related)
- {
- // The joining table name, by convention, is simply the snake cased models
- // sorted alphabetically and concatenated with an underscore, so we can
- // just sort the models and join them together to get the table name.
- $models = [
- Str::snake(class_basename($related)),
- Str::snake(class_basename($this)),
- ];
-
- // Now that we have the model names in an array we can just sort them and
- // use the implode function to join them together with an underscores,
- // which is typically used by convention within the database system.
- sort($models);
-
- return strtolower(implode('_', $models));
- }
-
- /**
- * Determine if the model touches a given relation.
- *
- * @param string $relation
- * @return bool
- */
- public function touches($relation)
- {
- return in_array($relation, $this->touches);
- }
-
- /**
- * Touch the owning relations of the model.
- *
- * @return void
- */
- public function touchOwners()
- {
- foreach ($this->touches as $relation) {
- $this->$relation()->touch();
-
- if ($this->$relation instanceof self) {
- $this->$relation->fireModelEvent('saved', false);
-
- $this->$relation->touchOwners();
- } elseif ($this->$relation instanceof Collection) {
- $this->$relation->each(function (Model $relation) {
- $relation->touchOwners();
- });
- }
- }
- }
-
- /**
- * Get the polymorphic relationship columns.
- *
- * @param string $name
- * @param string $type
- * @param string $id
- * @return array
- */
- protected function getMorphs($name, $type, $id)
- {
- return [$type ?: $name.'_type', $id ?: $name.'_id'];
- }
-
- /**
- * Get the class name for polymorphic relations.
- *
- * @return string
- */
- public function getMorphClass()
- {
- $morphMap = Relation::morphMap();
-
- if (! empty($morphMap) && in_array(static::class, $morphMap)) {
- return array_search(static::class, $morphMap, true);
- }
-
- return static::class;
- }
-
- /**
- * Create a new model instance for a related model.
- *
- * @param string $class
- * @return mixed
- */
- protected function newRelatedInstance($class)
- {
- return tap(new $class, function ($instance) {
- if (! $instance->getConnectionName()) {
- $instance->setConnection($this->connection);
- }
- });
- }
-
- /**
- * Get all the loaded relations for the instance.
- *
- * @return array
- */
- public function getRelations()
- {
- return $this->relations;
- }
-
- /**
- * Get a specified relationship.
- *
- * @param string $relation
- * @return mixed
- */
- public function getRelation($relation)
- {
- return $this->relations[$relation];
- }
-
- /**
- * Determine if the given relation is loaded.
- *
- * @param string $key
- * @return bool
- */
- public function relationLoaded($key)
- {
- return array_key_exists($key, $this->relations);
- }
-
- /**
- * Set the given relationship on the model.
- *
- * @param string $relation
- * @param mixed $value
- * @return $this
- */
- public function setRelation($relation, $value)
- {
- $this->relations[$relation] = $value;
-
- return $this;
- }
-
- /**
- * Unset a loaded relationship.
- *
- * @param string $relation
- * @return $this
- */
- public function unsetRelation($relation)
- {
- unset($this->relations[$relation]);
-
- return $this;
- }
-
- /**
- * Set the entire relations array on the model.
- *
- * @param array $relations
- * @return $this
- */
- public function setRelations(array $relations)
- {
- $this->relations = $relations;
-
- return $this;
- }
-
- /**
- * Get the relationships that are touched on save.
- *
- * @return array
- */
- public function getTouchedRelations()
- {
- return $this->touches;
- }
-
- /**
- * Set the relationships that are touched on save.
- *
- * @param array $touches
- * @return $this
- */
- public function setTouchedRelations(array $touches)
- {
- $this->touches = $touches;
-
- return $this;
- }
- }
|