diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index 9ce32d4a..68f04961 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -169,7 +169,18 @@ public function getRelationDefinition($name) public function getRelationTypeDefinitions($type) { if (in_array($type, static::$relationTypes)) { - return $this->{$type}; + $definitions = $this->{$type} ?: []; + + // Handle renaming otherKey to relatedKey + // @see https://github.com/laravel/framework/commit/d2a77776295cb155b985526c1fa1fddc190adb07 + // @since v1.1.8 + foreach ($definitions as $relation => $options) { + if (array_key_exists('otherKey', $options) && !array_key_exists('relatedKey', $options)) { + $definitions[$relation]['relatedKey'] = $options['otherKey']; + } + } + + return $definitions; } return []; @@ -309,18 +320,18 @@ protected function handleRelation($relationName) switch ($relationType) { case 'hasOne': case 'hasMany': - $relation = $this->validateRelationArgs($relationName, ['key', 'otherKey']); - $relationObj = $this->$relationType($relation[0], $relation['key'], $relation['otherKey'], $relationName); + $relation = $this->validateRelationArgs($relationName, ['key', 'relatedKey']); + $relationObj = $this->$relationType($relation[0], $relation['key'], $relation['relatedKey'], $relationName); break; case 'belongsTo': - $relation = $this->validateRelationArgs($relationName, ['key', 'otherKey']); - $relationObj = $this->$relationType($relation[0], $relation['key'], $relation['otherKey'], $relationName); + $relation = $this->validateRelationArgs($relationName, ['key', 'relatedKey']); + $relationObj = $this->$relationType($relation[0], $relation['key'], $relation['relatedKey'], $relationName); break; case 'belongsToMany': - $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'otherKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps']); - $relationObj = $this->$relationType($relation[0], $relation['table'], $relation['key'], $relation['otherKey'], $relation['parentKey'], $relation['relatedKey'], $relationName); + $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'relatedKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps']); + $relationObj = $this->$relationType($relation[0], $relation['table'], $relation['key'], $relation['relatedKey'], $relation['parentKey'], $relation['relatedKey'], $relationName); break; case 'morphTo': @@ -335,13 +346,13 @@ protected function handleRelation($relationName) break; case 'morphToMany': - $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'otherKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps'], ['name']); - $relationObj = $this->$relationType($relation[0], $relation['name'], $relation['table'], $relation['key'], $relation['otherKey'], $relation['parentKey'], $relation['relatedKey'], false, $relationName); + $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'relatedKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps'], ['name']); + $relationObj = $this->$relationType($relation[0], $relation['name'], $relation['table'], $relation['key'], $relation['relatedKey'], $relation['parentKey'], $relation['relatedKey'], false, $relationName); break; case 'morphedByMany': - $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'otherKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps'], ['name']); - $relationObj = $this->$relationType($relation[0], $relation['name'], $relation['table'], $relation['key'], $relation['otherKey'], $relation['parentKey'], $relation['relatedKey'], $relationName); + $relation = $this->validateRelationArgs($relationName, ['table', 'key', 'relatedKey', 'parentKey', 'relatedKey', 'pivot', 'timestamps'], ['name']); + $relationObj = $this->$relationType($relation[0], $relation['name'], $relation['table'], $relation['key'], $relation['relatedKey'], $relation['parentKey'], $relation['relatedKey'], $relationName); break; case 'attachOne': @@ -352,8 +363,8 @@ protected function handleRelation($relationName) case 'hasOneThrough': case 'hasManyThrough': - $relation = $this->validateRelationArgs($relationName, ['key', 'throughKey', 'otherKey', 'secondOtherKey'], ['through']); - $relationObj = $this->$relationType($relation[0], $relation['through'], $relation['key'], $relation['throughKey'], $relation['otherKey'], $relation['secondOtherKey']); + $relation = $this->validateRelationArgs($relationName, ['key', 'throughKey', 'relatedKey', 'secondOtherKey'], ['through']); + $relationObj = $this->$relationType($relation[0], $relation['through'], $relation['key'], $relation['throughKey'], $relation['relatedKey'], $relation['secondOtherKey']); break; default: diff --git a/src/Database/Model.php b/src/Database/Model.php index 2add8b61..1fe7e12a 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -723,7 +723,7 @@ public function newPivot(EloquentModel $parent, array $attributes, $table, $exis { return $using ? $using::fromRawAttributes($parent, $attributes, $table, $exists) - : new Pivot($parent, $attributes, $table, $exists); + : Pivot::fromAttributes($parent, $attributes, $table, $exists); } /** @@ -741,7 +741,7 @@ public function newRelationPivot($relationName, $parent, $attributes, $table, $e if (!is_null($definition) && array_key_exists('pivotModel', $definition)) { $pivotModel = $definition['pivotModel']; - return new $pivotModel($parent, $attributes, $table, $exists); + return $pivotModel::fromAttributes($parent, $attributes, $table, $exists); } } diff --git a/src/Database/Pivot.php b/src/Database/Pivot.php index 5172b7df..84022c7d 100644 --- a/src/Database/Pivot.php +++ b/src/Database/Pivot.php @@ -1,7 +1,8 @@ timestamps = $instance->hasTimestampAttributes($attributes); // The pivot model is a "dynamic" model since we will set the tables dynamically // for the instance. This allows it work for any intermediate tables for the // many to many relationship that are defined by this developer's classes. - $this->setRawAttributes($attributes, true); - - $this->setTable($table); - - $this->setConnection($parent->getConnectionName()); + $instance->setConnection($parent->getConnectionName()) + ->setTable($table) + ->forceFill($attributes) + ->syncOriginal(); // We store off the parent instance so we will access the timestamp column names // for the model, since the pivot model timestamps aren't easily configurable // from the developer's point of view. We can use the parents to get these. - $this->parent = $parent; + $instance->pivotParent = $parent; + + $instance->exists = $exists; + + return $instance; + } + + /** + * Create a new pivot model from raw values returned from a query. + * + * @param \Illuminate\Database\Eloquent\Model $parent + * @param array $attributes + * @param string $table + * @param bool $exists + * @return static + */ + public static function fromRawAttributes(ModelBase $parent, $attributes, $table, $exists = false) + { + $instance = static::fromAttributes($parent, [], $table, $exists); + + $instance->timestamps = $instance->hasTimestampAttributes($attributes); - $this->exists = $exists; + $instance->setRawAttributes($attributes, $exists); - $this->timestamps = $this->hasTimestampAttributes(); + return $instance; } /** * Set the keys for a save update query. * - * @param \Illuminate\Database\Eloquent\Builder + * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ - protected function setKeysForSaveQuery(BuilderBase $query) + protected function setKeysForSaveQuery(Builder $query) { - $query->where($this->foreignKey, $this->getAttribute($this->foreignKey)); + if (isset($this->attributes[$this->getKeyName()])) { + return parent::setKeysForSaveQuery($query); + } - return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + $query->where($this->foreignKey, $this->getOriginal( + $this->foreignKey, $this->getAttribute($this->foreignKey) + )); + + return $query->where($this->relatedKey, $this->getOriginal( + $this->relatedKey, $this->getAttribute($this->relatedKey) + )); } /** @@ -85,7 +115,19 @@ protected function setKeysForSaveQuery(BuilderBase $query) */ public function delete() { - return $this->getDeleteQuery()->delete(); + if (isset($this->attributes[$this->getKeyName()])) { + return (int) parent::delete(); + } + + if ($this->fireModelEvent('deleting') === false) { + return 0; + } + + $this->touchOwners(); + + return tap($this->getDeleteQuery()->delete(), function () { + $this->fireModelEvent('deleted', false); + }); } /** @@ -95,11 +137,26 @@ public function delete() */ protected function getDeleteQuery() { - $foreign = $this->getAttribute($this->foreignKey); + return $this->newQueryWithoutRelationships()->where([ + $this->foreignKey => $this->getOriginal($this->foreignKey, $this->getAttribute($this->foreignKey)), + $this->relatedKey => $this->getOriginal($this->relatedKey, $this->getAttribute($this->relatedKey)), + ]); + } - $query = $this->newQuery()->where($this->foreignKey, $foreign); + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + if (! isset($this->table)) { + $this->setTable(str_replace( + '\\', '', Str::snake(Str::singular(class_basename($this))) + )); + } - return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + return $this->table; } /** @@ -113,39 +170,50 @@ public function getForeignKey() } /** - * Get the "other key" column name. + * Get the "related key" column name. + * + * @return string + */ + public function getRelatedKey() + { + return $this->relatedKey; + } + + /** + * Get the "related key" column name. * * @return string */ public function getOtherKey() { - return $this->otherKey; + return $this->getRelatedKey(); } /** * Set the key names for the pivot model instance. * * @param string $foreignKey - * @param string $otherKey + * @param string $relatedKey * @return $this */ - public function setPivotKeys($foreignKey, $otherKey) + public function setPivotKeys($foreignKey, $relatedKey) { $this->foreignKey = $foreignKey; - $this->otherKey = $otherKey; + $this->relatedKey = $relatedKey; return $this; } /** - * Determine if the pivot model has timestamp attributes. + * Determine if the pivot model or given attributes has timestamp attributes. * + * @param array|null $attributes * @return bool */ - public function hasTimestampAttributes() + public function hasTimestampAttributes($attributes = null) { - return array_key_exists($this->getCreatedAtColumn(), $this->attributes); + return array_key_exists($this->getCreatedAtColumn(), $attributes ?? $this->attributes); } /** @@ -155,7 +223,9 @@ public function hasTimestampAttributes() */ public function getCreatedAtColumn() { - return $this->parent->getCreatedAtColumn(); + return $this->pivotParent + ? $this->pivotParent->getCreatedAtColumn() + : parent::getCreatedAtColumn(); } /** @@ -165,6 +235,90 @@ public function getCreatedAtColumn() */ public function getUpdatedAtColumn() { - return $this->parent->getUpdatedAtColumn(); + return $this->pivotParent + ? $this->pivotParent->getUpdatedAtColumn() + : parent::getUpdatedAtColumn(); + } + + /** + * Get the queueable identity for the entity. + * + * @return mixed + */ + public function getQueueableId() + { + if (isset($this->attributes[$this->getKeyName()])) { + return $this->getKey(); + } + + return sprintf( + '%s:%s:%s:%s', + $this->foreignKey, $this->getAttribute($this->foreignKey), + $this->relatedKey, $this->getAttribute($this->relatedKey) + ); + } + + /** + * Get a new query to restore one or more models by their queueable IDs. + * + * @param int[]|string[]|string $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryForRestoration($ids) + { + if (is_array($ids)) { + return $this->newQueryForCollectionRestoration($ids); + } + + if (! Str::contains($ids, ':')) { + return parent::newQueryForRestoration($ids); + } + + $segments = explode(':', $ids); + + return $this->newQueryWithoutScopes() + ->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]); + } + + /** + * Get a new query to restore multiple models by their queueable IDs. + * + * @param int[]|string[] $ids + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function newQueryForCollectionRestoration(array $ids) + { + $ids = array_values($ids); + + if (! Str::contains($ids[0], ':')) { + return parent::newQueryForRestoration($ids); + } + + $query = $this->newQueryWithoutScopes(); + + foreach ($ids as $id) { + $segments = explode(':', $id); + + $query->orWhere(function ($query) use ($segments) { + return $query->where($segments[0], $segments[1]) + ->where($segments[2], $segments[3]); + }); + } + + return $query; + } + + /** + * Unset all the loaded relations for the instance. + * + * @return $this + */ + public function unsetRelations() + { + $this->pivotParent = null; + $this->relations = []; + + return $this; } } diff --git a/src/Database/Relations/MorphTo.php b/src/Database/Relations/MorphTo.php index 92e567ea..2dfef088 100644 --- a/src/Database/Relations/MorphTo.php +++ b/src/Database/Relations/MorphTo.php @@ -13,11 +13,11 @@ class MorphTo extends MorphToBase */ protected $relationName; - public function __construct(Builder $query, Model $parent, $foreignKey, $otherKey, $type, $relationName) + public function __construct(Builder $query, Model $parent, $foreignKey, $relatedKey, $type, $relationName) { $this->relationName = $relationName; - parent::__construct($query, $parent, $foreignKey, $otherKey, $type, $relationName); + parent::__construct($query, $parent, $foreignKey, $relatedKey, $type, $relationName); $this->addDefinedConstraints(); } diff --git a/src/Database/Relations/MorphToMany.php b/src/Database/Relations/MorphToMany.php index b2771ca0..54c36ee7 100644 --- a/src/Database/Relations/MorphToMany.php +++ b/src/Database/Relations/MorphToMany.php @@ -46,7 +46,7 @@ class MorphToMany extends BelongsToMany * @param string $name * @param string $table * @param string $foreignKey - * @param string $otherKey + * @param string $relatedKey * @param string $relationName * @param bool $inverse * @return void @@ -57,7 +57,7 @@ public function __construct( $name, $table, $foreignKey, - $otherKey, + $relatedKey, $parentKey, $relatedKey, $relationName = null, @@ -74,7 +74,7 @@ public function __construct( $parent, $table, $foreignKey, - $otherKey, + $relatedKey, $parentKey, $relatedKey, $relationName diff --git a/tests/Database/RelationsTest.php b/tests/Database/RelationsTest.php index 93a1a854..159a1c58 100644 --- a/tests/Database/RelationsTest.php +++ b/tests/Database/RelationsTest.php @@ -306,6 +306,8 @@ class Post extends \Winter\Storm\Database\Model Term::class, 'table' => 'posts_terms', 'key' => 'post_id', + // Explicitly test pre: 1.1.8 option name + // @see https://github.com/laravel/framework/commit/d2a77776295cb155b985526c1fa1fddc190adb07 'otherKey' => 'term_id', 'pivot' => ['data'], 'timestamps' => true, @@ -315,7 +317,7 @@ class Post extends \Winter\Storm\Database\Model Term::class, 'table' => 'posts_terms', 'key' => 'post_id', - 'otherKey' => 'term_id', + 'relatedKey' => 'term_id', 'pivot' => ['data'], 'timestamps' => true, 'conditions' => 'type = "label"', @@ -324,7 +326,7 @@ class Post extends \Winter\Storm\Database\Model Term::class, 'table' => 'posts_terms', 'key' => 'post_id', - 'otherKey' => 'term_id', + 'relatedKey' => 'term_id', 'timestamps' => true, ], ]; @@ -357,8 +359,8 @@ class Term extends \Winter\Storm\Database\Model 'Post', 'table' => 'posts_terms', 'key' => 'term_id', - 'otherKey' => 'post_id', - 'pivot' => ['data'], + 'relatedKey' => 'post_id', + 'pivot' => ['data'], 'timestamps' => true, 'conditions' => 'type = "post"', ],