From 8eaad011502632613a45f20a90b537836cf79cef Mon Sep 17 00:00:00 2001 From: Tycho Huisman Date: Thu, 12 Oct 2023 17:09:14 +0200 Subject: [PATCH 01/74] add MorphToMany attach MorphToMany working detaching works sync working cleanup --- src/Eloquent/HybridRelations.php | 147 +++++++++++++++++++++++- src/Relations/MorphToMany.php | 189 +++++++++++++++++++++++++++++++ tests/Models/Client.php | 7 ++ tests/Models/Label.php | 43 +++++++ tests/Models/User.php | 20 +++- tests/RelationsTest.php | 49 ++++++++ 6 files changed, 447 insertions(+), 8 deletions(-) create mode 100644 src/Relations/MorphToMany.php create mode 100644 tests/Models/Label.php diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 9e11605a3..79b91aede 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -15,6 +15,8 @@ use MongoDB\Laravel\Relations\HasOne; use MongoDB\Laravel\Relations\MorphMany; use MongoDB\Laravel\Relations\MorphTo; +use MongoDB\Laravel\Relations\MorphToMany; + use function debug_backtrace; use function is_subclass_of; @@ -41,7 +43,7 @@ trait HybridRelations public function hasOne($related, $foreignKey = null, $localKey = null) { // Check if it is a relation with an original model. - if (! is_subclass_of($related, MongoDBModel::class)) { + if (!is_subclass_of($related, MongoDBModel::class)) { return parent::hasOne($related, $foreignKey, $localKey); } @@ -70,7 +72,7 @@ public function hasOne($related, $foreignKey = null, $localKey = null) public function morphOne($related, $name, $type = null, $id = null, $localKey = null) { // Check if it is a relation with an original model. - if (! is_subclass_of($related, MongoDBModel::class)) { + if (!is_subclass_of($related, MongoDBModel::class)) { return parent::morphOne($related, $name, $type, $id, $localKey); } @@ -97,7 +99,7 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey = public function hasMany($related, $foreignKey = null, $localKey = null) { // Check if it is a relation with an original model. - if (! is_subclass_of($related, MongoDBModel::class)) { + if (!is_subclass_of($related, MongoDBModel::class)) { return parent::hasMany($related, $foreignKey, $localKey); } @@ -126,7 +128,7 @@ public function hasMany($related, $foreignKey = null, $localKey = null) public function morphMany($related, $name, $type = null, $id = null, $localKey = null) { // Check if it is a relation with an original model. - if (! is_subclass_of($related, MongoDBModel::class)) { + if (!is_subclass_of($related, MongoDBModel::class)) { return parent::morphMany($related, $name, $type, $id, $localKey); } @@ -166,7 +168,7 @@ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relat } // Check if it is a relation with an original model. - if (! is_subclass_of($related, MongoDBModel::class)) { + if (!is_subclass_of($related, MongoDBModel::class)) { return parent::belongsTo($related, $foreignKey, $ownerKey, $relation); } @@ -278,7 +280,7 @@ public function belongsToMany( } // Check if it is a relation with an original model. - if (! is_subclass_of($related, MongoDBModel::class)) { + if (!is_subclass_of($related, MongoDBModel::class)) { return parent::belongsToMany( $related, $collection, @@ -323,6 +325,139 @@ public function belongsToMany( ); } + /** + * Define a many-to-many relationship. + * + * @param string $related + * @param string $collection + * @param string $foreignKey + * @param string $otherKey + * @param string $parentKey + * @param string $relatedKey + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function morphToMany( + $related, + $name, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null, + $relation = null, + $inverse = false + ) { + + // Check if it is a relation with an original model. + if (!is_subclass_of($related, \Mongodb\Laravel\Eloquent\Model::class)) { + return parent::MorphToMany( + $related, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $inverse, + ); + } + + $caller = $this->guessBelongsToManyRelation(); + + $instance = new $related; + + $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; + + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; + + // Now we're ready to create a new query builder for the related model and + // the relationship instances for this relation. This relation will set + // appropriate query constraints then entirely manage the hydrations. + if (!$table) { + $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); + + $lastWord = array_pop($words); + + $table = implode('', $words) . Str::plural($lastWord); + } + + return new MorphToMany( + $instance->newQuery(), + $this, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey ?: $this->getKeyName(), + $relatedKey ?: $instance->getKeyName(), + $caller, + $inverse, + ); + } + + /** + * Define a polymorphic, inverse many-to-many relationship. + * + * @param string $related + * @param string $name + * @param string|null $table + * @param string|null $foreignPivotKey + * @param string|null $relatedPivotKey + * @param string|null $parentKey + * @param string|null $relatedKey + * @param string|null $relation + * @param bool $inverse + * + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany + */ + public function morphedByMany( + $related, + $name, + $table = null, + $foreignPivotKey = null, + $relatedPivotKey = null, + $parentKey = null, + $relatedKey = null, + $inverse = false + ) { + $caller = $this->guessBelongsToManyRelation(); + + // $instance = new $related; + // 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'; + + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; + + return $this->morphToMany( + $related, + $name, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + null, + true, + ); + } + + /** + * Get the relationship name of the belongs to many. + * + * @return string + */ + protected function guessBelongsToManyRelation() + { + if (method_exists($this, 'getBelongsToManyCaller')) { + return $this->getBelongsToManyCaller(); + } + + return parent::guessBelongsToManyRelation(); + } + /** @inheritdoc */ public function newEloquentBuilder($query) { diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php new file mode 100644 index 000000000..01c669c28 --- /dev/null +++ b/src/Relations/MorphToMany.php @@ -0,0 +1,189 @@ +morphType = $name . '_type'; + $this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass(); + + parent::__construct( + $query, + $parent, + $table, + $foreignPivotKey, + $relatedPivotKey, + $parentKey, + $relatedKey, + $relationName, + ); + } + + /** + * Attach a model to the parent. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * + * @return void + */ + public function attach($id, array $attributes = [], $touch = true) + { + if ($id instanceof Model) { + $model = $id; + + $id = $model->getKey(); + + // Attach the new parent id to the related model. + $model->push($this->table, [ + $this->foreignPivotKey => $this->parent->getKey(), + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ], true); + } else { + if ($id instanceof Collection) { + $id = $id->modelKeys(); + } + + $query = $this->newRelatedQuery(); + + $query->whereIn($this->related->getKeyName(), (array) $id); + + // Attach the new parent id to the related model. + $query->push($this->table, [ + $this->foreignPivotKey => $this->parent->getKey(), + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ], true); + } + + // Attach the new ids to the parent model. + $this->parent->push($this->relatedPivotKey, (array) $id, true); + + if ($touch) { + $this->touchIfTouching(); + } + } + + /** @inheritdoc */ + public function detach($ids = [], $touch = true) + { + if ($ids instanceof Model) { + $ids = (array) $ids->getKey(); + } + + $query = $this->newRelatedQuery(); + + // If associated IDs were passed to the method we will only delete those + // associations, otherwise all of the association ties will be broken. + // We'll return the numbers of affected rows when we do the deletes. + $ids = (array) $ids; + + // Detach all ids from the parent model. + $this->parent->pull($this->relatedPivotKey, $ids); + + // Prepare the query to select all related objects. + if (count($ids) > 0) { + $query->whereIn($this->related->getKeyName(), $ids); + } + + // Remove the relation to the parent. + // $query->pull($this->foreignPivotKey, $this->foreignPivotKey, $this->parent->getKey()); + $query->pull($this->table, [ + $this->foreignPivotKey => $this->parent->getKey(), + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ]); + + if ($touch) { + $this->touchIfTouching(); + } + + return count($ids); + } + + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the class name of the parent model. + * + * @return string + */ + public function getMorphClass() + { + return $this->morphClass; + } + + /** + * Get the indicator for a reverse relationship. + * + * @return bool + */ + public function getInverse() + { + return $this->inverse; + } + + /** + * Set the where clause for the relation query. + * + * @return $this + */ + protected function setWhere() + { + $foreign = $this->getForeignKey(); + + if ($this->getInverse()) { + $this->query->where($foreign, '=', $this->parent->getKey()); + } else { + $relatedModels = $this->parent->{$this->relatedPivotKey} ?? []; + $this->query->whereIn($this->relatedKey, $relatedModels); + } + + return $this; + } +} diff --git a/tests/Models/Client.php b/tests/Models/Client.php index 7ee8cec4a..6f8859f63 100644 --- a/tests/Models/Client.php +++ b/tests/Models/Client.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphOne; use MongoDB\Laravel\Eloquent\Model as Eloquent; +// use Illuminate\Database\Eloquent\Relations\MorphToMany; class Client extends Eloquent { @@ -29,4 +30,10 @@ public function addresses(): HasMany { return $this->hasMany(Address::class, 'data.client_id', 'data.client_id'); } + + // labels + public function labels() + { + return $this->morphToMany(Label::class, 'labelled'); + } } diff --git a/tests/Models/Label.php b/tests/Models/Label.php new file mode 100644 index 000000000..efe3abf26 --- /dev/null +++ b/tests/Models/Label.php @@ -0,0 +1,43 @@ +morphedByMany(User::class, 'labelled'); + } + + /** + * Get all of the videos that are assigned this tag. + */ + public function clients() + { + return $this->morphedByMany(Client::class, 'labelled'); + } +} diff --git a/tests/Models/User.php b/tests/Models/User.php index 523b489e7..5e832c5cf 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -38,12 +38,22 @@ class User extends Eloquent implements AuthenticatableContract, CanResetPassword use Notifiable; use MassPrunable; - protected $connection = 'mongodb'; - protected $casts = [ + protected $connection = 'mongodb'; + protected $casts = [ 'birthday' => 'datetime', 'entry.date' => 'datetime', 'member_status' => MemberStatus::class, ]; + + protected $fillable = [ + 'name', + 'email', + 'title', + 'age', + 'birthday', + 'username', + 'member_status', + ]; protected static $unguarded = true; public function books() @@ -113,4 +123,10 @@ public function prunable(): Builder { return $this->where('age', '>', 18); } + + // labels + public function labels() + { + return $this->morphToMany(Label::class, 'labelled'); + } } diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 214c6f506..51ac8f902 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -11,6 +11,7 @@ use MongoDB\Laravel\Tests\Models\Client; use MongoDB\Laravel\Tests\Models\Group; use MongoDB\Laravel\Tests\Models\Item; +use MongoDB\Laravel\Tests\Models\Label; use MongoDB\Laravel\Tests\Models\Photo; use MongoDB\Laravel\Tests\Models\Role; use MongoDB\Laravel\Tests\Models\User; @@ -29,6 +30,7 @@ public function tearDown(): void Role::truncate(); Group::truncate(); Photo::truncate(); + Label::truncate(); } public function testHasMany(): void @@ -397,6 +399,53 @@ public function testMorph(): void $this->assertInstanceOf(Client::class, $photos[1]->hasImage); } + public function testMorphToMany(): void + { + + // cerate user + $user = User::updateOrCreate(['name' => 'John Doe']); + // create client + $client = Client::updateOrCreate(['name' => 'Jane Doe']); + // create label + $label = Label::updateOrCreate(['name' => 'My test label']); + $label2 = Label::updateOrCreate(['name' => 'My test label 2']); + + // check attach + + // attach label to models + $user->labels()->attach($label); + $client->labels()->attach($label); + + $this->assertEquals(1, $user->labels->count()); + $this->assertEquals($label->id, $user->labels->first()->id); + + $this->assertEquals(1, $client->labels->count()); + $this->assertEquals($label->id, $client->labels->first()->id); + + // check if label is attached to client + $this->assertEquals($label->id, $client->labels->first()->id); + + // check if label is attached to user + $this->assertEquals($label->id, $user->labels->first()->id); + + // check if client is attached to label + $this->assertEquals($client->id, $label->clients->first()->id); + + // check if user is attached to label + $this->assertEquals($user->id, $label->users->first()->id); + + // check detaching + $user->labels()->detach($label); + $this->assertNotContains($label->_id, $user->fresh()->labels->pluck('_id')->toArray()); + + // check if label still connected to client + $this->assertEquals($label->id, $client->fresh()->labels->first()->id); + + // check sync + $user->labels()->sync([$label->_id, $label2->_id]); + $this->assertCount(2, $user->fresh()->labels); + } + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From acfdb19a930bb084c0a3169877d9d1eb595e10e4 Mon Sep 17 00:00:00 2001 From: Tycho Date: Tue, 24 Oct 2023 17:11:16 +0200 Subject: [PATCH 02/74] requested code cleanup --- src/Relations/MorphToMany.php | 295 ++++++++++++++++++++++++++-------- tests/Models/Client.php | 1 - tests/Models/Label.php | 2 - tests/RelationsTest.php | 2 +- 4 files changed, 225 insertions(+), 75 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 01c669c28..8d559076c 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -4,67 +4,179 @@ namespace MongoDB\Laravel\Relations; +use Arr; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\MorphToMany as EloquentMorphToMany; -class MorphToMany extends BelongsToMany +use function array_diff; +use function array_keys; +use function array_map; +use function array_merge; +use function array_values; +use function count; +use function is_array; +use function is_numeric; + +class MorphToMany extends EloquentMorphToMany { - protected $morphType; + /** + * Get the key for comparing against the parent key in "has" query. + * + * @return string + */ + public function getHasCompareKey() + { + return $this->getForeignKey(); + } + + /** @inheritdoc */ + public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) + { + return $query; + } - protected $morphClass; + /** @inheritdoc */ + protected function hydratePivotRelation(array $models) + { + // Do nothing. + } /** - * Create a new morph to many relationship instance. + * Set the select clause for the relation query. * - * @param Builder $query - * @param Model $parent - * @param string $name - * @param string $table - * @param string $foreignPivotKey - * @param string $relatedPivotKey - * @param string $parentKey - * @param string $relatedKey - * @param string|null $relationName - * @param bool $inverse - * - * @return void + * @return array */ - public function __construct( - Builder $query, - Model $parent, - $name, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey, - $relatedKey, - $relationName = null, - protected $inverse = false, - ) { - $this->morphType = $name . '_type'; - $this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass(); - - parent::__construct( - $query, - $parent, - $table, - $foreignPivotKey, - $relatedPivotKey, - $parentKey, - $relatedKey, - $relationName, - ); + protected function getSelectColumns(array $columns = ['*']) + { + return $columns; + } + + /** @inheritdoc */ + protected function shouldSelect(array $columns = ['*']) + { + return $columns; + } + + /** @inheritdoc */ + public function addConstraints() + { + if (static::$constraints) { + $this->setWhere(); + } } /** - * Attach a model to the parent. - * - * @param mixed $id - * @param array $attributes - * @param bool $touch + * Set the where clause for the relation query. * - * @return void + * @return $this */ + protected function setWhere() + { + $foreign = $this->getForeignKey(); + + if ($this->getInverse()) { + $this->query->where($foreign, '=', $this->parent->getKey()); + } else { + $relatedModels = $this->parent->{$this->relatedPivotKey} ?? []; + $this->query->whereIn($this->relatedKey, $relatedModels); + } + + return $this; + } + + /** @inheritdoc */ + public function save(Model $model, array $joining = [], $touch = true) + { + $model->save(['touch' => false]); + + $this->attach($model, $joining, $touch); + + return $model; + } + + /** @inheritdoc */ + public function create(array $attributes = [], array $joining = [], $touch = true) + { + $instance = $this->related->newInstance($attributes); + + // Once we save the related model, we need to attach it to the base model via + // through intermediate table so we'll use the existing "attach" method to + // accomplish this which will insert the record and any more attributes. + $instance->save(['touch' => false]); + + $this->attach($instance, $joining, $touch); + + return $instance; + } + + /** @inheritdoc */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + if ($ids instanceof Collection) { + $ids = $ids->modelKeys(); + } + + // First we need to attach any of the associated models that are not currently + // in this joining table. We'll spin through the given IDs, checking to see + // if they exist in the array of current ones, and if not we will insert. + $current = $this->parent->{$this->relatedPivotKey} ?: []; + + // See issue #256. + if ($current instanceof Collection) { + $current = $ids->modelKeys(); + } + + $records = $this->formatSyncList($ids); + + $current = Arr::wrap($current); + + $detach = array_diff($current, array_keys($records)); + + // We need to make sure we pass a clean array, so that it is not interpreted + // as an associative array. + $detach = array_values($detach); + + // Next, we will take the differences of the currents and given IDs and detach + // all of the entities that exist in the "current" array but are not in the + // the array of the IDs given to the method which will complete the sync. + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = (array) array_map(function ($v) { + return is_numeric($v) ? (int) $v : (string) $v; + }, $detach); + } + + // Now we are finally ready to attach the new records. Note that we'll disable + // touching until after the entire operation is complete so we don't fire a + // ton of touch operations until we are totally done syncing the records. + $changes = array_merge( + $changes, + $this->attachNew($records, $current, false), + ); + + if (count($changes['attached']) || count($changes['updated'])) { + $this->touchIfTouching(); + } + + return $changes; + } + + /** @inheritdoc */ + public function updateExistingPivot($id, array $attributes, $touch = true) + { + // Do nothing, we have no pivot table. + } + + /** @inheritdoc */ public function attach($id, array $attributes = [], $touch = true) { if ($id instanceof Model) { @@ -137,53 +249,94 @@ public function detach($ids = [], $touch = true) return count($ids); } + /** @inheritdoc */ + protected function buildDictionary(Collection $results) + { + $foreign = $this->foreignPivotKey; + + // First we will build a dictionary of child models keyed by the foreign key + // of the relation so that we will easily and quickly match them to their + // parents without having a possibly slow inner loops for every models. + $dictionary = []; + + foreach ($results as $result) { + foreach ($result->$foreign as $item) { + $dictionary[$item][] = $result; + } + } + + return $dictionary; + } + + /** @inheritdoc */ + public function newPivotQuery() + { + return $this->newRelatedQuery(); + } /** - * Get the foreign key "type" name. + * Create a new query builder for the related model. * - * @return string + * @return \Illuminate\Database\Query\Builder */ - public function getMorphType() + public function newRelatedQuery() { - return $this->morphType; + return $this->related->newQuery(); } /** - * Get the class name of the parent model. + * Get the fully qualified foreign key for the relation. * * @return string */ - public function getMorphClass() + public function getForeignKey() { - return $this->morphClass; + return $this->foreignPivotKey; } - /** - * Get the indicator for a reverse relationship. - * - * @return bool - */ - public function getInverse() + /** @inheritdoc */ + public function getQualifiedForeignPivotKeyName() + { + return $this->foreignPivotKey; + } + + /** @inheritdoc */ + public function getQualifiedRelatedPivotKeyName() { - return $this->inverse; + return $this->relatedPivotKey; } /** - * Set the where clause for the relation query. + * Format the sync list so that it is keyed by ID. (Legacy Support) + * The original function has been renamed to formatRecordsList since Laravel 5.3. * - * @return $this + * @deprecated + * + * @return array */ - protected function setWhere() + protected function formatSyncList(array $records) { - $foreign = $this->getForeignKey(); + $results = []; + foreach ($records as $id => $attributes) { + if (! is_array($attributes)) { + [$id, $attributes] = [$attributes, []]; + } - if ($this->getInverse()) { - $this->query->where($foreign, '=', $this->parent->getKey()); - } else { - $relatedModels = $this->parent->{$this->relatedPivotKey} ?? []; - $this->query->whereIn($this->relatedKey, $relatedModels); + $results[$id] = $attributes; } - return $this; + return $results; + } + + /** + * Get the name of the "where in" method for eager loading. + * + * @param string $key + * + * @return string + */ + protected function whereInMethod(Model $model, $key) + { + return 'whereIn'; } } diff --git a/tests/Models/Client.php b/tests/Models/Client.php index 6f8859f63..1b7f823b4 100644 --- a/tests/Models/Client.php +++ b/tests/Models/Client.php @@ -8,7 +8,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphOne; use MongoDB\Laravel\Eloquent\Model as Eloquent; -// use Illuminate\Database\Eloquent\Relations\MorphToMany; class Client extends Eloquent { diff --git a/tests/Models/Label.php b/tests/Models/Label.php index efe3abf26..a1f796d4c 100644 --- a/tests/Models/Label.php +++ b/tests/Models/Label.php @@ -5,8 +5,6 @@ namespace MongoDB\Laravel\Tests\Models; use MongoDB\Laravel\Eloquent\Model as Eloquent; -// use Illuminate\Database\Eloquent\Relations\MorphToMany; -// use Illuminate\Database\Eloquent\Relations\MorphByMany; /** * @property string $title diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 51ac8f902..405f36002 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -402,7 +402,7 @@ public function testMorph(): void public function testMorphToMany(): void { - // cerate user + // create user $user = User::updateOrCreate(['name' => 'John Doe']); // create client $client = Client::updateOrCreate(['name' => 'Jane Doe']); From d929456a69d5b7b78ebb410b550b10d31cf9ad1a Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Tue, 7 Nov 2023 14:20:54 +0330 Subject: [PATCH 03/74] WIP --- src/Eloquent/HybridRelations.php | 60 +++++++++++++++++--------------- tests/RelationsTest.php | 23 +++--------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 79b91aede..f7dff51d8 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -326,15 +326,18 @@ public function belongsToMany( } /** - * Define a many-to-many relationship. + * Define a morph-to-many relationship. + * + * @param string $related + * @param $name + * @param null $table + * @param null $foreignPivotKey + * @param null $relatedPivotKey + * @param null $parentKey + * @param null $relatedKey + * @param null $relation + * @param bool $inverse * - * @param string $related - * @param string $collection - * @param string $foreignKey - * @param string $otherKey - * @param string $parentKey - * @param string $relatedKey - * @param string $relation * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function morphToMany( @@ -348,9 +351,15 @@ public function morphToMany( $relation = null, $inverse = false ) { + // 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 ($relation === null) { + $relation = $this->guessBelongsToManyRelation(); + } // Check if it is a relation with an original model. - if (!is_subclass_of($related, \Mongodb\Laravel\Eloquent\Model::class)) { + if (!is_subclass_of($related, Model::class)) { return parent::MorphToMany( $related, $name, @@ -359,12 +368,11 @@ public function morphToMany( $relatedPivotKey, $parentKey, $relatedKey, + $relation, $inverse, ); } - $caller = $this->guessBelongsToManyRelation(); - $instance = new $related; $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; @@ -373,7 +381,7 @@ public function morphToMany( // Now we're ready to create a new query builder for the related model and // the relationship instances for this relation. This relation will set - // appropriate query constraints then entirely manage the hydrations. + // appropriate query constraints then entirely manage the hydration. if (!$table) { $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); @@ -391,7 +399,7 @@ public function morphToMany( $relatedPivotKey, $parentKey ?: $this->getKeyName(), $relatedKey ?: $instance->getKeyName(), - $caller, + $relation, $inverse, ); } @@ -399,15 +407,14 @@ public function morphToMany( /** * Define a polymorphic, inverse many-to-many relationship. * - * @param string $related - * @param string $name - * @param string|null $table - * @param string|null $foreignPivotKey - * @param string|null $relatedPivotKey - * @param string|null $parentKey - * @param string|null $relatedKey - * @param string|null $relation - * @param bool $inverse + * @param string $related + * @param string $name + * @param null $table + * @param null $foreignPivotKey + * @param null $relatedPivotKey + * @param null $parentKey + * @param null $relatedKey + * @param bool $inverse * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ @@ -421,15 +428,12 @@ public function morphedByMany( $relatedKey = null, $inverse = false ) { - $caller = $this->guessBelongsToManyRelation(); + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; - // $instance = new $related; // 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'; - - $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; + $relatedPivotKey = $relatedPivotKey ?: $name.'_id'; return $this->morphToMany( $related, @@ -439,7 +443,7 @@ public function morphedByMany( $relatedPivotKey, $parentKey, $relatedKey, - null, + $relatedKey, true, ); } diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index c305238a3..633d300f0 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -448,20 +448,14 @@ public function testMorph(): void $this->assertInstanceOf(Client::class, $photos[1]->hasImage); } + /** @group hans */ public function testMorphToMany(): void { + $user = User::query()->create(['name' => 'John Doe']); + $client = Client::query()->create(['name' => 'Hans Thomas']); + $label = Label::query()->create(['name' => 'My test label']); + $label2 = Label::query()->create(['name' => 'My test label 2']); - // create user - $user = User::updateOrCreate(['name' => 'John Doe']); - // create client - $client = Client::updateOrCreate(['name' => 'Jane Doe']); - // create label - $label = Label::updateOrCreate(['name' => 'My test label']); - $label2 = Label::updateOrCreate(['name' => 'My test label 2']); - - // check attach - - // attach label to models $user->labels()->attach($label); $client->labels()->attach($label); @@ -471,26 +465,19 @@ public function testMorphToMany(): void $this->assertEquals(1, $client->labels->count()); $this->assertEquals($label->id, $client->labels->first()->id); - // check if label is attached to client $this->assertEquals($label->id, $client->labels->first()->id); - // check if label is attached to user $this->assertEquals($label->id, $user->labels->first()->id); - // check if client is attached to label $this->assertEquals($client->id, $label->clients->first()->id); - // check if user is attached to label $this->assertEquals($user->id, $label->users->first()->id); - // check detaching $user->labels()->detach($label); $this->assertNotContains($label->_id, $user->fresh()->labels->pluck('_id')->toArray()); - // check if label still connected to client $this->assertEquals($label->id, $client->fresh()->labels->first()->id); - // check sync $user->labels()->sync([$label->_id, $label2->_id]); $this->assertCount(2, $user->fresh()->labels); } From dd8e70134ee3887daa4c2f0ea07325d9e534d761 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Tue, 7 Nov 2023 17:13:27 +0330 Subject: [PATCH 04/74] WIP --- src/Eloquent/HybridRelations.php | 20 +++++---------- src/Relations/MorphToMany.php | 44 ++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index f7dff51d8..92854d043 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -375,18 +375,18 @@ public function morphToMany( $instance = new $related; - $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; - - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; + $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; // labelled_id + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; // label_ids + if ($inverse) { + $relatedPivotKey = $this->getForeignKey() . 's'; // labelleds + } // Now we're ready to create a new query builder for the related model and // the relationship instances for this relation. This relation will set // appropriate query constraints then entirely manage the hydration. if (!$table) { $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); - $lastWord = array_pop($words); - $table = implode('', $words) . Str::plural($lastWord); } @@ -414,7 +414,6 @@ public function morphToMany( * @param null $relatedPivotKey * @param null $parentKey * @param null $relatedKey - * @param bool $inverse * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ @@ -426,15 +425,8 @@ public function morphedByMany( $relatedPivotKey = null, $parentKey = null, $relatedKey = null, - $inverse = false + $relation = null ) { - $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; - - // 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, diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 8d559076c..e1990b510 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -4,11 +4,11 @@ namespace MongoDB\Laravel\Relations; -use Arr; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphToMany as EloquentMorphToMany; +use Illuminate\Support\Arr; use function array_diff; use function array_keys; @@ -74,11 +74,15 @@ public function addConstraints() */ protected function setWhere() { - $foreign = $this->getForeignKey(); - if ($this->getInverse()) { - $this->query->where($foreign, '=', $this->parent->getKey()); + // query -> User + // parent -> Label + // getForeignKey -> labelled_id -in-> User + // relatedPivotKey -> label_ids + $this->query->where($this->relatedPivotKey, '=', $this->parent->{$this->parentKey}); } else { + // query -> Label + // parent -> User $relatedModels = $this->parent->{$this->relatedPivotKey} ?? []; $this->query->whereIn($this->relatedKey, $relatedModels); } @@ -182,16 +186,33 @@ public function attach($id, array $attributes = [], $touch = true) if ($id instanceof Model) { $model = $id; - $id = $model->getKey(); + $id = $this->parseId($model); // Attach the new parent id to the related model. - $model->push($this->table, [ - $this->foreignPivotKey => $this->parent->getKey(), - $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, - ], true); + if ($this->getInverse()) { + // related -> User + // parent -> Label + // table -> labelleds + $this->parent->push($this->table, [ + $this->foreignPivotKey => $model->{$this->relatedKey}, + $this->morphType => $model->getMorphClass(), + ], true); + + // Attach the new ids to the parent model. + $model->push($this->relatedPivotKey, $this->parseIds($this->parent), true); + } + else{ + $model->push($this->table, [ + $this->foreignPivotKey => $this->parent->getKey(), + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ], true); + + // Attach the new ids to the parent model. + $this->parent->push($this->relatedPivotKey, (array) $id, true); + } } else { if ($id instanceof Collection) { - $id = $id->modelKeys(); + $id = $this->parseIds($id); } $query = $this->newRelatedQuery(); @@ -205,9 +226,6 @@ public function attach($id, array $attributes = [], $touch = true) ], true); } - // Attach the new ids to the parent model. - $this->parent->push($this->relatedPivotKey, (array) $id, true); - if ($touch) { $this->touchIfTouching(); } From 17a3aca6bdcb4d99908b07087db61931ca927274 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Tue, 7 Nov 2023 17:14:03 +0330 Subject: [PATCH 05/74] Add tests for MorphToMany and MorphedByMany; --- tests/Models/Label.php | 4 ++-- tests/Models/User.php | 11 +++++------ tests/RelationsTest.php | 32 +++++++++++++++++--------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/tests/Models/Label.php b/tests/Models/Label.php index a1f796d4c..2afcd4e3b 100644 --- a/tests/Models/Label.php +++ b/tests/Models/Label.php @@ -24,7 +24,7 @@ class Label extends Eloquent ]; /** - * Get all of the posts that are assigned this tag. + * Get all the posts that are assigned this tag. */ public function users() { @@ -32,7 +32,7 @@ public function users() } /** - * Get all of the videos that are assigned this tag. + * Get all the videos that are assigned this tag. */ public function clients() { diff --git a/tests/Models/User.php b/tests/Models/User.php index b2d826a92..f2d2cf7cc 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -106,6 +106,11 @@ public function photos() return $this->morphMany(Photo::class, 'has_image'); } + public function labels() + { + return $this->morphToMany(Label::class, 'labelled'); + } + public function addresses() { return $this->embedsMany(Address::class); @@ -133,10 +138,4 @@ public function prunable(): Builder { return $this->where('age', '>', 18); } - - // labels - public function labels() - { - return $this->morphToMany(Label::class, 'labelled'); - } } diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 633d300f0..bda9c85c3 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -5,6 +5,7 @@ namespace MongoDB\Laravel\Tests; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\DB; use Mockery; use MongoDB\Laravel\Tests\Models\Address; use MongoDB\Laravel\Tests\Models\Book; @@ -448,38 +449,39 @@ public function testMorph(): void $this->assertInstanceOf(Client::class, $photos[1]->hasImage); } - /** @group hans */ public function testMorphToMany(): void { $user = User::query()->create(['name' => 'John Doe']); $client = Client::query()->create(['name' => 'Hans Thomas']); + $label = Label::query()->create(['name' => 'My test label']); - $label2 = Label::query()->create(['name' => 'My test label 2']); $user->labels()->attach($label); $client->labels()->attach($label); $this->assertEquals(1, $user->labels->count()); - $this->assertEquals($label->id, $user->labels->first()->id); + $this->assertContains($label->_id, $user->labels->pluck('_id')); $this->assertEquals(1, $client->labels->count()); - $this->assertEquals($label->id, $client->labels->first()->id); - - $this->assertEquals($label->id, $client->labels->first()->id); - - $this->assertEquals($label->id, $user->labels->first()->id); + $this->assertContains($label->_id, $user->labels->pluck('_id')); + } - $this->assertEquals($client->id, $label->clients->first()->id); + /** @group hans */ + public function testMorphedByMany(): void + { + $user = User::query()->create(['name' => 'John Doe']); + $client = Client::query()->create(['name' => 'Hans Thomas']); - $this->assertEquals($user->id, $label->users->first()->id); + $label = Label::query()->create(['name' => 'My test label']); - $user->labels()->detach($label); - $this->assertNotContains($label->_id, $user->fresh()->labels->pluck('_id')->toArray()); + $label->users()->attach($user); + $label->clients()->attach($client); - $this->assertEquals($label->id, $client->fresh()->labels->first()->id); + $this->assertEquals(1, $label->users->count()); + $this->assertContains($user->_id, $label->users->pluck('_id')); - $user->labels()->sync([$label->_id, $label2->_id]); - $this->assertCount(2, $user->fresh()->labels); + $this->assertEquals(1, $label->clients->count()); + $this->assertContains($client->_id, $label->clients->pluck('_id')); } public function testHasManyHas(): void From 4e7ad77d67374ad545cd36f950f59715148ab353 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Tue, 7 Nov 2023 17:22:52 +0330 Subject: [PATCH 06/74] Improve DX; --- src/Eloquent/HybridRelations.php | 11 ++++++++--- src/Relations/MorphToMany.php | 6 +++--- tests/RelationsTest.php | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 92854d043..8b33f1a75 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -377,9 +377,6 @@ public function morphToMany( $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; // labelled_id $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; // label_ids - if ($inverse) { - $relatedPivotKey = $this->getForeignKey() . 's'; // labelleds - } // Now we're ready to create a new query builder for the related model and // the relationship instances for this relation. This relation will set @@ -427,6 +424,14 @@ public function morphedByMany( $relatedKey = null, $relation = null ) { + $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; + + // 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, diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index e1990b510..b9b8865ac 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -79,7 +79,7 @@ protected function setWhere() // parent -> Label // getForeignKey -> labelled_id -in-> User // relatedPivotKey -> label_ids - $this->query->where($this->relatedPivotKey, '=', $this->parent->{$this->parentKey}); + $this->query->where($this->foreignPivotKey, '=', $this->parent->{$this->parentKey}); } else { // query -> Label // parent -> User @@ -194,12 +194,12 @@ public function attach($id, array $attributes = [], $touch = true) // parent -> Label // table -> labelleds $this->parent->push($this->table, [ - $this->foreignPivotKey => $model->{$this->relatedKey}, + $this->relatedPivotKey => $model->{$this->relatedKey}, $this->morphType => $model->getMorphClass(), ], true); // Attach the new ids to the parent model. - $model->push($this->relatedPivotKey, $this->parseIds($this->parent), true); + $model->push($this->foreignPivotKey, $this->parseIds($this->parent), true); } else{ $model->push($this->table, [ diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index bda9c85c3..71dd25ec2 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -466,7 +466,6 @@ public function testMorphToMany(): void $this->assertContains($label->_id, $user->labels->pluck('_id')); } - /** @group hans */ public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); From f8240e281e07a1a7f613d88f911861b013d59e61 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Tue, 7 Nov 2023 17:52:51 +0330 Subject: [PATCH 07/74] fix cs; --- src/Eloquent/HybridRelations.php | 63 +++++++++++++++++--------------- src/Relations/MorphToMany.php | 3 +- tests/Models/Client.php | 1 - 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 8b33f1a75..4da9ccc4c 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -17,11 +17,15 @@ use MongoDB\Laravel\Relations\MorphTo; use MongoDB\Laravel\Relations\MorphToMany; - +use function array_pop; use function debug_backtrace; +use function implode; use function is_subclass_of; +use function method_exists; +use function preg_split; use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const PREG_SPLIT_DELIM_CAPTURE; /** * Cross-database relationships between SQL and MongoDB. @@ -43,7 +47,7 @@ trait HybridRelations public function hasOne($related, $foreignKey = null, $localKey = null) { // Check if it is a relation with an original model. - if (!is_subclass_of($related, MongoDBModel::class)) { + if (! is_subclass_of($related, MongoDBModel::class)) { return parent::hasOne($related, $foreignKey, $localKey); } @@ -72,7 +76,7 @@ public function hasOne($related, $foreignKey = null, $localKey = null) public function morphOne($related, $name, $type = null, $id = null, $localKey = null) { // Check if it is a relation with an original model. - if (!is_subclass_of($related, MongoDBModel::class)) { + if (! is_subclass_of($related, MongoDBModel::class)) { return parent::morphOne($related, $name, $type, $id, $localKey); } @@ -99,7 +103,7 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey = public function hasMany($related, $foreignKey = null, $localKey = null) { // Check if it is a relation with an original model. - if (!is_subclass_of($related, MongoDBModel::class)) { + if (! is_subclass_of($related, MongoDBModel::class)) { return parent::hasMany($related, $foreignKey, $localKey); } @@ -128,7 +132,7 @@ public function hasMany($related, $foreignKey = null, $localKey = null) public function morphMany($related, $name, $type = null, $id = null, $localKey = null) { // Check if it is a relation with an original model. - if (!is_subclass_of($related, MongoDBModel::class)) { + if (! is_subclass_of($related, MongoDBModel::class)) { return parent::morphMany($related, $name, $type, $id, $localKey); } @@ -168,7 +172,7 @@ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relat } // Check if it is a relation with an original model. - if (!is_subclass_of($related, MongoDBModel::class)) { + if (! is_subclass_of($related, MongoDBModel::class)) { return parent::belongsTo($related, $foreignKey, $ownerKey, $relation); } @@ -280,7 +284,7 @@ public function belongsToMany( } // Check if it is a relation with an original model. - if (!is_subclass_of($related, MongoDBModel::class)) { + if (! is_subclass_of($related, MongoDBModel::class)) { return parent::belongsToMany( $related, $collection, @@ -328,15 +332,15 @@ public function belongsToMany( /** * Define a morph-to-many relationship. * - * @param string $related - * @param $name - * @param null $table - * @param null $foreignPivotKey - * @param null $relatedPivotKey - * @param null $parentKey - * @param null $relatedKey - * @param null $relation - * @param bool $inverse + * @param string $related + * @param string $name + * @param null $table + * @param null $foreignPivotKey + * @param null $relatedPivotKey + * @param null $parentKey + * @param null $relatedKey + * @param null $relation + * @param bool $inverse * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ @@ -349,7 +353,7 @@ public function morphToMany( $parentKey = null, $relatedKey = null, $relation = null, - $inverse = false + $inverse = false, ) { // 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 @@ -359,7 +363,7 @@ public function morphToMany( } // Check if it is a relation with an original model. - if (!is_subclass_of($related, Model::class)) { + if (! is_subclass_of($related, Model::class)) { return parent::MorphToMany( $related, $name, @@ -373,7 +377,7 @@ public function morphToMany( ); } - $instance = new $related; + $instance = new $related(); $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; // labelled_id $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; // label_ids @@ -381,7 +385,7 @@ public function morphToMany( // Now we're ready to create a new query builder for the related model and // the relationship instances for this relation. This relation will set // appropriate query constraints then entirely manage the hydration. - if (!$table) { + if (! $table) { $words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE); $lastWord = array_pop($words); $table = implode('', $words) . Str::plural($lastWord); @@ -404,13 +408,13 @@ public function morphToMany( /** * Define a polymorphic, inverse many-to-many relationship. * - * @param string $related - * @param string $name - * @param null $table - * @param null $foreignPivotKey - * @param null $relatedPivotKey - * @param null $parentKey - * @param null $relatedKey + * @param string $related + * @param string $name + * @param null $table + * @param null $foreignPivotKey + * @param null $relatedPivotKey + * @param null $parentKey + * @param null $relatedKey * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ @@ -422,15 +426,14 @@ public function morphedByMany( $relatedPivotKey = null, $parentKey = null, $relatedKey = null, - $relation = null + $relation = null, ) { $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; // 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'; - + $relatedPivotKey = $relatedPivotKey ?: $name . '_id'; return $this->morphToMany( $related, diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index b9b8865ac..dc4a6e65c 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -200,8 +200,7 @@ public function attach($id, array $attributes = [], $touch = true) // Attach the new ids to the parent model. $model->push($this->foreignPivotKey, $this->parseIds($this->parent), true); - } - else{ + } else { $model->push($this->table, [ $this->foreignPivotKey => $this->parent->getKey(), $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, diff --git a/tests/Models/Client.php b/tests/Models/Client.php index 1b7f823b4..cfbc73edc 100644 --- a/tests/Models/Client.php +++ b/tests/Models/Client.php @@ -30,7 +30,6 @@ public function addresses(): HasMany return $this->hasMany(Address::class, 'data.client_id', 'data.client_id'); } - // labels public function labels() { return $this->morphToMany(Label::class, 'labelled'); From fd0a9f9f37c40758573ebbb19c6c7ebdb8c7748d Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 20:47:18 +0330 Subject: [PATCH 08/74] Querying logic in inverse mode updated; --- src/Relations/MorphToMany.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index dc4a6e65c..d20afeb2c 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -79,7 +79,8 @@ protected function setWhere() // parent -> Label // getForeignKey -> labelled_id -in-> User // relatedPivotKey -> label_ids - $this->query->where($this->foreignPivotKey, '=', $this->parent->{$this->parentKey}); + $ids = array_filter((array)$this->parent->{$this->table}, fn ($id) => $id !== get_class($this->related)); + $this->query->whereIn($this->relatedKey, $ids); } else { // query -> Label // parent -> User From dbe93d04a85f448fb20fccbba1c33939d3a3a1ae Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 20:48:09 +0330 Subject: [PATCH 09/74] Attaching a collection or string id(s) fixed; --- src/Relations/MorphToMany.php | 47 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index d20afeb2c..ab5324dc2 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -84,6 +84,7 @@ protected function setWhere() } else { // query -> Label // parent -> User + // relatedPivotKey -> label_ids $relatedModels = $this->parent->{$this->relatedPivotKey} ?? []; $this->query->whereIn($this->relatedKey, $relatedModels); } @@ -203,7 +204,7 @@ public function attach($id, array $attributes = [], $touch = true) $model->push($this->foreignPivotKey, $this->parseIds($this->parent), true); } else { $model->push($this->table, [ - $this->foreignPivotKey => $this->parent->getKey(), + $this->foreignPivotKey => $this->parent->{$this->parentKey}, $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, ], true); @@ -215,15 +216,35 @@ public function attach($id, array $attributes = [], $touch = true) $id = $this->parseIds($id); } - $query = $this->newRelatedQuery(); + if ($this->getInverse()) { + $query = $this->newRelatedQuery(); - $query->whereIn($this->related->getKeyName(), (array) $id); + $query->whereIn($this->relatedKey, (array) $id); - // Attach the new parent id to the related model. - $query->push($this->table, [ - $this->foreignPivotKey => $this->parent->getKey(), - $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, - ], true); + // Attach the new parent id to the related model. + $query->push($this->foreignPivotKey, $this->parent->{$this->parentKey}); + + // Attach the new ids to the parent model. + foreach ($id as $item) { + $this->parent->push($this->table, [ + $this->relatedPivotKey => $item, + $this->morphType => $this->related instanceof Model ? $this->related->getMorphClass() : null, + ], true); + } + }else{ + $query = $this->newRelatedQuery(); + + $query->whereIn($this->relatedKey, (array) $id); + + // Attach the new parent id to the related model. + $query->push($this->table, [ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ], true); + + // Attach the new ids to the parent model. + $this->parent->push($this->relatedPivotKey, (array) $id, true); + } } if ($touch) { @@ -302,6 +323,16 @@ public function newRelatedQuery() return $this->related->newQuery(); } + /** + * Create a new query builder for the parent model. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newParentQuery() + { + return $this->parent->newQuery(); + } + /** * Get the fully qualified foreign key for the relation. * From dc22fc95e20b6aefc61c19e37bff9ea12d544e48 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 20:48:47 +0330 Subject: [PATCH 10/74] Add tests for attaching a collection; --- tests/RelationsTest.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index b52e82b2e..74e57b431 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -5,6 +5,7 @@ namespace MongoDB\Laravel\Tests; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\DB; use Mockery; use MongoDB\BSON\ObjectId; use MongoDB\Laravel\Tests\Models\Address; @@ -510,6 +511,20 @@ public function testMorphToMany(): void $this->assertContains($label->_id, $user->labels->pluck('_id')); } + public function testMorphToManyAttachEloquentCollection(): void + { + $client = Client::query()->create(['name' => 'Young Gerald']); + + $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); + $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); + + $client->labels()->attach(new Collection([$label1, $label2])); + + $this->assertEquals(2, $client->labels->count()); + $this->assertContains($label1->_id, $client->labels->pluck('_id')); + $this->assertContains($label2->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); @@ -527,6 +542,25 @@ public function testMorphedByMany(): void $this->assertContains($client->_id, $label->clients->pluck('_id')); } + public function testMorphedByManyAttachEloquentCollection(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $extra = Client::query()->create(['name' => 'one more client']); + + $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + + $label->clients()->attach(new Collection([$client1, $client2])); + + $this->assertEquals(2, $label->clients->count()); + $this->assertContains($client1->_id, $label->clients->pluck('_id')); + $this->assertContains($client2->_id, $label->clients->pluck('_id')); + + $client1->refresh(); + $this->assertEquals(1, $client1->labels->count()); + } + + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From a0cb4805a3cd83350a540a4a1931111d6eee46d7 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 20:54:08 +0330 Subject: [PATCH 11/74] Add tests for attaching multiple string ids; --- tests/RelationsTest.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 74e57b431..620cf50ea 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -525,6 +525,20 @@ public function testMorphToManyAttachEloquentCollection(): void $this->assertContains($label2->_id, $client->labels->pluck('_id')); } + public function testMorphToManyAttachMultipleIds(): void + { + $client = Client::query()->create(['name' => 'Young Gerald']); + + $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); + $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); + + $client->labels()->attach([$label1->_id, $label2->_id]); + + $this->assertEquals(2, $client->labels->count()); + $this->assertContains($label1->_id, $client->labels->pluck('_id')); + $this->assertContains($label2->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); @@ -560,6 +574,23 @@ public function testMorphedByManyAttachEloquentCollection(): void $this->assertEquals(1, $client1->labels->count()); } + public function testMorphedByManyAttachMultipleIds(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $extra = Client::query()->create(['name' => 'one more client']); + + $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + + $label->clients()->attach([$client1->_id, $client2->_id]); + + $this->assertEquals(2, $label->clients->count()); + $this->assertContains($client1->_id, $label->clients->pluck('_id')); + $this->assertContains($client2->_id, $label->clients->pluck('_id')); + + $client1->refresh(); + $this->assertEquals(1, $client1->labels->count()); + } public function testHasManyHas(): void { From 9f888ab640298968179578c0b109c8026ade568e Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 21:12:06 +0330 Subject: [PATCH 12/74] WIP --- src/Relations/MorphToMany.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index ab5324dc2..c85c2aef6 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -216,9 +216,9 @@ public function attach($id, array $attributes = [], $touch = true) $id = $this->parseIds($id); } - if ($this->getInverse()) { - $query = $this->newRelatedQuery(); + $query = $this->newRelatedQuery(); + if ($this->getInverse()) { $query->whereIn($this->relatedKey, (array) $id); // Attach the new parent id to the related model. @@ -232,8 +232,6 @@ public function attach($id, array $attributes = [], $touch = true) ], true); } }else{ - $query = $this->newRelatedQuery(); - $query->whereIn($this->relatedKey, (array) $id); // Attach the new parent id to the related model. @@ -262,7 +260,7 @@ public function detach($ids = [], $touch = true) $query = $this->newRelatedQuery(); // If associated IDs were passed to the method we will only delete those - // associations, otherwise all of the association ties will be broken. + // associations, otherwise all the association ties will be broken. // We'll return the numbers of affected rows when we do the deletes. $ids = (array) $ids; From 3d4abab77a3080e2071ca7468746d9f73714cf61 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 21:12:50 +0330 Subject: [PATCH 13/74] Fix CS; --- src/Relations/MorphToMany.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index c85c2aef6..ba736ff48 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -11,11 +11,13 @@ use Illuminate\Support\Arr; use function array_diff; +use function array_filter; use function array_keys; use function array_map; use function array_merge; use function array_values; use function count; +use function get_class; use function is_array; use function is_numeric; @@ -79,7 +81,7 @@ protected function setWhere() // parent -> Label // getForeignKey -> labelled_id -in-> User // relatedPivotKey -> label_ids - $ids = array_filter((array)$this->parent->{$this->table}, fn ($id) => $id !== get_class($this->related)); + $ids = array_filter((array) $this->parent->{$this->table}, fn ($id) => $id !== get_class($this->related)); $this->query->whereIn($this->relatedKey, $ids); } else { // query -> Label @@ -231,7 +233,7 @@ public function attach($id, array $attributes = [], $touch = true) $this->morphType => $this->related instanceof Model ? $this->related->getMorphClass() : null, ], true); } - }else{ + } else { $query->whereIn($this->relatedKey, (array) $id); // Attach the new parent id to the related model. From 24119d7969e0a5681fa8b041d238dbf819a585dd Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 22:09:44 +0330 Subject: [PATCH 14/74] Detaching fixed; --- src/Relations/MorphToMany.php | 50 +++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index ba736ff48..92bd85488 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -87,8 +87,7 @@ protected function setWhere() // query -> Label // parent -> User // relatedPivotKey -> label_ids - $relatedModels = $this->parent->{$this->relatedPivotKey} ?? []; - $this->query->whereIn($this->relatedKey, $relatedModels); + $this->query->whereIn($this->relatedKey, (array) $this->parent->{$this->relatedPivotKey}); } return $this; @@ -256,7 +255,7 @@ public function attach($id, array $attributes = [], $touch = true) public function detach($ids = [], $touch = true) { if ($ids instanceof Model) { - $ids = (array) $ids->getKey(); + $ids = $this->parseIds($ids); } $query = $this->newRelatedQuery(); @@ -267,19 +266,42 @@ public function detach($ids = [], $touch = true) $ids = (array) $ids; // Detach all ids from the parent model. - $this->parent->pull($this->relatedPivotKey, $ids); + if ($this->getInverse()) { + // parent -> label + // related -> Client + // relatedPivotKey -> labelled_id + // foreignPivotKey -> label_ids + foreach ($ids as $item) { + $this->parent->pull($this->table, [ + $this->relatedPivotKey => $item, + $this->morphType => $this->related->getMorphClass(), + ]); + } - // Prepare the query to select all related objects. - if (count($ids) > 0) { - $query->whereIn($this->related->getKeyName(), $ids); - } + // Prepare the query to select all related objects. + if (count($ids) > 0) { + $query->whereIn($this->relatedKey, $ids); + } + + // Remove the relation to the parent. + $query->pull($this->foreignPivotKey, $this->parent->{$this->parentKey}); + } else { + // parent -> Client + // related -> label + // relatedPivotKey -> label_ids + $this->parent->pull($this->relatedPivotKey, $ids); - // Remove the relation to the parent. - // $query->pull($this->foreignPivotKey, $this->foreignPivotKey, $this->parent->getKey()); - $query->pull($this->table, [ - $this->foreignPivotKey => $this->parent->getKey(), - $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, - ]); + // Prepare the query to select all related objects. + if (count($ids) > 0) { + $query->whereIn($this->relatedKey, $ids); + } + + // Remove the relation to the parent. + $query->pull($this->table, [ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->morphType => $this->parent->getMorphClass(), + ]); + } if ($touch) { $this->touchIfTouching(); From 1833c5afa886ee19741d20d09f819c36397677e4 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 8 Nov 2023 22:09:58 +0330 Subject: [PATCH 15/74] Add tests for detaching; --- tests/RelationsTest.php | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 620cf50ea..f29aab745 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -5,7 +5,6 @@ namespace MongoDB\Laravel\Tests; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\DB; use Mockery; use MongoDB\BSON\ObjectId; use MongoDB\Laravel\Tests\Models\Address; @@ -539,6 +538,24 @@ public function testMorphToManyAttachMultipleIds(): void $this->assertContains($label2->_id, $client->labels->pluck('_id')); } + public function testMorphToManyDetaching(): void + { + $client = Client::query()->create(['name' => 'Young Gerald']); + + $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); + $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); + + $client->labels()->attach([$label1->_id, $label2->_id]); + + $this->assertEquals(2, $client->labels->count()); + + $client->labels()->detach($label1); + $check = $client->withoutRelations(); + + $this->assertEquals(1, $check->labels->count()); + $this->assertContains($label2->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); @@ -592,6 +609,25 @@ public function testMorphedByManyAttachMultipleIds(): void $this->assertEquals(1, $client1->labels->count()); } + public function testMorphedByManyDetaching(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $extra = Client::query()->create(['name' => 'one more client']); + + $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + + $label->clients()->attach([$client1->_id, $client2->_id]); + + $this->assertEquals(2, $label->clients->count()); + + $label->clients()->detach($client1->_id); + $check = $label->withoutRelations(); + + $this->assertEquals(1, $check->clients->count()); + $this->assertContains($client2->_id, $check->clients->pluck('_id')); + } + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From ad1c6ae2c523fbd08022d828d8f40db5ece12486 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 11:05:48 +0330 Subject: [PATCH 16/74] Add tests for detaching multiple ids; --- tests/RelationsTest.php | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index f29aab745..49161e06a 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -556,6 +556,25 @@ public function testMorphToManyDetaching(): void $this->assertContains($label2->_id, $client->labels->pluck('_id')); } + public function testMorphToManyDetachingMultipleIds(): void + { + $client = Client::query()->create(['name' => 'Young Gerald']); + + $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); + $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); + $label3 = Label::query()->create(['name' => 'If it was easy, everyone would do it']); + + $client->labels()->attach([$label1->_id, $label2->_id, $label3->_id]); + + $this->assertEquals(3, $client->labels->count()); + + $client->labels()->detach([$label1->_id, $label2->_id]); + $check = $client->withoutRelations(); + + $this->assertEquals(1, $check->labels->count()); + $this->assertContains($label3->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); @@ -628,6 +647,25 @@ public function testMorphedByManyDetaching(): void $this->assertContains($client2->_id, $check->clients->pluck('_id')); } + public function testMorphedByManyDetachingMultipleIds(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + + $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + + $label->clients()->attach([$client1->_id, $client2->_id, $client3->_id]); + + $this->assertEquals(3, $label->clients->count()); + + $label->clients()->detach([$client1->_id, $client2->_id]); + $check = $label->withoutRelations(); + + $this->assertEquals(1, $check->clients->count()); + $this->assertContains($client3->_id, $check->clients->pluck('_id')); + } + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From 26f7ad276eb8f52a223090ec2ceec180a86ee1e7 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 11:40:02 +0330 Subject: [PATCH 17/74] Update MorphToMany.php --- src/Relations/MorphToMany.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 92bd85488..5e95eea26 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -128,17 +128,21 @@ public function sync($ids, $detaching = true) ]; if ($ids instanceof Collection) { - $ids = $ids->modelKeys(); + $ids = $this->parseIds($ids); + } elseif ($ids instanceof Model) { + $ids = $this->parseIds($ids); } // First we need to attach any of the associated models that are not currently // in this joining table. We'll spin through the given IDs, checking to see // if they exist in the array of current ones, and if not we will insert. + // parent -> User, Client + // relatedPivotKey -> label_ids $current = $this->parent->{$this->relatedPivotKey} ?: []; // See issue #256. if ($current instanceof Collection) { - $current = $ids->modelKeys(); + $current = $this->parseIds($current); } $records = $this->formatSyncList($ids); @@ -157,7 +161,7 @@ public function sync($ids, $detaching = true) if ($detaching && count($detach) > 0) { $this->detach($detach); - $changes['detached'] = (array) array_map(function ($v) { + $changes['detached'] = array_map(function ($v) { return is_numeric($v) ? (int) $v : (string) $v; }, $detach); } From 8921127faff37b46bed5ef4488845dc09217318c Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 11:40:25 +0330 Subject: [PATCH 18/74] Add test for syncing a mode; --- tests/RelationsTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 49161e06a..a13eaa553 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -575,6 +575,27 @@ public function testMorphToManyDetachingMultipleIds(): void $this->assertContains($label3->_id, $client->labels->pluck('_id')); } + public function testMorphToManySyncing(): void + { + $user = User::query()->create(['name' => 'John Doe']); + $client = Client::query()->create(['name' => 'Hans Thomas']); + + $label = Label::query()->create(['name' => 'My test label']); + $label2 = Label::query()->create(['name' => 'My test label 2']); + + $user->labels()->sync($label); + $client->labels()->sync($label); + $client->labels()->sync($label2, false); + + $this->assertEquals(1, $user->labels->count()); + $this->assertContains($label->_id, $user->labels->pluck('_id')); + $this->assertNotContains($label2->_id, $user->labels->pluck('_id')); + + $this->assertEquals(2, $client->labels->count()); + $this->assertContains($label->_id, $client->labels->pluck('_id')); + $this->assertContains($label2->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); From dbaab5f962a5bfc1dfce0cab19f7fee3a097a65a Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 11:40:49 +0330 Subject: [PATCH 19/74] Add test for syncing a collection of models; --- tests/RelationsTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index a13eaa553..6344863a5 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -596,6 +596,20 @@ public function testMorphToManySyncing(): void $this->assertContains($label2->_id, $client->labels->pluck('_id')); } + public function testMorphToManySyncingEloquentCollection(): void + { + $client = Client::query()->create(['name' => 'Hans Thomas']); + + $label = Label::query()->create(['name' => 'My test label']); + $label2 = Label::query()->create(['name' => 'My test label 2']); + + $client->labels()->sync(new Collection([$label, $label2])); + + $this->assertEquals(2, $client->labels->count()); + $this->assertContains($label->_id, $client->labels->pluck('_id')); + $this->assertContains($label2->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); From a2c6407d6fd871df72481e334e0084d9d48a015f Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 11:42:25 +0330 Subject: [PATCH 20/74] Add test for syncing multiple ids; --- tests/RelationsTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 6344863a5..bd4ccdb15 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -610,6 +610,20 @@ public function testMorphToManySyncingEloquentCollection(): void $this->assertContains($label2->_id, $client->labels->pluck('_id')); } + public function testMorphToManySyncingMultipleIds(): void + { + $client = Client::query()->create(['name' => 'Hans Thomas']); + + $label = Label::query()->create(['name' => 'My test label']); + $label2 = Label::query()->create(['name' => 'My test label 2']); + + $client->labels()->sync([$label->_id, $label2->_id]); + + $this->assertEquals(2, $client->labels->count()); + $this->assertContains($label->_id, $client->labels->pluck('_id')); + $this->assertContains($label2->_id, $client->labels->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); From 284b8c3d7507160797759ebabf7adee75537486f Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 14:46:27 +0330 Subject: [PATCH 21/74] WIP --- src/Relations/MorphToMany.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 5e95eea26..f23f42fde 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -220,12 +220,12 @@ public function attach($id, array $attributes = [], $touch = true) if ($id instanceof Collection) { $id = $this->parseIds($id); } + $id = (array) $id; $query = $this->newRelatedQuery(); + $query->whereIn($this->relatedKey, $id); if ($this->getInverse()) { - $query->whereIn($this->relatedKey, (array) $id); - // Attach the new parent id to the related model. $query->push($this->foreignPivotKey, $this->parent->{$this->parentKey}); @@ -237,8 +237,6 @@ public function attach($id, array $attributes = [], $touch = true) ], true); } } else { - $query->whereIn($this->relatedKey, (array) $id); - // Attach the new parent id to the related model. $query->push($this->table, [ $this->foreignPivotKey => $this->parent->{$this->parentKey}, @@ -246,7 +244,7 @@ public function attach($id, array $attributes = [], $touch = true) ], true); // Attach the new ids to the parent model. - $this->parent->push($this->relatedPivotKey, (array) $id, true); + $this->parent->push($this->relatedPivotKey, $id, true); } } From 92040ccf15fefe67889c54dc48a8bbad6440e172 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 14:47:07 +0330 Subject: [PATCH 22/74] Fix syncing in inverse mode --- src/Relations/MorphToMany.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index f23f42fde..418384476 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -136,9 +136,17 @@ public function sync($ids, $detaching = true) // First we need to attach any of the associated models that are not currently // in this joining table. We'll spin through the given IDs, checking to see // if they exist in the array of current ones, and if not we will insert. - // parent -> User, Client - // relatedPivotKey -> label_ids - $current = $this->parent->{$this->relatedPivotKey} ?: []; + if ($this->getInverse()) { + // parent -> Label + // relatedPivotKey -> labelled_id + // table -> labelleds + $current = $this->parent->{$this->table} ?: []; + $current = array_filter($current, fn ($item) => $item !== get_class($this->related)); + }else{ + // parent -> User, Client + // relatedPivotKey -> label_ids + $current = $this->parent->{$this->relatedPivotKey} ?: []; + } // See issue #256. if ($current instanceof Collection) { From 79e5618b3fbdb3562542a04b0bf8d6fc9a963ea4 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 14:47:24 +0330 Subject: [PATCH 23/74] Add test for syncing a model; --- tests/RelationsTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index bd4ccdb15..d24e94080 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -715,6 +715,25 @@ public function testMorphedByManyDetachingMultipleIds(): void $this->assertContains($client3->_id, $check->clients->pluck('_id')); } + public function testMorphedByManySyncing(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + + $label = Label::query()->create(['name' => 'My test label']); + + $label->clients()->sync($client1); + $label->clients()->sync($client2, false); + $label->clients()->sync($client3, false); + + $this->assertEquals(3, $label->clients->count()); + $this->assertContains($client1->_id, $label->clients->pluck('_id')); + $this->assertContains($client2->_id, $label->clients->pluck('_id')); + $this->assertContains($client3->_id, $label->clients->pluck('_id')); + } + + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From 5ea4aef23f05a05e3e26a6e6f2522c5adc51917a Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 14:49:00 +0330 Subject: [PATCH 24/74] Add test for syncing a collection of models; --- tests/RelationsTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index d24e94080..bc294ef08 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -733,6 +733,21 @@ public function testMorphedByManySyncing(): void $this->assertContains($client3->_id, $label->clients->pluck('_id')); } + public function testMorphedByManySyncingEloquentCollection(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + + $label = Label::query()->create(['name' => 'My test label']); + + $label->clients()->sync(new Collection([$client1,$client2,$client3])); + + $this->assertEquals(3, $label->clients->count()); + $this->assertContains($client1->_id, $label->clients->pluck('_id')); + $this->assertContains($client2->_id, $label->clients->pluck('_id')); + $this->assertContains($client3->_id, $label->clients->pluck('_id')); + } public function testHasManyHas(): void { From 7008008543ebfc2e9b780d3c872f7e732dfd9e33 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 14:52:53 +0330 Subject: [PATCH 25/74] Add test for syncing multiple id; --- tests/RelationsTest.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index bc294ef08..41ce21bb0 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -741,12 +741,30 @@ public function testMorphedByManySyncingEloquentCollection(): void $label = Label::query()->create(['name' => 'My test label']); - $label->clients()->sync(new Collection([$client1,$client2,$client3])); + $label->clients()->sync(new Collection([$client1,$client2])); - $this->assertEquals(3, $label->clients->count()); + $this->assertEquals(2, $label->clients->count()); $this->assertContains($client1->_id, $label->clients->pluck('_id')); $this->assertContains($client2->_id, $label->clients->pluck('_id')); - $this->assertContains($client3->_id, $label->clients->pluck('_id')); + + $this->assertNotContains($client3->_id, $label->clients->pluck('_id')); + } + + public function testMorphedByManySyncingMultipleIds(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + + $label = Label::query()->create(['name' => 'My test label']); + + $label->clients()->sync([$client1->_id,$client2->_id]); + + $this->assertEquals(2, $label->clients->count()); + $this->assertContains($client1->_id, $label->clients->pluck('_id')); + $this->assertContains($client2->_id, $label->clients->pluck('_id')); + + $this->assertNotContains($client3->_id, $label->clients->pluck('_id')); } public function testHasManyHas(): void From f655f72e172db59601858fb07ff39224635da13e Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 15:02:26 +0330 Subject: [PATCH 26/74] Fix CS; --- src/Relations/MorphToMany.php | 3 ++- tests/RelationsTest.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 418384476..9334160b6 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -142,7 +142,7 @@ public function sync($ids, $detaching = true) // table -> labelleds $current = $this->parent->{$this->table} ?: []; $current = array_filter($current, fn ($item) => $item !== get_class($this->related)); - }else{ + } else { // parent -> User, Client // relatedPivotKey -> label_ids $current = $this->parent->{$this->relatedPivotKey} ?: []; @@ -228,6 +228,7 @@ public function attach($id, array $attributes = [], $touch = true) if ($id instanceof Collection) { $id = $this->parseIds($id); } + $id = (array) $id; $query = $this->newRelatedQuery(); diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 41ce21bb0..f28440a6b 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -741,7 +741,7 @@ public function testMorphedByManySyncingEloquentCollection(): void $label = Label::query()->create(['name' => 'My test label']); - $label->clients()->sync(new Collection([$client1,$client2])); + $label->clients()->sync(new Collection([$client1, $client2])); $this->assertEquals(2, $label->clients->count()); $this->assertContains($client1->_id, $label->clients->pluck('_id')); @@ -758,7 +758,7 @@ public function testMorphedByManySyncingMultipleIds(): void $label = Label::query()->create(['name' => 'My test label']); - $label->clients()->sync([$client1->_id,$client2->_id]); + $label->clients()->sync([$client1->_id, $client2->_id]); $this->assertEquals(2, $label->clients->count()); $this->assertContains($client1->_id, $label->clients->pluck('_id')); From 4932b3bad884caa411f9ed55e9d30841bf199773 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 17:40:43 +0330 Subject: [PATCH 27/74] Fix eager loading relation and refreshing the model before lazy loading; --- src/Relations/MorphToMany.php | 66 +++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 9334160b6..7e12b815b 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -12,14 +12,18 @@ use function array_diff; use function array_filter; +use function array_key_exists; use function array_keys; use function array_map; use function array_merge; +use function array_reduce; use function array_values; use function count; use function get_class; use function is_array; use function is_numeric; +use function is_string; +use function str_contains; class MorphToMany extends EloquentMorphToMany { @@ -69,6 +73,32 @@ public function addConstraints() } } + /** @inheritdoc */ + public function addEagerConstraints(array $models) + { + if ($this->getInverse()) { + // query -> Client + // related -> Client + // relatedPivotKey -> labelled_id + // table -> labelleds + $ids = $this->getKeys($models, $this->table); + $ids = array_reduce($ids[0] ?? [], function ($carry, $item) { + if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { + $carry[] = $item[$this->relatedPivotKey]; + } elseif (is_string($item) && ! str_contains($item, '\\')) { + $carry[] = $item; + } + + return $carry; + }, []); + $this->query->whereIn($this->relatedKey, $ids); + } else { + parent::addEagerConstraints($models); + + $this->query->where($this->qualifyPivotColumn($this->morphType), $this->morphClass); + } + } + /** * Set the where clause for the relation query. * @@ -81,7 +111,16 @@ protected function setWhere() // parent -> Label // getForeignKey -> labelled_id -in-> User // relatedPivotKey -> label_ids - $ids = array_filter((array) $this->parent->{$this->table}, fn ($id) => $id !== get_class($this->related)); + $ids = array_reduce((array) $this->parent->{$this->table}, function ($carry, $item) { + if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { + $carry[] = $item[$this->relatedPivotKey]; + } elseif (is_string($item) && ! str_contains($item, '\\')) { + $carry[] = $item; + } + + return $carry; + }, []); + $this->query->whereIn($this->relatedKey, $ids); } else { // query -> Label @@ -332,8 +371,15 @@ protected function buildDictionary(Collection $results) $dictionary = []; foreach ($results as $result) { - foreach ($result->$foreign as $item) { - $dictionary[$item][] = $result; + if ($this->getInverse()) { + foreach ($result->$foreign as $item) { + $dictionary[$item][] = $result; + } + } else { + $items = array_reduce($result->{$this->table} ?? [], fn ($carry, $item) => array_merge($carry, [$item[$foreign]]), []); + foreach ($items as $item) { + $dictionary[$item][] = $result; + } } } @@ -356,16 +402,6 @@ public function newRelatedQuery() return $this->related->newQuery(); } - /** - * Create a new query builder for the parent model. - * - * @return \Illuminate\Database\Query\Builder - */ - public function newParentQuery() - { - return $this->parent->newQuery(); - } - /** * Get the fully qualified foreign key for the relation. * @@ -379,7 +415,9 @@ public function getForeignKey() /** @inheritdoc */ public function getQualifiedForeignPivotKeyName() { - return $this->foreignPivotKey; + return str_contains($this->foreignPivotKey, '.') + ? $this->foreignPivotKey + : $this->table . '.' . $this->foreignPivotKey; } /** @inheritdoc */ From c1df5019964ced99a3d5ad503016547d223ea57c Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 9 Nov 2023 17:41:04 +0330 Subject: [PATCH 28/74] Use load and refresh in tests; --- tests/RelationsTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index f28440a6b..fab4115b5 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -569,9 +569,9 @@ public function testMorphToManyDetachingMultipleIds(): void $this->assertEquals(3, $client->labels->count()); $client->labels()->detach([$label1->_id, $label2->_id]); - $check = $client->withoutRelations(); + $client->refresh(); - $this->assertEquals(1, $check->labels->count()); + $this->assertEquals(1, $client->labels->count()); $this->assertContains($label3->_id, $client->labels->pluck('_id')); } @@ -628,6 +628,7 @@ public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); $client = Client::query()->create(['name' => 'Hans Thomas']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); $label = Label::query()->create(['name' => 'My test label']); @@ -709,10 +710,10 @@ public function testMorphedByManyDetachingMultipleIds(): void $this->assertEquals(3, $label->clients->count()); $label->clients()->detach([$client1->_id, $client2->_id]); - $check = $label->withoutRelations(); + $label->load('clients'); - $this->assertEquals(1, $check->clients->count()); - $this->assertContains($client3->_id, $check->clients->pluck('_id')); + $this->assertEquals(1, $label->clients->count()); + $this->assertContains($client3->_id, $label->clients->pluck('_id')); } public function testMorphedByManySyncing(): void From c8ee4ced6807638df71777a6058dff73c119de2b Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 15:16:01 +0330 Subject: [PATCH 29/74] New labelsWithCustomKeys relation defined with custom keys; --- tests/Models/Client.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Models/Client.php b/tests/Models/Client.php index cfbc73edc..48e4cb571 100644 --- a/tests/Models/Client.php +++ b/tests/Models/Client.php @@ -34,4 +34,16 @@ public function labels() { return $this->morphToMany(Label::class, 'labelled'); } + public function labelsWithCustomKeys() + { + return $this->morphToMany( + Label::class, + 'clabelled', + 'clabelleds', + 'cclabelled_id', + 'clabel_ids', + 'cclient_id', + 'clabel_id', + ); + } } From b4fca9de4864e0239eeb0c4cca65a28ffadee3cc Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 15:16:25 +0330 Subject: [PATCH 30/74] Add tests for new relation with custom keys; --- tests/RelationsTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index fab4115b5..335ca7b72 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -624,6 +624,27 @@ public function testMorphToManySyncingMultipleIds(): void $this->assertContains($label2->_id, $client->labels->pluck('_id')); } + public function testMorphToManySyncingWithCustomKeys(): void + { + $client = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Hans Thomas']); + + $label = Label::query()->create(['clabel_id'=>(string)(new ObjectId()), 'name' => 'My test label']); + $label2 = Label::query()->create(['clabel_id'=>(string)(new ObjectId()), 'name' => 'My test label 2']); + + $client->labelsWithCustomKeys()->sync([$label->clabel_id, $label2->clabel_id]); + + $this->assertEquals(2, $client->labelsWithCustomKeys->count()); + $this->assertContains($label->_id, $client->labelsWithCustomKeys->pluck('_id')); + $this->assertContains($label2->_id, $client->labelsWithCustomKeys->pluck('_id')); + + $client->labelsWithCustomKeys()->sync($label); + $client->load('labelsWithCustomKeys'); + + $this->assertEquals(1, $client->labelsWithCustomKeys->count()); + $this->assertContains($label->_id, $client->labelsWithCustomKeys->pluck('_id')); + $this->assertNotContains($label2->_id, $client->labelsWithCustomKeys->pluck('_id')); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); From db782d96647c8e03e238b96935df2f2af78c3c1f Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 16:40:41 +0330 Subject: [PATCH 31/74] New clientsWithCustomKeys defined with custom keys; --- tests/Models/Label.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/Models/Label.php b/tests/Models/Label.php index 2afcd4e3b..8c634ab28 100644 --- a/tests/Models/Label.php +++ b/tests/Models/Label.php @@ -31,11 +31,20 @@ public function users() return $this->morphedByMany(User::class, 'labelled'); } - /** - * Get all the videos that are assigned this tag. - */ public function clients() { return $this->morphedByMany(Client::class, 'labelled'); } + public function clientsWithCustomKeys() + { + return $this->morphedByMany( + Client::class, + 'clabelled', + 'clabelleds', + 'clabel_ids', + 'cclabelled_id', + 'clabel_id', + 'cclient_id', + ); + } } From 85c01597b2fc6d33e3a414512e6944c7e65521bf Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 16:40:52 +0330 Subject: [PATCH 32/74] Add test for clientsWithCustomKeys relation; --- tests/RelationsTest.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 335ca7b72..9d04bc5fb 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -789,6 +789,32 @@ public function testMorphedByManySyncingMultipleIds(): void $this->assertNotContains($client3->_id, $label->clients->pluck('_id')); } + public function testMorphedByManySyncingWithCustomKeys(): void + { + $client1 = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Young Gerald']); + $client2 = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Hans Thomas']); + $client3 = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Austin Richard Post']); + + $label = Label::query()->create(['clabel_id'=>(string)(new ObjectId()), 'name' => 'My test label']); + + $label->clientsWithCustomKeys()->sync([$client1->cclient_id, $client2->cclient_id]); + + $this->assertEquals(2, $label->clientsWithCustomKeys->count()); + $this->assertContains($client1->_id, $label->clientsWithCustomKeys->pluck('_id')); + $this->assertContains($client2->_id, $label->clientsWithCustomKeys->pluck('_id')); + + $this->assertNotContains($client3->_id, $label->clientsWithCustomKeys->pluck('_id')); + + $label->clientsWithCustomKeys()->sync($client3); + $label->load('clientsWithCustomKeys'); + + $this->assertEquals(1, $label->clientsWithCustomKeys->count()); + $this->assertNotContains($client1->_id, $label->clientsWithCustomKeys->pluck('_id')); + $this->assertNotContains($client2->_id, $label->clientsWithCustomKeys->pluck('_id')); + + $this->assertContains($client3->_id, $label->clientsWithCustomKeys->pluck('_id')); + } + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From 31dbf5e11f181511b1c73d688fec591c87179632 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 16:51:33 +0330 Subject: [PATCH 33/74] Add test for load and refreshing an instance with MorphToMany relation; --- tests/RelationsTest.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 9d04bc5fb..82906c82a 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -645,6 +645,34 @@ public function testMorphToManySyncingWithCustomKeys(): void $this->assertNotContains($label2->_id, $client->labelsWithCustomKeys->pluck('_id')); } + public function testMorphToManyLoadAndRefreshing(): void + { + $client = Client::query()->create(['name' => 'Hans Thomas']); + + $label = Label::query()->create(['name' => 'My test label']); + $label2 = Label::query()->create(['name' => 'My test label 2']); + + $client->labels()->sync([$label->_id, $label2->_id]); + + $this->assertEquals(2, $client->labels->count()); + + $client->load('labels'); + + $this->assertEquals(2, $client->labels->count()); + + $client->refresh(); + + $this->assertEquals(2, $client->labels->count()); + + $check = Client::query()->find($client->_id); + + $this->assertEquals(2, $check->labels->count()); + + $check = Client::query()->with('labels')->find($client->_id); + + $this->assertEquals(2, $check->labels->count()); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'John Doe']); From d54f46c975a221095763ab234da1329820e22773 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 16:52:48 +0330 Subject: [PATCH 34/74] Add test for load and refreshing an instance with MorphedByMany relation; --- tests/RelationsTest.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 82906c82a..6777d2157 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -843,6 +843,35 @@ public function testMorphedByManySyncingWithCustomKeys(): void $this->assertContains($client3->_id, $label->clientsWithCustomKeys->pluck('_id')); } + public function testMorphedByManyLoadAndRefreshing(): void + { + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + + $label = Label::query()->create(['name' => 'My test label']); + + $label->clients()->sync(new Collection([$client1,$client2,$client3])); + + $this->assertEquals(3, $label->clients->count()); + + $label->load('clients'); + + $this->assertEquals(3, $label->clients->count()); + + $label->refresh(); + + $this->assertEquals(3, $label->clients->count()); + + $check = Label::query()->find($label->_id); + + $this->assertEquals(3, $check->clients->count()); + + $check = Label::query()->with('clients')->find($label->_id); + + $this->assertEquals(3, $check->clients->count()); + } + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From 4026890af3f388e86a145ac1ba689c2bff9e0c7e Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 16:53:41 +0330 Subject: [PATCH 35/74] Fix CS; --- tests/Models/Client.php | 1 + tests/Models/Label.php | 1 + tests/RelationsTest.php | 16 ++++++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/Models/Client.php b/tests/Models/Client.php index 48e4cb571..2ab4f5e33 100644 --- a/tests/Models/Client.php +++ b/tests/Models/Client.php @@ -34,6 +34,7 @@ public function labels() { return $this->morphToMany(Label::class, 'labelled'); } + public function labelsWithCustomKeys() { return $this->morphToMany( diff --git a/tests/Models/Label.php b/tests/Models/Label.php index 8c634ab28..179503ce1 100644 --- a/tests/Models/Label.php +++ b/tests/Models/Label.php @@ -35,6 +35,7 @@ public function clients() { return $this->morphedByMany(Client::class, 'labelled'); } + public function clientsWithCustomKeys() { return $this->morphedByMany( diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 6777d2157..7942c66fc 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -626,10 +626,10 @@ public function testMorphToManySyncingMultipleIds(): void public function testMorphToManySyncingWithCustomKeys(): void { - $client = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Hans Thomas']); + $client = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Hans Thomas']); - $label = Label::query()->create(['clabel_id'=>(string)(new ObjectId()), 'name' => 'My test label']); - $label2 = Label::query()->create(['clabel_id'=>(string)(new ObjectId()), 'name' => 'My test label 2']); + $label = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => 'My test label']); + $label2 = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => 'My test label 2']); $client->labelsWithCustomKeys()->sync([$label->clabel_id, $label2->clabel_id]); @@ -819,11 +819,11 @@ public function testMorphedByManySyncingMultipleIds(): void public function testMorphedByManySyncingWithCustomKeys(): void { - $client1 = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Young Gerald']); - $client2 = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Hans Thomas']); - $client3 = Client::query()->create(['cclient_id'=>(string)(new ObjectId()), 'name' => 'Austin Richard Post']); + $client1 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Young Gerald']); + $client2 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Hans Thomas']); + $client3 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Austin Richard Post']); - $label = Label::query()->create(['clabel_id'=>(string)(new ObjectId()), 'name' => 'My test label']); + $label = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => 'My test label']); $label->clientsWithCustomKeys()->sync([$client1->cclient_id, $client2->cclient_id]); @@ -851,7 +851,7 @@ public function testMorphedByManyLoadAndRefreshing(): void $label = Label::query()->create(['name' => 'My test label']); - $label->clients()->sync(new Collection([$client1,$client2,$client3])); + $label->clients()->sync(new Collection([$client1, $client2, $client3])); $this->assertEquals(3, $label->clients->count()); From 9e21445b9a6edcf91d338af40e3f766e2fd43ee5 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 17:44:48 +0330 Subject: [PATCH 36/74] testMorphedByManyLoadAndRefreshing test updated; --- tests/RelationsTest.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 7942c66fc..316e66b07 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -845,6 +845,8 @@ public function testMorphedByManySyncingWithCustomKeys(): void public function testMorphedByManyLoadAndRefreshing(): void { + $user = User::query()->create(['name' => 'Abel Tesfaye']); + $client1 = Client::query()->create(['name' => 'Young Gerald']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); $client3 = Client::query()->create(['name' => 'Austin Richard Post']); @@ -852,16 +854,19 @@ public function testMorphedByManyLoadAndRefreshing(): void $label = Label::query()->create(['name' => 'My test label']); $label->clients()->sync(new Collection([$client1, $client2, $client3])); + $label->users()->sync($user); - $this->assertEquals(3, $label->clients->count()); + $check = Label::query()->find($label->_id); - $label->load('clients'); + $this->assertEquals(3, $check->clients->count()); - $this->assertEquals(3, $label->clients->count()); + $check->load('clients'); - $label->refresh(); + $this->assertEquals(3, $check->clients->count()); - $this->assertEquals(3, $label->clients->count()); + $check->refresh(); + + $this->assertEquals(3, $check->clients->count()); $check = Label::query()->find($label->_id); From 0872237f5b53234d819653d0dc6439fb401f0c5a Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 17:44:58 +0330 Subject: [PATCH 37/74] Comments updated; --- src/Relations/MorphToMany.php | 43 ++++++++++------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 7e12b815b..385f93906 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -76,11 +76,10 @@ public function addConstraints() /** @inheritdoc */ public function addEagerConstraints(array $models) { + // To load relation's data, we act normally on MorphToMany relation, + // But on MorphedByMany relation, we collect related ids from pivot column + // and add to a whereIn condition if ($this->getInverse()) { - // query -> Client - // related -> Client - // relatedPivotKey -> labelled_id - // table -> labelleds $ids = $this->getKeys($models, $this->table); $ids = array_reduce($ids[0] ?? [], function ($carry, $item) { if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { @@ -107,10 +106,6 @@ public function addEagerConstraints(array $models) protected function setWhere() { if ($this->getInverse()) { - // query -> User - // parent -> Label - // getForeignKey -> labelled_id -in-> User - // relatedPivotKey -> label_ids $ids = array_reduce((array) $this->parent->{$this->table}, function ($carry, $item) { if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { $carry[] = $item[$this->relatedPivotKey]; @@ -123,9 +118,6 @@ protected function setWhere() $this->query->whereIn($this->relatedKey, $ids); } else { - // query -> Label - // parent -> User - // relatedPivotKey -> label_ids $this->query->whereIn($this->relatedKey, (array) $this->parent->{$this->relatedPivotKey}); } @@ -176,14 +168,9 @@ public function sync($ids, $detaching = true) // in this joining table. We'll spin through the given IDs, checking to see // if they exist in the array of current ones, and if not we will insert. if ($this->getInverse()) { - // parent -> Label - // relatedPivotKey -> labelled_id - // table -> labelleds $current = $this->parent->{$this->table} ?: []; - $current = array_filter($current, fn ($item) => $item !== get_class($this->related)); + $current = array_filter($current, fn ($item) => ! str_contains($item,'\\')); } else { - // parent -> User, Client - // relatedPivotKey -> label_ids $current = $this->parent->{$this->relatedPivotKey} ?: []; } @@ -242,19 +229,17 @@ public function attach($id, array $attributes = [], $touch = true) $id = $this->parseId($model); - // Attach the new parent id to the related model. if ($this->getInverse()) { - // related -> User - // parent -> Label - // table -> labelleds + // Attach the new ids to the parent model. $this->parent->push($this->table, [ $this->relatedPivotKey => $model->{$this->relatedKey}, $this->morphType => $model->getMorphClass(), ], true); - // Attach the new ids to the parent model. + // Attach the new parent id to the related model. $model->push($this->foreignPivotKey, $this->parseIds($this->parent), true); } else { + // Attach the new parent id to the related model. $model->push($this->table, [ $this->foreignPivotKey => $this->parent->{$this->parentKey}, $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, @@ -317,10 +302,7 @@ public function detach($ids = [], $touch = true) // Detach all ids from the parent model. if ($this->getInverse()) { - // parent -> label - // related -> Client - // relatedPivotKey -> labelled_id - // foreignPivotKey -> label_ids + // Remove the relation from the parent. foreach ($ids as $item) { $this->parent->pull($this->table, [ $this->relatedPivotKey => $item, @@ -333,12 +315,10 @@ public function detach($ids = [], $touch = true) $query->whereIn($this->relatedKey, $ids); } - // Remove the relation to the parent. + // Remove the relation from the related. $query->pull($this->foreignPivotKey, $this->parent->{$this->parentKey}); } else { - // parent -> Client - // related -> label - // relatedPivotKey -> label_ids + // Remove the relation from the parent. $this->parent->pull($this->relatedPivotKey, $ids); // Prepare the query to select all related objects. @@ -346,7 +326,7 @@ public function detach($ids = [], $touch = true) $query->whereIn($this->relatedKey, $ids); } - // Remove the relation to the parent. + // Remove the relation to the related. $query->pull($this->table, [ $this->foreignPivotKey => $this->parent->{$this->parentKey}, $this->morphType => $this->parent->getMorphClass(), @@ -376,6 +356,7 @@ protected function buildDictionary(Collection $results) $dictionary[$item][] = $result; } } else { + // Collect $foreign value from pivot column of result model $items = array_reduce($result->{$this->table} ?? [], fn ($carry, $item) => array_merge($carry, [$item[$foreign]]), []); foreach ($items as $item) { $dictionary[$item][] = $result; From 77bb8470fea4c2a72ece89bad96288c9dd5ccaf9 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 11 Nov 2023 17:48:58 +0330 Subject: [PATCH 38/74] Fix CS; --- src/Relations/MorphToMany.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 385f93906..33f75a5ca 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -19,7 +19,6 @@ use function array_reduce; use function array_values; use function count; -use function get_class; use function is_array; use function is_numeric; use function is_string; @@ -169,7 +168,7 @@ public function sync($ids, $detaching = true) // if they exist in the array of current ones, and if not we will insert. if ($this->getInverse()) { $current = $this->parent->{$this->table} ?: []; - $current = array_filter($current, fn ($item) => ! str_contains($item,'\\')); + $current = array_filter($current, fn ($item) => ! str_contains($item, '\\')); } else { $current = $this->parent->{$this->relatedPivotKey} ?: []; } From 3f3db6541ba97bd4a02fb09f079c7de29e2df9a8 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 15:03:00 +0330 Subject: [PATCH 39/74] WIP --- tests/RelationsTest.php | 109 +++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 316e66b07..c4456e16d 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -495,10 +495,10 @@ public function testMorph(): void public function testMorphToMany(): void { - $user = User::query()->create(['name' => 'John Doe']); + $user = User::query()->create(['name' => 'Young Gerald']); $client = Client::query()->create(['name' => 'Hans Thomas']); - $label = Label::query()->create(['name' => 'My test label']); + $label = Label::query()->create(['name' => 'sincere']); $user->labels()->attach($label); $client->labels()->attach($label); @@ -528,8 +528,8 @@ public function testMorphToManyAttachMultipleIds(): void { $client = Client::query()->create(['name' => 'Young Gerald']); - $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); - $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); + $label1 = Label::query()->create(['name' => 'stayed solid i never fled']); + $label2 = Label::query()->create(['name' => "I've got a lane and I'm in gear"]); $client->labels()->attach([$label1->_id, $label2->_id]); @@ -540,10 +540,10 @@ public function testMorphToManyAttachMultipleIds(): void public function testMorphToManyDetaching(): void { - $client = Client::query()->create(['name' => 'Young Gerald']); + $client = Client::query()->create(['name' => 'Marshall Mathers']); - $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); - $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); + $label1 = Label::query()->create(['name' => "I'll never love again"]); + $label2 = Label::query()->create(['name' => 'The way I loved you']); $client->labels()->attach([$label1->_id, $label2->_id]); @@ -560,9 +560,9 @@ public function testMorphToManyDetachingMultipleIds(): void { $client = Client::query()->create(['name' => 'Young Gerald']); - $label1 = Label::query()->create(['name' => "Make no mistake, it's the life that I was chosen for"]); - $label2 = Label::query()->create(['name' => 'All I prayed for was an open door']); - $label3 = Label::query()->create(['name' => 'If it was easy, everyone would do it']); + $label1 = Label::query()->create(['name' => "I make what I wanna make, but I won't make everyone happy"]); + $label2 = Label::query()->create(['name' => "My skin's thick, but I'm not bulletproof"]); + $label3 = Label::query()->create(['name' => 'All I can be is myself, go, and tell the truth']); $client->labels()->attach([$label1->_id, $label2->_id, $label3->_id]); @@ -577,11 +577,11 @@ public function testMorphToManyDetachingMultipleIds(): void public function testMorphToManySyncing(): void { - $user = User::query()->create(['name' => 'John Doe']); + $user = User::query()->create(['name' => 'Young Gerald']); $client = Client::query()->create(['name' => 'Hans Thomas']); - $label = Label::query()->create(['name' => 'My test label']); - $label2 = Label::query()->create(['name' => 'My test label 2']); + $label = Label::query()->create(['name' => "Lesson learned, we weren't the perfect match"]); + $label2 = Label::query()->create(['name' => 'Future ref, not keeping personal and work attached']); $user->labels()->sync($label); $client->labels()->sync($label); @@ -598,10 +598,10 @@ public function testMorphToManySyncing(): void public function testMorphToManySyncingEloquentCollection(): void { - $client = Client::query()->create(['name' => 'Hans Thomas']); + $client = Client::query()->create(['name' => 'Young Gerald']); - $label = Label::query()->create(['name' => 'My test label']); - $label2 = Label::query()->create(['name' => 'My test label 2']); + $label = Label::query()->create(['name' => 'Why the ones who love me most, the people I push away?']); + $label2 = Label::query()->create(['name' => 'Look in a mirror, this is you']); $client->labels()->sync(new Collection([$label, $label2])); @@ -612,10 +612,10 @@ public function testMorphToManySyncingEloquentCollection(): void public function testMorphToManySyncingMultipleIds(): void { - $client = Client::query()->create(['name' => 'Hans Thomas']); + $client = Client::query()->create(['name' => 'Young Gerald']); - $label = Label::query()->create(['name' => 'My test label']); - $label2 = Label::query()->create(['name' => 'My test label 2']); + $label = Label::query()->create(['name' => 'They all talk about karma, how it slowly comes']); + $label2 = Label::query()->create(['name' => "But life is short, enjoy it while you're young"]); $client->labels()->sync([$label->_id, $label2->_id]); @@ -626,10 +626,10 @@ public function testMorphToManySyncingMultipleIds(): void public function testMorphToManySyncingWithCustomKeys(): void { - $client = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Hans Thomas']); + $client = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Young Gerald']); - $label = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => 'My test label']); - $label2 = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => 'My test label 2']); + $label = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => "Why do people do things that be bad for 'em?"]); + $label2 = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => "Say we done with these things, then we ask for 'em"]); $client->labelsWithCustomKeys()->sync([$label->clabel_id, $label2->clabel_id]); @@ -647,12 +647,15 @@ public function testMorphToManySyncingWithCustomKeys(): void public function testMorphToManyLoadAndRefreshing(): void { - $client = Client::query()->create(['name' => 'Hans Thomas']); + $user = User::query()->create(['name' => 'The Pretty Reckless']); - $label = Label::query()->create(['name' => 'My test label']); - $label2 = Label::query()->create(['name' => 'My test label 2']); + $client = Client::query()->create(['name' => 'Young Gerald']); + + $label = Label::query()->create(['name' => 'The greatest gift is knowledge itself']); + $label2 = Label::query()->create(['name' => "I made it here all by my lonely, no askin' for help"]); $client->labels()->sync([$label->_id, $label2->_id]); + $client->users()->sync($user); $this->assertEquals(2, $client->labels->count()); @@ -675,11 +678,11 @@ public function testMorphToManyLoadAndRefreshing(): void public function testMorphedByMany(): void { - $user = User::query()->create(['name' => 'John Doe']); + $user = User::query()->create(['name' => 'Young Gerald']); $client = Client::query()->create(['name' => 'Hans Thomas']); - $client2 = Client::query()->create(['name' => 'Hans Thomas']); + $extra = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'My test label']); + $label = Label::query()->create(['name' => 'Never finished, tryna search for more']); $label->users()->attach($user); $label->clients()->attach($client); @@ -695,7 +698,7 @@ public function testMorphedByManyAttachEloquentCollection(): void { $client1 = Client::query()->create(['name' => 'Young Gerald']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $extra = Client::query()->create(['name' => 'one more client']); + $extra = Client::query()->create(['name' => 'John Doe']); $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); @@ -711,11 +714,11 @@ public function testMorphedByManyAttachEloquentCollection(): void public function testMorphedByManyAttachMultipleIds(): void { - $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client1 = Client::query()->create(['name' => 'Austin Richard Post']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $extra = Client::query()->create(['name' => 'one more client']); + $extra = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + $label = Label::query()->create(['name' => 'Always in the game and never played by the rules']); $label->clients()->attach([$client1->_id, $client2->_id]); @@ -729,11 +732,11 @@ public function testMorphedByManyAttachMultipleIds(): void public function testMorphedByManyDetaching(): void { - $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client1 = Client::query()->create(['name' => 'Austin Richard Post']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $extra = Client::query()->create(['name' => 'one more client']); + $extra = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + $label = Label::query()->create(['name' => 'Seasons change and our love went cold']); $label->clients()->attach([$client1->_id, $client2->_id]); @@ -748,11 +751,11 @@ public function testMorphedByManyDetaching(): void public function testMorphedByManyDetachingMultipleIds(): void { - $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client1 = Client::query()->create(['name' => 'Austin Richard Post']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + $client3 = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'They want me to architect Rome, in a day']); + $label = Label::query()->create(['name' => "Run away, but we're running in circles"]); $label->clients()->attach([$client1->_id, $client2->_id, $client3->_id]); @@ -767,11 +770,11 @@ public function testMorphedByManyDetachingMultipleIds(): void public function testMorphedByManySyncing(): void { - $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client1 = Client::query()->create(['name' => 'Austin Richard Post']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + $client3 = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'My test label']); + $label = Label::query()->create(['name' => "Was scared of losin' somethin' that we never found"]); $label->clients()->sync($client1); $label->clients()->sync($client2, false); @@ -785,11 +788,11 @@ public function testMorphedByManySyncing(): void public function testMorphedByManySyncingEloquentCollection(): void { - $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client1 = Client::query()->create(['name' => 'Austin Richard Post']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + $extra = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'My test label']); + $label = Label::query()->create(['name' => "I'm goin' hard 'til I'm gone. Can you feel it?"]); $label->clients()->sync(new Collection([$client1, $client2])); @@ -797,16 +800,16 @@ public function testMorphedByManySyncingEloquentCollection(): void $this->assertContains($client1->_id, $label->clients->pluck('_id')); $this->assertContains($client2->_id, $label->clients->pluck('_id')); - $this->assertNotContains($client3->_id, $label->clients->pluck('_id')); + $this->assertNotContains($extra->_id, $label->clients->pluck('_id')); } public function testMorphedByManySyncingMultipleIds(): void { - $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client1 = Client::query()->create(['name' => 'Dorothy']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + $extra = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'My test label']); + $label = Label::query()->create(['name' => "Love ain't patient, it's not kind. true love waits to rob you blind"]); $label->clients()->sync([$client1->_id, $client2->_id]); @@ -814,16 +817,16 @@ public function testMorphedByManySyncingMultipleIds(): void $this->assertContains($client1->_id, $label->clients->pluck('_id')); $this->assertContains($client2->_id, $label->clients->pluck('_id')); - $this->assertNotContains($client3->_id, $label->clients->pluck('_id')); + $this->assertNotContains($extra->_id, $label->clients->pluck('_id')); } public function testMorphedByManySyncingWithCustomKeys(): void { $client1 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Young Gerald']); $client2 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Hans Thomas']); - $client3 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'Austin Richard Post']); + $client3 = Client::query()->create(['cclient_id' => (string) (new ObjectId()), 'name' => 'John Doe']); - $label = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => 'My test label']); + $label = Label::query()->create(['clabel_id' => (string) (new ObjectId()), 'name' => "I'm in my own lane, so what do I have to hurry for?"]); $label->clientsWithCustomKeys()->sync([$client1->cclient_id, $client2->cclient_id]); @@ -849,9 +852,9 @@ public function testMorphedByManyLoadAndRefreshing(): void $client1 = Client::query()->create(['name' => 'Young Gerald']); $client2 = Client::query()->create(['name' => 'Hans Thomas']); - $client3 = Client::query()->create(['name' => 'Austin Richard Post']); + $client3 = Client::query()->create(['name' => 'John Doe']); - $label = Label::query()->create(['name' => 'My test label']); + $label = Label::query()->create(['name' => "but don't think I don't think about you just cause I ain't spoken about you"]); $label->clients()->sync(new Collection([$client1, $client2, $client3])); $label->users()->sync($user); From 1903bb421161534327bac2e1e153e55af4b2eed7 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 15:38:29 +0330 Subject: [PATCH 40/74] Fix pushing relation data to the instance; --- src/Relations/MorphToMany.php | 17 +++++++++++++---- tests/RelationsTest.php | 13 ++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 33f75a5ca..77083374c 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -168,6 +168,15 @@ public function sync($ids, $detaching = true) // if they exist in the array of current ones, and if not we will insert. if ($this->getInverse()) { $current = $this->parent->{$this->table} ?: []; + $current = array_reduce($current, function ($carry, $item) { + if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { + $carry[] = $item[$this->relatedPivotKey]; + } elseif (is_string($item) && ! str_contains($item, '\\')) { + $carry[] = $item; + } + + return $carry; + }, []); $current = array_filter($current, fn ($item) => ! str_contains($item, '\\')); } else { $current = $this->parent->{$this->relatedPivotKey} ?: []; @@ -263,17 +272,17 @@ public function attach($id, array $attributes = [], $touch = true) // Attach the new ids to the parent model. foreach ($id as $item) { - $this->parent->push($this->table, [ + $this->parent->push($this->table, [[ $this->relatedPivotKey => $item, $this->morphType => $this->related instanceof Model ? $this->related->getMorphClass() : null, - ], true); + ]], true); } } else { // Attach the new parent id to the related model. - $query->push($this->table, [ + $query->push($this->table, [[ $this->foreignPivotKey => $this->parent->{$this->parentKey}, $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, - ], true); + ]], true); // Attach the new ids to the parent model. $this->parent->push($this->relatedPivotKey, $id, true); diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index c4456e16d..d1b9e6bdd 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -645,6 +645,7 @@ public function testMorphToManySyncingWithCustomKeys(): void $this->assertNotContains($label2->_id, $client->labelsWithCustomKeys->pluck('_id')); } + /** @group hans */ public function testMorphToManyLoadAndRefreshing(): void { $user = User::query()->create(['name' => 'The Pretty Reckless']); @@ -859,17 +860,15 @@ public function testMorphedByManyLoadAndRefreshing(): void $label->clients()->sync(new Collection([$client1, $client2, $client3])); $label->users()->sync($user); - $check = Label::query()->find($label->_id); - - $this->assertEquals(3, $check->clients->count()); + $this->assertEquals(3, $label->clients->count()); - $check->load('clients'); + $label->load('clients'); - $this->assertEquals(3, $check->clients->count()); + $this->assertEquals(3, $label->clients->count()); - $check->refresh(); + $label->refresh(); - $this->assertEquals(3, $check->clients->count()); + $this->assertEquals(3, $label->clients->count()); $check = Label::query()->find($label->_id); From 3888115b434e47fb1aa69dd743cb9af573ef61d6 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 15:40:01 +0330 Subject: [PATCH 41/74] Fix CS; --- src/Relations/MorphToMany.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 77083374c..26ad63e8a 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -272,17 +272,21 @@ public function attach($id, array $attributes = [], $touch = true) // Attach the new ids to the parent model. foreach ($id as $item) { - $this->parent->push($this->table, [[ - $this->relatedPivotKey => $item, - $this->morphType => $this->related instanceof Model ? $this->related->getMorphClass() : null, - ]], true); + $this->parent->push($this->table, [ + [ + $this->relatedPivotKey => $item, + $this->morphType => $this->related instanceof Model ? $this->related->getMorphClass() : null, + ], + ], true); } } else { // Attach the new parent id to the related model. - $query->push($this->table, [[ - $this->foreignPivotKey => $this->parent->{$this->parentKey}, - $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, - ]], true); + $query->push($this->table, [ + [ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ], + ], true); // Attach the new ids to the parent model. $this->parent->push($this->relatedPivotKey, $id, true); From bf990a56cba48cb0da0bbbace3cf20fea31a0eb0 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 15:40:14 +0330 Subject: [PATCH 42/74] Update RelationsTest.php --- tests/RelationsTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index d1b9e6bdd..4037baafb 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -645,7 +645,6 @@ public function testMorphToManySyncingWithCustomKeys(): void $this->assertNotContains($label2->_id, $client->labelsWithCustomKeys->pluck('_id')); } - /** @group hans */ public function testMorphToManyLoadAndRefreshing(): void { $user = User::query()->create(['name' => 'The Pretty Reckless']); From 9e41f464309f9f37bca5bcd629e662ae3cec8ac5 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 15:49:50 +0330 Subject: [PATCH 43/74] Fix the error; --- src/Relations/MorphToMany.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 26ad63e8a..43a15726a 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -249,8 +249,10 @@ public function attach($id, array $attributes = [], $touch = true) } else { // Attach the new parent id to the related model. $model->push($this->table, [ - $this->foreignPivotKey => $this->parent->{$this->parentKey}, - $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + [ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null, + ], ], true); // Attach the new ids to the parent model. @@ -317,8 +319,10 @@ public function detach($ids = [], $touch = true) // Remove the relation from the parent. foreach ($ids as $item) { $this->parent->pull($this->table, [ - $this->relatedPivotKey => $item, - $this->morphType => $this->related->getMorphClass(), + [ + $this->relatedPivotKey => $item, + $this->morphType => $this->related->getMorphClass(), + ], ]); } @@ -340,8 +344,10 @@ public function detach($ids = [], $touch = true) // Remove the relation to the related. $query->pull($this->table, [ - $this->foreignPivotKey => $this->parent->{$this->parentKey}, - $this->morphType => $this->parent->getMorphClass(), + [ + $this->foreignPivotKey => $this->parent->{$this->parentKey}, + $this->morphType => $this->parent->getMorphClass(), + ], ]); } From 91e05565319cc91439fa4229f96d6a569c6de92e Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 16:04:40 +0330 Subject: [PATCH 44/74] Rerun the pipeline; --- tests/RelationsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 4037baafb..d42ac94cd 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -498,7 +498,7 @@ public function testMorphToMany(): void $user = User::query()->create(['name' => 'Young Gerald']); $client = Client::query()->create(['name' => 'Hans Thomas']); - $label = Label::query()->create(['name' => 'sincere']); + $label = Label::query()->create(['name' => 'Had the world in my palms, I gave it to you']); $user->labels()->attach($label); $client->labels()->attach($label); From f8c322a0cd34fc323a28da3839398b5edc03a705 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 16:27:19 +0330 Subject: [PATCH 45/74] Create extractIds method; --- src/Relations/MorphToMany.php | 61 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 43a15726a..399ccc415 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -80,15 +80,7 @@ public function addEagerConstraints(array $models) // and add to a whereIn condition if ($this->getInverse()) { $ids = $this->getKeys($models, $this->table); - $ids = array_reduce($ids[0] ?? [], function ($carry, $item) { - if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { - $carry[] = $item[$this->relatedPivotKey]; - } elseif (is_string($item) && ! str_contains($item, '\\')) { - $carry[] = $item; - } - - return $carry; - }, []); + $ids = $this->extractIds($ids[0] ?? []); $this->query->whereIn($this->relatedKey, $ids); } else { parent::addEagerConstraints($models); @@ -105,15 +97,7 @@ public function addEagerConstraints(array $models) protected function setWhere() { if ($this->getInverse()) { - $ids = array_reduce((array) $this->parent->{$this->table}, function ($carry, $item) { - if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { - $carry[] = $item[$this->relatedPivotKey]; - } elseif (is_string($item) && ! str_contains($item, '\\')) { - $carry[] = $item; - } - - return $carry; - }, []); + $ids = $this->extractIds((array) $this->parent->{$this->table}); $this->query->whereIn($this->relatedKey, $ids); } else { @@ -167,16 +151,7 @@ public function sync($ids, $detaching = true) // in this joining table. We'll spin through the given IDs, checking to see // if they exist in the array of current ones, and if not we will insert. if ($this->getInverse()) { - $current = $this->parent->{$this->table} ?: []; - $current = array_reduce($current, function ($carry, $item) { - if (is_array($item) && array_key_exists($this->relatedPivotKey, $item)) { - $carry[] = $item[$this->relatedPivotKey]; - } elseif (is_string($item) && ! str_contains($item, '\\')) { - $carry[] = $item; - } - - return $carry; - }, []); + $current = $this->extractIds($this->parent->{$this->table} ?: []); $current = array_filter($current, fn ($item) => ! str_contains($item, '\\')); } else { $current = $this->parent->{$this->relatedPivotKey} ?: []; @@ -240,8 +215,10 @@ public function attach($id, array $attributes = [], $touch = true) if ($this->getInverse()) { // Attach the new ids to the parent model. $this->parent->push($this->table, [ - $this->relatedPivotKey => $model->{$this->relatedKey}, - $this->morphType => $model->getMorphClass(), + [ + $this->relatedPivotKey => $model->{$this->relatedKey}, + $this->morphType => $model->getMorphClass(), + ] ], true); // Attach the new parent id to the related model. @@ -375,7 +352,7 @@ protected function buildDictionary(Collection $results) } } else { // Collect $foreign value from pivot column of result model - $items = array_reduce($result->{$this->table} ?? [], fn ($carry, $item) => array_merge($carry, [$item[$foreign]]), []); + $items = $this->extractIds($result->{$this->table} ?? [], $foreign); foreach ($items as $item) { $dictionary[$item][] = $result; } @@ -458,4 +435,26 @@ protected function whereInMethod(Model $model, $key) { return 'whereIn'; } + + /** + * Extract ids from given pivot table data + * + * @param array $data + * @param string|null $relatedPivotKey + * + * @return mixed + */ + private function extractIds(array $data, string $relatedPivotKey = null) + { + $relatedPivotKey = $relatedPivotKey ?: $this->relatedPivotKey; + return array_reduce($data, function ($carry, $item)use ($relatedPivotKey) { + if (is_array($item) && array_key_exists($relatedPivotKey, $item)) { + $carry[] = $item[$relatedPivotKey]; + } elseif (is_string($item) && ! str_contains($item, '\\')) { + $carry[] = $item; + } + + return $carry; + }, []); + } } From 2f83d0249354a276e7d08ea2e3bdb9371ff0cece Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 16:28:26 +0330 Subject: [PATCH 46/74] Fix CS; --- src/Relations/MorphToMany.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 399ccc415..adfb4d1fd 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -218,7 +218,7 @@ public function attach($id, array $attributes = [], $touch = true) [ $this->relatedPivotKey => $model->{$this->relatedKey}, $this->morphType => $model->getMorphClass(), - ] + ], ], true); // Attach the new parent id to the related model. @@ -439,15 +439,15 @@ protected function whereInMethod(Model $model, $key) /** * Extract ids from given pivot table data * - * @param array $data - * @param string|null $relatedPivotKey + * @param array $data + * @param string|null $relatedPivotKey * * @return mixed */ - private function extractIds(array $data, string $relatedPivotKey = null) + private function extractIds(array $data, ?string $relatedPivotKey = null) { $relatedPivotKey = $relatedPivotKey ?: $this->relatedPivotKey; - return array_reduce($data, function ($carry, $item)use ($relatedPivotKey) { + return array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { if (is_array($item) && array_key_exists($relatedPivotKey, $item)) { $carry[] = $item[$relatedPivotKey]; } elseif (is_string($item) && ! str_contains($item, '\\')) { From e411071314f0c74d889aba5434ca55fa617e78b4 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 16:38:11 +0330 Subject: [PATCH 47/74] Update MorphToMany.php --- src/Relations/MorphToMany.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index adfb4d1fd..b8a941ffb 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -152,7 +152,6 @@ public function sync($ids, $detaching = true) // if they exist in the array of current ones, and if not we will insert. if ($this->getInverse()) { $current = $this->extractIds($this->parent->{$this->table} ?: []); - $current = array_filter($current, fn ($item) => ! str_contains($item, '\\')); } else { $current = $this->parent->{$this->relatedPivotKey} ?: []; } From c0973927634f7a98f64ce455998c20b2a21bc54e Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sun, 12 Nov 2023 16:56:48 +0330 Subject: [PATCH 48/74] Detach multiple ids at once; --- src/Relations/MorphToMany.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index b8a941ffb..ebc7c3348 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -11,7 +11,6 @@ use Illuminate\Support\Arr; use function array_diff; -use function array_filter; use function array_key_exists; use function array_keys; use function array_map; @@ -293,15 +292,18 @@ public function detach($ids = [], $touch = true) // Detach all ids from the parent model. if ($this->getInverse()) { // Remove the relation from the parent. + $data = []; foreach ($ids as $item) { - $this->parent->pull($this->table, [ + $data = array_merge($data, [ [ $this->relatedPivotKey => $item, - $this->morphType => $this->related->getMorphClass(), + $this->morphType => $this->related->getMorphClass(), ], ]); } + $this->parent->pull($this->table, $data); + // Prepare the query to select all related objects. if (count($ids) > 0) { $query->whereIn($this->relatedKey, $ids); From 711ed0f010a3c84d38f00093d8970cd86538c42d Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Mon, 13 Nov 2023 16:42:32 +0330 Subject: [PATCH 49/74] Remove meaningless comments; --- src/Eloquent/HybridRelations.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 12561bd22..1ff34dc21 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -384,8 +384,8 @@ public function morphToMany( $instance = new $related(); - $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; // labelled_id - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; // label_ids + $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; + $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; // Now we're ready to create a new query builder for the related model and // the relationship instances for this relation. This relation will set From b785d9312dde88af58c6bcd57d45921365a3bc46 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Mon, 13 Nov 2023 16:52:49 +0330 Subject: [PATCH 50/74] Pluralize using Str facade; --- src/Eloquent/HybridRelations.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 1ff34dc21..f8d24716b 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -385,7 +385,7 @@ public function morphToMany( $instance = new $related(); $foreignPivotKey = $foreignPivotKey ?: $name . '_id'; - $relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey() . 's'; + $relatedPivotKey = $relatedPivotKey ?: Str::plural($instance->getForeignKey()); // Now we're ready to create a new query builder for the related model and // the relationship instances for this relation. This relation will set @@ -433,7 +433,7 @@ public function morphedByMany( $relatedKey = null, $relation = null, ) { - $foreignPivotKey = $foreignPivotKey ?: $this->getForeignKey() . 's'; + $foreignPivotKey = $foreignPivotKey ?: Str::plural($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 From c443ca6b693061b8f775c9773af5c7fd0b49ba1a Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 13:41:43 +0330 Subject: [PATCH 51/74] Remove guessBelongsToManyRelation method; --- src/Eloquent/HybridRelations.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index f8d24716b..b854902f5 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -453,20 +453,6 @@ public function morphedByMany( ); } - /** - * Get the relationship name of the belongs to many. - * - * @return string - */ - protected function guessBelongsToManyRelation() - { - if (method_exists($this, 'getBelongsToManyCaller')) { - return $this->getBelongsToManyCaller(); - } - - return parent::guessBelongsToManyRelation(); - } - /** @inheritdoc */ public function newEloquentBuilder($query) { From f6b5112bef36014d8118ba0258e9317eba1117f3 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 13:42:41 +0330 Subject: [PATCH 52/74] Remove getSelectColumns method; --- src/Relations/MorphToMany.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index ebc7c3348..d0fbcf285 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -47,16 +47,6 @@ protected function hydratePivotRelation(array $models) // Do nothing. } - /** - * Set the select clause for the relation query. - * - * @return array - */ - protected function getSelectColumns(array $columns = ['*']) - { - return $columns; - } - /** @inheritdoc */ protected function shouldSelect(array $columns = ['*']) { From 0674ad6bb042644cf16a22b90e75af390b421839 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 14:39:12 +0330 Subject: [PATCH 53/74] Remove getQualifiedForeignPivotKeyName method; Don't need to override. the result is the same as parent. --- src/Relations/MorphToMany.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index d0fbcf285..419ac7eea 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -379,14 +379,6 @@ public function getForeignKey() return $this->foreignPivotKey; } - /** @inheritdoc */ - public function getQualifiedForeignPivotKeyName() - { - return str_contains($this->foreignPivotKey, '.') - ? $this->foreignPivotKey - : $this->table . '.' . $this->foreignPivotKey; - } - /** @inheritdoc */ public function getQualifiedRelatedPivotKeyName() { From 874a7d3f393c85304f8561da3f80460b1598ea55 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 14:41:13 +0330 Subject: [PATCH 54/74] Remove getForeignKey method; Replace the method's result with the method. --- src/Relations/MorphToMany.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 419ac7eea..25e666c84 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -32,7 +32,7 @@ class MorphToMany extends EloquentMorphToMany */ public function getHasCompareKey() { - return $this->getForeignKey(); + return $this->foreignPivotKey; } /** @inheritdoc */ @@ -369,16 +369,6 @@ public function newRelatedQuery() return $this->related->newQuery(); } - /** - * Get the fully qualified foreign key for the relation. - * - * @return string - */ - public function getForeignKey() - { - return $this->foreignPivotKey; - } - /** @inheritdoc */ public function getQualifiedRelatedPivotKeyName() { From add4f1c0aad0795f7939330f0904bdd221d801db Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 14:55:57 +0330 Subject: [PATCH 55/74] formatRecordsList replaced with deprecated formatSyncList method; --- src/Relations/MorphToMany.php | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 25e666c84..4f8305eb8 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -150,7 +150,7 @@ public function sync($ids, $detaching = true) $current = $this->parseIds($current); } - $records = $this->formatSyncList($ids); + $records = $this->formatRecordsList($ids); $current = Arr::wrap($current); @@ -375,28 +375,6 @@ public function getQualifiedRelatedPivotKeyName() return $this->relatedPivotKey; } - /** - * Format the sync list so that it is keyed by ID. (Legacy Support) - * The original function has been renamed to formatRecordsList since Laravel 5.3. - * - * @deprecated - * - * @return array - */ - protected function formatSyncList(array $records) - { - $results = []; - foreach ($records as $id => $attributes) { - if (! is_array($attributes)) { - [$id, $attributes] = [$attributes, []]; - } - - $results[$id] = $attributes; - } - - return $results; - } - /** * Get the name of the "where in" method for eager loading. * From 4e2766e568268c4a4d1942be63ebe19d8f67ae16 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 15:08:29 +0330 Subject: [PATCH 56/74] Remove useless condition in extractIds method; --- src/Relations/MorphToMany.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 4f8305eb8..18bb234c6 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -401,8 +401,6 @@ private function extractIds(array $data, ?string $relatedPivotKey = null) return array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { if (is_array($item) && array_key_exists($relatedPivotKey, $item)) { $carry[] = $item[$relatedPivotKey]; - } elseif (is_string($item) && ! str_contains($item, '\\')) { - $carry[] = $item; } return $carry; From 45a0c7a22a029f77f2200e28409ec856d6c1156c Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Thu, 16 Nov 2023 15:08:34 +0330 Subject: [PATCH 57/74] Fix CS; --- src/Eloquent/HybridRelations.php | 1 - src/Relations/MorphToMany.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index b854902f5..859c70eb1 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -21,7 +21,6 @@ use function debug_backtrace; use function implode; use function is_subclass_of; -use function method_exists; use function preg_split; use const DEBUG_BACKTRACE_IGNORE_ARGS; diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 18bb234c6..a65c72b90 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -20,8 +20,6 @@ use function count; use function is_array; use function is_numeric; -use function is_string; -use function str_contains; class MorphToMany extends EloquentMorphToMany { From 5d229ebab0bf27a02dd75ec0475216d77276f52c Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 11:32:12 +0330 Subject: [PATCH 58/74] extractIds method returns unique ids; --- src/Relations/MorphToMany.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index a65c72b90..44aeec25e 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -393,15 +393,17 @@ protected function whereInMethod(Model $model, $key) * * @return mixed */ - private function extractIds(array $data, ?string $relatedPivotKey = null) + public function extractIds(array $data, ?string $relatedPivotKey = null) { $relatedPivotKey = $relatedPivotKey ?: $this->relatedPivotKey; - return array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { + $result = array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { if (is_array($item) && array_key_exists($relatedPivotKey, $item)) { $carry[] = $item[$relatedPivotKey]; } return $carry; }, []); + + return array_values(array_unique($result)); } } From b56b90e8166d055f67f1c2ad224e8442e83a9210 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 11:32:49 +0330 Subject: [PATCH 59/74] getHasCompareKey updated; --- src/Relations/MorphToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 44aeec25e..da8cdfa76 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -30,7 +30,7 @@ class MorphToMany extends EloquentMorphToMany */ public function getHasCompareKey() { - return $this->foreignPivotKey; + return $this->relatedKey; } /** @inheritdoc */ From ca1cc626dcff0d05bc310c8b90e4f530b1599e11 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 11:47:04 +0330 Subject: [PATCH 60/74] Handling MorphToMany has query; --- src/Helpers/QueriesRelationships.php | 36 +++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index a83c96e3e..b2551beac 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -14,6 +14,7 @@ use Illuminate\Support\Collection; use MongoDB\Laravel\Eloquent\Model; +use MongoDB\Laravel\Relations\MorphToMany; use function array_count_values; use function array_filter; use function array_keys; @@ -114,13 +115,46 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $ $not = ! $not; } - $relations = $hasQuery->pluck($this->getHasCompareKey($relation)); + $relations = match (true){ + $relation instanceof MorphToMany => $relation->getInverse() ? + $this->handleMorphedByMany($hasQuery,$relation): + $this->handleMorphToMany($hasQuery,$relation), + default => $hasQuery->pluck($this->getHasCompareKey($relation)) + }; $relatedIds = $this->getConstrainedRelatedIds($relations, $operator, $count); return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not); } + /** + * @param Builder $hasQuery + * @param Relation $relation + * + * @return Collection + */ + private function handleMorphToMany($hasQuery,$relation) + { + // First we select the parent models that have a relation to our related model, + // Then extracts related model's ids from the pivot column + $hasQuery->where($relation->getTable().'.'.$relation->getMorphType(), get_class($relation->getParent())); + $relations = $hasQuery->pluck($relation->getTable()); + $relations = $relation->extractIds($relations->flatten(1)->toArray(),$relation->getForeignPivotKeyName()); + return collect($relations); + } + + /** + * @param Builder $hasQuery + * @param Relation $relation + * + * @return Collection + */ + private function handleMorphedByMany($hasQuery,$relation) + { + // Not implemented yet. + return $hasQuery->pluck($this->getHasCompareKey($relation)); + } + /** @return string */ protected function getHasCompareKey(Relation $relation) { From 5d77236ffc9dc003a10b2ab75fa7c943dcc67fbf Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 11:47:38 +0330 Subject: [PATCH 61/74] Add tests for handling has query in MorphToMany relationship; --- tests/RelationsTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index d42ac94cd..86adbf086 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -5,6 +5,7 @@ namespace MongoDB\Laravel\Tests; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\DB; use Mockery; use MongoDB\BSON\ObjectId; use MongoDB\Laravel\Tests\Models\Address; @@ -676,6 +677,22 @@ public function testMorphToManyLoadAndRefreshing(): void $this->assertEquals(2, $check->labels->count()); } + public function testMorphToManyHasQuery(): void + { + $client = Client::query()->create(['name' => 'Ashley']); + $client2 = Client::query()->create(['name' => 'John Doe']); + $client3 = Client::query()->create(['name' => 'John Doe 2']); + + $label = Label::query()->create(['name' => "I've been digging myself down deeper"]); + $label2 = Label::query()->create(['name' => "I won't stop 'til I get where you are"]); + + $client->labels()->sync([$label->_id, $label2->_id]); + + $this->assertEquals(2, $client->labels->count()); + + $this->assertEquals(1, Client::query()->has('labels')->count()); + } + public function testMorphedByMany(): void { $user = User::query()->create(['name' => 'Young Gerald']); From 9618b84f53a43c589290b1c90e7e691e770dc81c Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 16:13:30 +0330 Subject: [PATCH 62/74] Handling MorphedByMany has query; --- src/Helpers/QueriesRelationships.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index b2551beac..072b5774c 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -151,8 +151,8 @@ private function handleMorphToMany($hasQuery,$relation) */ private function handleMorphedByMany($hasQuery,$relation) { - // Not implemented yet. - return $hasQuery->pluck($this->getHasCompareKey($relation)); + $hasQuery->whereNotNull($relation->getForeignPivotKeyName()); + return $hasQuery->pluck($relation->getRelatedKeyName()); } /** @return string */ @@ -218,6 +218,10 @@ protected function getRelatedConstraintKey(Relation $relation) return $relation->getForeignKeyName(); } + if ($relation instanceof MorphToMany && $relation->getInverse()) { + return $relation->getTable().'.'.$relation->getRelatedPivotKeyName(); + } + if ($relation instanceof BelongsToMany && ! $this->isAcrossConnections($relation)) { return $this->model->getKeyName(); } From 6d6a99e0cdf7e693f67f2e118a7734c97832ddae Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 16:14:24 +0330 Subject: [PATCH 63/74] getHasCompareKey removed; We don't need this method anymore; --- src/Relations/MorphToMany.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index da8cdfa76..45802bdc0 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -23,15 +23,6 @@ class MorphToMany extends EloquentMorphToMany { - /** - * Get the key for comparing against the parent key in "has" query. - * - * @return string - */ - public function getHasCompareKey() - { - return $this->relatedKey; - } /** @inheritdoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) From 9f53b26f3fd52c1ec6a264074df350519d996679 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 16:14:50 +0330 Subject: [PATCH 64/74] Add test for MorphedByMany has query; --- tests/RelationsTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 86adbf086..f0755b4ae 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -895,6 +895,31 @@ public function testMorphedByManyLoadAndRefreshing(): void $this->assertEquals(3, $check->clients->count()); } + public function testMorphedByManyHasQuery(): void + { + $user = User::query()->create(['name' => 'Austin Richard Post']); + + $client1 = Client::query()->create(['name' => 'Young Gerald']); + $client2 = Client::query()->create(['name' => 'John Doe']); + + $label = Label::query()->create(['name' => "My star's back shining bright, I just polished it"]); + $label2 = Label::query()->create(['name' => "Somethin' in my spirit woke back up like I just sat up"]); + $label3 = Label::query()->create(['name' => "How can I beam when you blocking my light?"]); + + $label->clients()->sync(new Collection([$client1,$client2])); + $label2->clients()->sync($client1); + $label3->users()->sync($user); + + $this->assertEquals(2, $label->clients->count()); + + $this->assertEquals(2, Label::query()->has('clients')->count()); + $this->assertContains($label->_id,Label::query()->has('clients')->pluck('_id')); + $this->assertContains($label2->_id,Label::query()->has('clients')->pluck('_id')); + + $this->assertEquals(1, Label::query()->has('users')->count()); + $this->assertContains($label3->_id,Label::query()->has('users')->pluck('_id')); + } + public function testHasManyHas(): void { $author1 = User::create(['name' => 'George R. R. Martin']); From c4ca1466dc46ca103a4e52e57a919e3b2253ae1e Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 16:38:51 +0330 Subject: [PATCH 65/74] Fix handling custom MorphedByMany has query; --- src/Helpers/QueriesRelationships.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index 072b5774c..eff7f40be 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -152,7 +152,7 @@ private function handleMorphToMany($hasQuery,$relation) private function handleMorphedByMany($hasQuery,$relation) { $hasQuery->whereNotNull($relation->getForeignPivotKeyName()); - return $hasQuery->pluck($relation->getRelatedKeyName()); + return $hasQuery->pluck($relation->getForeignPivotKeyName())->flatten(1); } /** @return string */ @@ -218,10 +218,6 @@ protected function getRelatedConstraintKey(Relation $relation) return $relation->getForeignKeyName(); } - if ($relation instanceof MorphToMany && $relation->getInverse()) { - return $relation->getTable().'.'.$relation->getRelatedPivotKeyName(); - } - if ($relation instanceof BelongsToMany && ! $this->isAcrossConnections($relation)) { return $this->model->getKeyName(); } From 989b2635eafc209719eaba10774d46b6ef15b860 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 16:39:20 +0330 Subject: [PATCH 66/74] Add test for MorphByMany has query; --- tests/RelationsTest.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index f0755b4ae..cabf7e8ca 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -912,12 +912,18 @@ public function testMorphedByManyHasQuery(): void $this->assertEquals(2, $label->clients->count()); - $this->assertEquals(2, Label::query()->has('clients')->count()); - $this->assertContains($label->_id,Label::query()->has('clients')->pluck('_id')); - $this->assertContains($label2->_id,Label::query()->has('clients')->pluck('_id')); - - $this->assertEquals(1, Label::query()->has('users')->count()); - $this->assertContains($label3->_id,Label::query()->has('users')->pluck('_id')); + $check = Label::query()->has('clients')->get(); + $this->assertCount(2, $check); + $this->assertContains($label->_id,$check->pluck('_id')); + $this->assertContains($label2->_id,$check->pluck('_id')); + + $check = Label::query()->has('users')->get(); + $this->assertCount(1, $check); + $this->assertContains($label3->_id,$check->pluck('_id')); + + $check = Label::query()->has('clients', '>', 1)->get(); + $this->assertCount(1, $check); + $this->assertContains($label->_id,$check->pluck('_id')); } public function testHasManyHas(): void @@ -930,10 +936,10 @@ public function testHasManyHas(): void User::create(['name' => 'Anonymous author']); Book::create(['title' => 'Anonymous book', 'rating' => 1]); - $authors = User::has('books')->get(); - $this->assertCount(2, $authors); - $this->assertEquals('George R. R. Martin', $authors[0]->name); - $this->assertEquals('John Doe', $authors[1]->name); +// $authors = User::has('books')->get(); +// $this->assertCount(2, $authors); +// $this->assertEquals('George R. R. Martin', $authors[0]->name); +// $this->assertEquals('John Doe', $authors[1]->name); $authors = User::has('books', '>', 1)->get(); $this->assertCount(1, $authors); From 1817c941fc88da5176edda87ccbe8d0f5ac7a674 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 17:04:07 +0330 Subject: [PATCH 67/74] Revert "extractIds method returns unique ids;" This reverts commit 5d229ebab0bf27a02dd75ec0475216d77276f52c. --- src/Relations/MorphToMany.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 45802bdc0..68e4f8edf 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -384,17 +384,15 @@ protected function whereInMethod(Model $model, $key) * * @return mixed */ - public function extractIds(array $data, ?string $relatedPivotKey = null) + private function extractIds(array $data, ?string $relatedPivotKey = null) { $relatedPivotKey = $relatedPivotKey ?: $this->relatedPivotKey; - $result = array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { + return array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { if (is_array($item) && array_key_exists($relatedPivotKey, $item)) { $carry[] = $item[$relatedPivotKey]; } return $carry; }, []); - - return array_values(array_unique($result)); } } From fc53e7d828ce1dbadbfbfc954150685028a2a11b Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 17:05:00 +0330 Subject: [PATCH 68/74] Make extractIds method public; --- src/Relations/MorphToMany.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 68e4f8edf..7969ce302 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -384,7 +384,7 @@ protected function whereInMethod(Model $model, $key) * * @return mixed */ - private function extractIds(array $data, ?string $relatedPivotKey = null) + public function extractIds(array $data, ?string $relatedPivotKey = null) { $relatedPivotKey = $relatedPivotKey ?: $this->relatedPivotKey; return array_reduce($data, function ($carry, $item) use ($relatedPivotKey) { From d9dedf67336540d65975592004f1acb960975a98 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 17:05:44 +0330 Subject: [PATCH 69/74] Add test for handling custom MorphToMany has query; --- tests/RelationsTest.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index cabf7e8ca..12e6f7af2 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -680,17 +680,29 @@ public function testMorphToManyLoadAndRefreshing(): void public function testMorphToManyHasQuery(): void { $client = Client::query()->create(['name' => 'Ashley']); - $client2 = Client::query()->create(['name' => 'John Doe']); + $client2 = Client::query()->create(['name' => 'Halsey']); $client3 = Client::query()->create(['name' => 'John Doe 2']); $label = Label::query()->create(['name' => "I've been digging myself down deeper"]); $label2 = Label::query()->create(['name' => "I won't stop 'til I get where you are"]); $client->labels()->sync([$label->_id, $label2->_id]); + $client2->labels()->sync($label); $this->assertEquals(2, $client->labels->count()); + $this->assertEquals(1, $client2->labels->count()); + + $check = Client::query()->has('labels')->get(); + $this->assertCount(2, $check); - $this->assertEquals(1, Client::query()->has('labels')->count()); + $check = Client::query()->has('labels','>',1)->get(); + $this->assertCount(1, $check); + $this->assertContains($client->_id, $check->pluck('_id')); + + $check = Client::query()->has('labels','<',2)->get(); + $this->assertCount(2, $check); + $this->assertContains($client2->_id, $check->pluck('_id')); + $this->assertContains($client3->_id, $check->pluck('_id')); } public function testMorphedByMany(): void From 800bfac73e23c8846158d36bba108eef09fdbb58 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Sat, 18 Nov 2023 17:07:45 +0330 Subject: [PATCH 70/74] Fix CS; --- src/Helpers/QueriesRelationships.php | 24 +++++++++++++----------- src/Relations/MorphToMany.php | 1 - tests/RelationsTest.php | 17 ++++++++--------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index eff7f40be..24329da90 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -13,13 +13,15 @@ use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Collection; use MongoDB\Laravel\Eloquent\Model; - use MongoDB\Laravel\Relations\MorphToMany; + use function array_count_values; use function array_filter; use function array_keys; use function array_map; use function class_basename; +use function collect; +use function get_class; use function in_array; use function is_array; use function is_string; @@ -115,10 +117,10 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $ $not = ! $not; } - $relations = match (true){ - $relation instanceof MorphToMany => $relation->getInverse() ? - $this->handleMorphedByMany($hasQuery,$relation): - $this->handleMorphToMany($hasQuery,$relation), + $relations = match (true) { + $relation instanceof MorphToMany => $relation->getInverse() ? + $this->handleMorphedByMany($hasQuery, $relation) : + $this->handleMorphToMany($hasQuery, $relation), default => $hasQuery->pluck($this->getHasCompareKey($relation)) }; @@ -128,28 +130,28 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $ } /** - * @param Builder $hasQuery + * @param Builder $hasQuery * @param Relation $relation * * @return Collection */ - private function handleMorphToMany($hasQuery,$relation) + private function handleMorphToMany($hasQuery, $relation) { // First we select the parent models that have a relation to our related model, // Then extracts related model's ids from the pivot column - $hasQuery->where($relation->getTable().'.'.$relation->getMorphType(), get_class($relation->getParent())); + $hasQuery->where($relation->getTable() . '.' . $relation->getMorphType(), get_class($relation->getParent())); $relations = $hasQuery->pluck($relation->getTable()); - $relations = $relation->extractIds($relations->flatten(1)->toArray(),$relation->getForeignPivotKeyName()); + $relations = $relation->extractIds($relations->flatten(1)->toArray(), $relation->getForeignPivotKeyName()); return collect($relations); } /** - * @param Builder $hasQuery + * @param Builder $hasQuery * @param Relation $relation * * @return Collection */ - private function handleMorphedByMany($hasQuery,$relation) + private function handleMorphedByMany($hasQuery, $relation) { $hasQuery->whereNotNull($relation->getForeignPivotKeyName()); return $hasQuery->pluck($relation->getForeignPivotKeyName())->flatten(1); diff --git a/src/Relations/MorphToMany.php b/src/Relations/MorphToMany.php index 7969ce302..9c9576d90 100644 --- a/src/Relations/MorphToMany.php +++ b/src/Relations/MorphToMany.php @@ -23,7 +23,6 @@ class MorphToMany extends EloquentMorphToMany { - /** @inheritdoc */ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) { diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 12e6f7af2..57c3fddee 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -5,7 +5,6 @@ namespace MongoDB\Laravel\Tests; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\DB; use Mockery; use MongoDB\BSON\ObjectId; use MongoDB\Laravel\Tests\Models\Address; @@ -695,11 +694,11 @@ public function testMorphToManyHasQuery(): void $check = Client::query()->has('labels')->get(); $this->assertCount(2, $check); - $check = Client::query()->has('labels','>',1)->get(); + $check = Client::query()->has('labels', '>', 1)->get(); $this->assertCount(1, $check); $this->assertContains($client->_id, $check->pluck('_id')); - $check = Client::query()->has('labels','<',2)->get(); + $check = Client::query()->has('labels', '<', 2)->get(); $this->assertCount(2, $check); $this->assertContains($client2->_id, $check->pluck('_id')); $this->assertContains($client3->_id, $check->pluck('_id')); @@ -916,9 +915,9 @@ public function testMorphedByManyHasQuery(): void $label = Label::query()->create(['name' => "My star's back shining bright, I just polished it"]); $label2 = Label::query()->create(['name' => "Somethin' in my spirit woke back up like I just sat up"]); - $label3 = Label::query()->create(['name' => "How can I beam when you blocking my light?"]); + $label3 = Label::query()->create(['name' => 'How can I beam when you blocking my light?']); - $label->clients()->sync(new Collection([$client1,$client2])); + $label->clients()->sync(new Collection([$client1, $client2])); $label2->clients()->sync($client1); $label3->users()->sync($user); @@ -926,16 +925,16 @@ public function testMorphedByManyHasQuery(): void $check = Label::query()->has('clients')->get(); $this->assertCount(2, $check); - $this->assertContains($label->_id,$check->pluck('_id')); - $this->assertContains($label2->_id,$check->pluck('_id')); + $this->assertContains($label->_id, $check->pluck('_id')); + $this->assertContains($label2->_id, $check->pluck('_id')); $check = Label::query()->has('users')->get(); $this->assertCount(1, $check); - $this->assertContains($label3->_id,$check->pluck('_id')); + $this->assertContains($label3->_id, $check->pluck('_id')); $check = Label::query()->has('clients', '>', 1)->get(); $this->assertCount(1, $check); - $this->assertContains($label->_id,$check->pluck('_id')); + $this->assertContains($label->_id, $check->pluck('_id')); } public function testHasManyHas(): void From 51b9e2a3ef69c3493beb9da5d32535e7bb4b4ce3 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi <39920372+hans-thomas@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:02:33 +0330 Subject: [PATCH 71/74] Parent morphToMany call with a minuscule "m"; Co-authored-by: Andreas Braun --- src/Eloquent/HybridRelations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Eloquent/HybridRelations.php b/src/Eloquent/HybridRelations.php index 859c70eb1..9551a6c43 100644 --- a/src/Eloquent/HybridRelations.php +++ b/src/Eloquent/HybridRelations.php @@ -368,7 +368,7 @@ public function morphToMany( // Check if it is a relation with an original model. if (! is_subclass_of($related, Model::class)) { - return parent::MorphToMany( + return parent::morphToMany( $related, $name, $table, From cc5bbcffb5854e672cf67f944c74620973902688 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi <39920372+hans-thomas@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:01:26 +0330 Subject: [PATCH 72/74] Add an empty line before return in handleMorphToMany method; MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Tamarelle --- src/Helpers/QueriesRelationships.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index 24329da90..fb4446b68 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -142,6 +142,7 @@ private function handleMorphToMany($hasQuery, $relation) $hasQuery->where($relation->getTable() . '.' . $relation->getMorphType(), get_class($relation->getParent())); $relations = $hasQuery->pluck($relation->getTable()); $relations = $relation->extractIds($relations->flatten(1)->toArray(), $relation->getForeignPivotKeyName()); + return collect($relations); } From 92e6389bb8decbac673e17ee8a606a2a78887dd9 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi <39920372+hans-thomas@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:03:02 +0330 Subject: [PATCH 73/74] Add an empty line before return in handleMorphedByMany method; MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Tamarelle --- src/Helpers/QueriesRelationships.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Helpers/QueriesRelationships.php b/src/Helpers/QueriesRelationships.php index fb4446b68..b1234124b 100644 --- a/src/Helpers/QueriesRelationships.php +++ b/src/Helpers/QueriesRelationships.php @@ -155,6 +155,7 @@ private function handleMorphToMany($hasQuery, $relation) private function handleMorphedByMany($hasQuery, $relation) { $hasQuery->whereNotNull($relation->getForeignPivotKeyName()); + return $hasQuery->pluck($relation->getForeignPivotKeyName())->flatten(1); } From 9198df9daa38cc3c8efc570c1b42597fae6949d9 Mon Sep 17 00:00:00 2001 From: Mohammad Mortazavi Date: Wed, 22 Nov 2023 14:09:38 +0330 Subject: [PATCH 74/74] Uncomment the checks of testHasManyHas; --- tests/RelationsTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index 57c3fddee..652f3d7bf 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -947,10 +947,10 @@ public function testHasManyHas(): void User::create(['name' => 'Anonymous author']); Book::create(['title' => 'Anonymous book', 'rating' => 1]); -// $authors = User::has('books')->get(); -// $this->assertCount(2, $authors); -// $this->assertEquals('George R. R. Martin', $authors[0]->name); -// $this->assertEquals('John Doe', $authors[1]->name); + $authors = User::has('books')->get(); + $this->assertCount(2, $authors); + $this->assertEquals('George R. R. Martin', $authors[0]->name); + $this->assertEquals('John Doe', $authors[1]->name); $authors = User::has('books', '>', 1)->get(); $this->assertCount(1, $authors);