Skip to content

Commit 82ddb83

Browse files
authored
[Feature] Add MorphToMany support (mongodb#2670)
1 parent bcadf52 commit 82ddb83

File tree

7 files changed

+1092
-3
lines changed

7 files changed

+1092
-3
lines changed

src/Eloquent/HybridRelations.php

+124
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
use MongoDB\Laravel\Relations\HasOne;
1616
use MongoDB\Laravel\Relations\MorphMany;
1717
use MongoDB\Laravel\Relations\MorphTo;
18+
use MongoDB\Laravel\Relations\MorphToMany;
1819

20+
use function array_pop;
1921
use function debug_backtrace;
22+
use function implode;
2023
use function is_subclass_of;
24+
use function preg_split;
2125

2226
use const DEBUG_BACKTRACE_IGNORE_ARGS;
27+
use const PREG_SPLIT_DELIM_CAPTURE;
2328

2429
/**
2530
* Cross-database relationships between SQL and MongoDB.
@@ -328,6 +333,125 @@ public function belongsToMany(
328333
);
329334
}
330335

336+
/**
337+
* Define a morph-to-many relationship.
338+
*
339+
* @param string $related
340+
* @param string $name
341+
* @param null $table
342+
* @param null $foreignPivotKey
343+
* @param null $relatedPivotKey
344+
* @param null $parentKey
345+
* @param null $relatedKey
346+
* @param null $relation
347+
* @param bool $inverse
348+
*
349+
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
350+
*/
351+
public function morphToMany(
352+
$related,
353+
$name,
354+
$table = null,
355+
$foreignPivotKey = null,
356+
$relatedPivotKey = null,
357+
$parentKey = null,
358+
$relatedKey = null,
359+
$relation = null,
360+
$inverse = false,
361+
) {
362+
// If no relationship name was passed, we will pull backtraces to get the
363+
// name of the calling function. We will use that function name as the
364+
// title of this relation since that is a great convention to apply.
365+
if ($relation === null) {
366+
$relation = $this->guessBelongsToManyRelation();
367+
}
368+
369+
// Check if it is a relation with an original model.
370+
if (! is_subclass_of($related, Model::class)) {
371+
return parent::morphToMany(
372+
$related,
373+
$name,
374+
$table,
375+
$foreignPivotKey,
376+
$relatedPivotKey,
377+
$parentKey,
378+
$relatedKey,
379+
$relation,
380+
$inverse,
381+
);
382+
}
383+
384+
$instance = new $related();
385+
386+
$foreignPivotKey = $foreignPivotKey ?: $name . '_id';
387+
$relatedPivotKey = $relatedPivotKey ?: Str::plural($instance->getForeignKey());
388+
389+
// Now we're ready to create a new query builder for the related model and
390+
// the relationship instances for this relation. This relation will set
391+
// appropriate query constraints then entirely manage the hydration.
392+
if (! $table) {
393+
$words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
394+
$lastWord = array_pop($words);
395+
$table = implode('', $words) . Str::plural($lastWord);
396+
}
397+
398+
return new MorphToMany(
399+
$instance->newQuery(),
400+
$this,
401+
$name,
402+
$table,
403+
$foreignPivotKey,
404+
$relatedPivotKey,
405+
$parentKey ?: $this->getKeyName(),
406+
$relatedKey ?: $instance->getKeyName(),
407+
$relation,
408+
$inverse,
409+
);
410+
}
411+
412+
/**
413+
* Define a polymorphic, inverse many-to-many relationship.
414+
*
415+
* @param string $related
416+
* @param string $name
417+
* @param null $table
418+
* @param null $foreignPivotKey
419+
* @param null $relatedPivotKey
420+
* @param null $parentKey
421+
* @param null $relatedKey
422+
*
423+
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
424+
*/
425+
public function morphedByMany(
426+
$related,
427+
$name,
428+
$table = null,
429+
$foreignPivotKey = null,
430+
$relatedPivotKey = null,
431+
$parentKey = null,
432+
$relatedKey = null,
433+
$relation = null,
434+
) {
435+
$foreignPivotKey = $foreignPivotKey ?: Str::plural($this->getForeignKey());
436+
437+
// For the inverse of the polymorphic many-to-many relations, we will change
438+
// the way we determine the foreign and other keys, as it is the opposite
439+
// of the morph-to-many method since we're figuring out these inverses.
440+
$relatedPivotKey = $relatedPivotKey ?: $name . '_id';
441+
442+
return $this->morphToMany(
443+
$related,
444+
$name,
445+
$table,
446+
$foreignPivotKey,
447+
$relatedPivotKey,
448+
$parentKey,
449+
$relatedKey,
450+
$relatedKey,
451+
true,
452+
);
453+
}
454+
331455
/** @inheritdoc */
332456
public function newEloquentBuilder($query)
333457
{

src/Helpers/QueriesRelationships.php

+39-1
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
use Illuminate\Database\Eloquent\Relations\Relation;
1414
use Illuminate\Support\Collection;
1515
use MongoDB\Laravel\Eloquent\Model;
16+
use MongoDB\Laravel\Relations\MorphToMany;
1617

1718
use function array_count_values;
1819
use function array_filter;
1920
use function array_keys;
2021
use function array_map;
2122
use function class_basename;
23+
use function collect;
24+
use function get_class;
2225
use function in_array;
2326
use function is_array;
2427
use function is_string;
@@ -114,13 +117,48 @@ public function addHybridHas(Relation $relation, $operator = '>=', $count = 1, $
114117
$not = ! $not;
115118
}
116119

117-
$relations = $hasQuery->pluck($this->getHasCompareKey($relation));
120+
$relations = match (true) {
121+
$relation instanceof MorphToMany => $relation->getInverse() ?
122+
$this->handleMorphedByMany($hasQuery, $relation) :
123+
$this->handleMorphToMany($hasQuery, $relation),
124+
default => $hasQuery->pluck($this->getHasCompareKey($relation))
125+
};
118126

119127
$relatedIds = $this->getConstrainedRelatedIds($relations, $operator, $count);
120128

121129
return $this->whereIn($this->getRelatedConstraintKey($relation), $relatedIds, $boolean, $not);
122130
}
123131

132+
/**
133+
* @param Builder $hasQuery
134+
* @param Relation $relation
135+
*
136+
* @return Collection
137+
*/
138+
private function handleMorphToMany($hasQuery, $relation)
139+
{
140+
// First we select the parent models that have a relation to our related model,
141+
// Then extracts related model's ids from the pivot column
142+
$hasQuery->where($relation->getTable() . '.' . $relation->getMorphType(), get_class($relation->getParent()));
143+
$relations = $hasQuery->pluck($relation->getTable());
144+
$relations = $relation->extractIds($relations->flatten(1)->toArray(), $relation->getForeignPivotKeyName());
145+
146+
return collect($relations);
147+
}
148+
149+
/**
150+
* @param Builder $hasQuery
151+
* @param Relation $relation
152+
*
153+
* @return Collection
154+
*/
155+
private function handleMorphedByMany($hasQuery, $relation)
156+
{
157+
$hasQuery->whereNotNull($relation->getForeignPivotKeyName());
158+
159+
return $hasQuery->pluck($relation->getForeignPivotKeyName())->flatten(1);
160+
}
161+
124162
/** @return string */
125163
protected function getHasCompareKey(Relation $relation)
126164
{

0 commit comments

Comments
 (0)