Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add MorphToMany support #2670

Merged
merged 81 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
8eaad01
add MorphToMany
ithuis Oct 12, 2023
acfdb19
requested code cleanup
Oct 24, 2023
5a55f80
Merge branch '4.1' into morphToMany
ithuis Nov 6, 2023
d929456
WIP
hans-thomas Nov 7, 2023
dd8e701
WIP
hans-thomas Nov 7, 2023
17a3aca
Add tests for MorphToMany and MorphedByMany;
hans-thomas Nov 7, 2023
4e7ad77
Improve DX;
hans-thomas Nov 7, 2023
07afe18
Merge branch '4.1' into 2640-morph-to-many
hans-thomas Nov 7, 2023
f8240e2
fix cs;
hans-thomas Nov 7, 2023
b9ae8ab
Merge branch '4.1' into 2640-morph-to-many
hans-thomas Nov 8, 2023
fd0a9f9
Querying logic in inverse mode updated;
hans-thomas Nov 8, 2023
dbe93d0
Attaching a collection or string id(s) fixed;
hans-thomas Nov 8, 2023
dc22fc9
Add tests for attaching a collection;
hans-thomas Nov 8, 2023
a0cb480
Add tests for attaching multiple string ids;
hans-thomas Nov 8, 2023
9f888ab
WIP
hans-thomas Nov 8, 2023
3d4abab
Fix CS;
hans-thomas Nov 8, 2023
24119d7
Detaching fixed;
hans-thomas Nov 8, 2023
1833c5a
Add tests for detaching;
hans-thomas Nov 8, 2023
ad1c6ae
Add tests for detaching multiple ids;
hans-thomas Nov 9, 2023
26f7ad2
Update MorphToMany.php
hans-thomas Nov 9, 2023
8921127
Add test for syncing a mode;
hans-thomas Nov 9, 2023
dbaab5f
Add test for syncing a collection of models;
hans-thomas Nov 9, 2023
a2c6407
Add test for syncing multiple ids;
hans-thomas Nov 9, 2023
284b8c3
WIP
hans-thomas Nov 9, 2023
92040cc
Fix syncing in inverse mode
hans-thomas Nov 9, 2023
79e5618
Add test for syncing a model;
hans-thomas Nov 9, 2023
5ea4aef
Add test for syncing a collection of models;
hans-thomas Nov 9, 2023
7008008
Add test for syncing multiple id;
hans-thomas Nov 9, 2023
f655f72
Fix CS;
hans-thomas Nov 9, 2023
4932b3b
Fix eager loading relation and refreshing the model before lazy loading;
hans-thomas Nov 9, 2023
c1df501
Use load and refresh in tests;
hans-thomas Nov 9, 2023
d2b8267
Merge branch '4.1' into 2640-morph-to-many
hans-thomas Nov 11, 2023
c8ee4ce
New labelsWithCustomKeys relation defined with custom keys;
hans-thomas Nov 11, 2023
b4fca9d
Add tests for new relation with custom keys;
hans-thomas Nov 11, 2023
7a32c98
Merge branch '4.1' into 2640-morph-to-many
hans-thomas Nov 11, 2023
db782d9
New clientsWithCustomKeys defined with custom keys;
hans-thomas Nov 11, 2023
85c0159
Add test for clientsWithCustomKeys relation;
hans-thomas Nov 11, 2023
31dbf5e
Add test for load and refreshing an instance with MorphToMany relation;
hans-thomas Nov 11, 2023
d54f46c
Add test for load and refreshing an instance with MorphedByMany relat…
hans-thomas Nov 11, 2023
4026890
Fix CS;
hans-thomas Nov 11, 2023
5307e19
Merge branch '2640-morph-to-many' of github.com:hans-thomas/laravel-m…
hans-thomas Nov 11, 2023
9e21445
testMorphedByManyLoadAndRefreshing test updated;
hans-thomas Nov 11, 2023
0872237
Comments updated;
hans-thomas Nov 11, 2023
77bb847
Fix CS;
hans-thomas Nov 11, 2023
3f3db65
WIP
hans-thomas Nov 12, 2023
1903bb4
Fix pushing relation data to the instance;
hans-thomas Nov 12, 2023
3888115
Fix CS;
hans-thomas Nov 12, 2023
bf990a5
Update RelationsTest.php
hans-thomas Nov 12, 2023
9e41f46
Fix the error;
hans-thomas Nov 12, 2023
91e0556
Rerun the pipeline;
hans-thomas Nov 12, 2023
f8c322a
Create extractIds method;
hans-thomas Nov 12, 2023
2f83d02
Fix CS;
hans-thomas Nov 12, 2023
e411071
Update MorphToMany.php
hans-thomas Nov 12, 2023
c097392
Detach multiple ids at once;
hans-thomas Nov 12, 2023
711ed0f
Remove meaningless comments;
hans-thomas Nov 13, 2023
b785d93
Pluralize using Str facade;
hans-thomas Nov 13, 2023
c443ca6
Remove guessBelongsToManyRelation method;
hans-thomas Nov 16, 2023
f6b5112
Remove getSelectColumns method;
hans-thomas Nov 16, 2023
0674ad6
Remove getQualifiedForeignPivotKeyName method;
hans-thomas Nov 16, 2023
874a7d3
Remove getForeignKey method;
hans-thomas Nov 16, 2023
add4f1c
formatRecordsList replaced with deprecated formatSyncList method;
hans-thomas Nov 16, 2023
4e2766e
Remove useless condition in extractIds method;
hans-thomas Nov 16, 2023
45a0c7a
Fix CS;
hans-thomas Nov 16, 2023
5d229eb
extractIds method returns unique ids;
hans-thomas Nov 18, 2023
b56b90e
getHasCompareKey updated;
hans-thomas Nov 18, 2023
ca1cc62
Handling MorphToMany has query;
hans-thomas Nov 18, 2023
5d77236
Add tests for handling has query in MorphToMany relationship;
hans-thomas Nov 18, 2023
9618b84
Handling MorphedByMany has query;
hans-thomas Nov 18, 2023
6d6a99e
getHasCompareKey removed;
hans-thomas Nov 18, 2023
9f53b26
Add test for MorphedByMany has query;
hans-thomas Nov 18, 2023
c4ca146
Fix handling custom MorphedByMany has query;
hans-thomas Nov 18, 2023
989b263
Add test for MorphByMany has query;
hans-thomas Nov 18, 2023
1817c94
Revert "extractIds method returns unique ids;"
hans-thomas Nov 18, 2023
fc53e7d
Make extractIds method public;
hans-thomas Nov 18, 2023
d9dedf6
Add test for handling custom MorphToMany has query;
hans-thomas Nov 18, 2023
800bfac
Fix CS;
hans-thomas Nov 18, 2023
51b9e2a
Parent morphToMany call with a minuscule "m";
hans-thomas Nov 20, 2023
cc5bbcf
Add an empty line before return in handleMorphToMany method;
hans-thomas Nov 22, 2023
92e6389
Add an empty line before return in handleMorphedByMany method;
hans-thomas Nov 22, 2023
9198df9
Uncomment the checks of testHasManyHas;
hans-thomas Nov 22, 2023
44c2517
Merge branch '4.1' into 2640-morph-to-many
GromNaN Nov 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/Eloquent/HybridRelations.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
use MongoDB\Laravel\Relations\HasOne;
use MongoDB\Laravel\Relations\MorphMany;
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 preg_split;

use const DEBUG_BACKTRACE_IGNORE_ARGS;
use const PREG_SPLIT_DELIM_CAPTURE;

/**
* Cross-database relationships between SQL and MongoDB.
Expand Down Expand Up @@ -328,6 +333,125 @@ public function belongsToMany(
);
}

/**
* Define a morph-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 null $relation
* @param bool $inverse
*
* @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,
) {
// 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, Model::class)) {
return parent::morphToMany(
$related,
$name,
$table,
$foreignPivotKey,
$relatedPivotKey,
$parentKey,
$relatedKey,
$relation,
$inverse,
);
}

$instance = new $related();

$foreignPivotKey = $foreignPivotKey ?: $name . '_id';
$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
// 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);
}

return new MorphToMany(
$instance->newQuery(),
$this,
$name,
$table,
$foreignPivotKey,
$relatedPivotKey,
$parentKey ?: $this->getKeyName(),
$relatedKey ?: $instance->getKeyName(),
$relation,
$inverse,
);
GromNaN marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function morphedByMany(
$related,
$name,
$table = null,
$foreignPivotKey = null,
$relatedPivotKey = null,
$parentKey = null,
$relatedKey = null,
$relation = null,
) {
$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
// of the morph-to-many method since we're figuring out these inverses.
$relatedPivotKey = $relatedPivotKey ?: $name . '_id';

return $this->morphToMany(
$related,
$name,
$table,
$foreignPivotKey,
$relatedPivotKey,
$parentKey,
$relatedKey,
$relatedKey,
true,
);
GromNaN marked this conversation as resolved.
Show resolved Hide resolved
}

/** @inheritdoc */
public function newEloquentBuilder($query)
{
Expand Down
40 changes: 39 additions & 1 deletion src/Helpers/QueriesRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +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;
Expand Down Expand Up @@ -114,13 +117,48 @@ 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))
};
Comment on lines -117 to +125
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a match expression to handle the MorphToMany relations has query.


$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);
hans-thomas marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @param Builder $hasQuery
* @param Relation $relation
*
* @return Collection
*/
private function handleMorphedByMany($hasQuery, $relation)
{
$hasQuery->whereNotNull($relation->getForeignPivotKeyName());

return $hasQuery->pluck($relation->getForeignPivotKeyName())->flatten(1);
hans-thomas marked this conversation as resolved.
Show resolved Hide resolved
}

/** @return string */
protected function getHasCompareKey(Relation $relation)
{
Expand Down
Loading