Skip to content

Commit 8eaad01

Browse files
ithuismedspec
authored andcommitted
add MorphToMany
attach MorphToMany working detaching works sync working cleanup
1 parent 849cb1f commit 8eaad01

File tree

6 files changed

+447
-8
lines changed

6 files changed

+447
-8
lines changed

src/Eloquent/HybridRelations.php

Lines changed: 141 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use MongoDB\Laravel\Relations\HasOne;
1616
use MongoDB\Laravel\Relations\MorphMany;
1717
use MongoDB\Laravel\Relations\MorphTo;
18+
use MongoDB\Laravel\Relations\MorphToMany;
19+
1820

1921
use function debug_backtrace;
2022
use function is_subclass_of;
@@ -41,7 +43,7 @@ trait HybridRelations
4143
public function hasOne($related, $foreignKey = null, $localKey = null)
4244
{
4345
// Check if it is a relation with an original model.
44-
if (! is_subclass_of($related, MongoDBModel::class)) {
46+
if (!is_subclass_of($related, MongoDBModel::class)) {
4547
return parent::hasOne($related, $foreignKey, $localKey);
4648
}
4749

@@ -70,7 +72,7 @@ public function hasOne($related, $foreignKey = null, $localKey = null)
7072
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
7173
{
7274
// Check if it is a relation with an original model.
73-
if (! is_subclass_of($related, MongoDBModel::class)) {
75+
if (!is_subclass_of($related, MongoDBModel::class)) {
7476
return parent::morphOne($related, $name, $type, $id, $localKey);
7577
}
7678

@@ -97,7 +99,7 @@ public function morphOne($related, $name, $type = null, $id = null, $localKey =
9799
public function hasMany($related, $foreignKey = null, $localKey = null)
98100
{
99101
// Check if it is a relation with an original model.
100-
if (! is_subclass_of($related, MongoDBModel::class)) {
102+
if (!is_subclass_of($related, MongoDBModel::class)) {
101103
return parent::hasMany($related, $foreignKey, $localKey);
102104
}
103105

@@ -126,7 +128,7 @@ public function hasMany($related, $foreignKey = null, $localKey = null)
126128
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
127129
{
128130
// Check if it is a relation with an original model.
129-
if (! is_subclass_of($related, MongoDBModel::class)) {
131+
if (!is_subclass_of($related, MongoDBModel::class)) {
130132
return parent::morphMany($related, $name, $type, $id, $localKey);
131133
}
132134

@@ -166,7 +168,7 @@ public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relat
166168
}
167169

168170
// Check if it is a relation with an original model.
169-
if (! is_subclass_of($related, MongoDBModel::class)) {
171+
if (!is_subclass_of($related, MongoDBModel::class)) {
170172
return parent::belongsTo($related, $foreignKey, $ownerKey, $relation);
171173
}
172174

@@ -278,7 +280,7 @@ public function belongsToMany(
278280
}
279281

280282
// Check if it is a relation with an original model.
281-
if (! is_subclass_of($related, MongoDBModel::class)) {
283+
if (!is_subclass_of($related, MongoDBModel::class)) {
282284
return parent::belongsToMany(
283285
$related,
284286
$collection,
@@ -323,6 +325,139 @@ public function belongsToMany(
323325
);
324326
}
325327

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

src/Relations/MorphToMany.php

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Laravel\Relations;
6+
7+
use Illuminate\Database\Eloquent\Builder;
8+
use Illuminate\Database\Eloquent\Model;
9+
10+
class MorphToMany extends BelongsToMany
11+
{
12+
protected $morphType;
13+
14+
protected $morphClass;
15+
16+
/**
17+
* Create a new morph to many relationship instance.
18+
*
19+
* @param Builder $query
20+
* @param Model $parent
21+
* @param string $name
22+
* @param string $table
23+
* @param string $foreignPivotKey
24+
* @param string $relatedPivotKey
25+
* @param string $parentKey
26+
* @param string $relatedKey
27+
* @param string|null $relationName
28+
* @param bool $inverse
29+
*
30+
* @return void
31+
*/
32+
public function __construct(
33+
Builder $query,
34+
Model $parent,
35+
$name,
36+
$table,
37+
$foreignPivotKey,
38+
$relatedPivotKey,
39+
$parentKey,
40+
$relatedKey,
41+
$relationName = null,
42+
protected $inverse = false,
43+
) {
44+
$this->morphType = $name . '_type';
45+
$this->morphClass = $inverse ? $query->getModel()->getMorphClass() : $parent->getMorphClass();
46+
47+
parent::__construct(
48+
$query,
49+
$parent,
50+
$table,
51+
$foreignPivotKey,
52+
$relatedPivotKey,
53+
$parentKey,
54+
$relatedKey,
55+
$relationName,
56+
);
57+
}
58+
59+
/**
60+
* Attach a model to the parent.
61+
*
62+
* @param mixed $id
63+
* @param array $attributes
64+
* @param bool $touch
65+
*
66+
* @return void
67+
*/
68+
public function attach($id, array $attributes = [], $touch = true)
69+
{
70+
if ($id instanceof Model) {
71+
$model = $id;
72+
73+
$id = $model->getKey();
74+
75+
// Attach the new parent id to the related model.
76+
$model->push($this->table, [
77+
$this->foreignPivotKey => $this->parent->getKey(),
78+
$this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null,
79+
], true);
80+
} else {
81+
if ($id instanceof Collection) {
82+
$id = $id->modelKeys();
83+
}
84+
85+
$query = $this->newRelatedQuery();
86+
87+
$query->whereIn($this->related->getKeyName(), (array) $id);
88+
89+
// Attach the new parent id to the related model.
90+
$query->push($this->table, [
91+
$this->foreignPivotKey => $this->parent->getKey(),
92+
$this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null,
93+
], true);
94+
}
95+
96+
// Attach the new ids to the parent model.
97+
$this->parent->push($this->relatedPivotKey, (array) $id, true);
98+
99+
if ($touch) {
100+
$this->touchIfTouching();
101+
}
102+
}
103+
104+
/** @inheritdoc */
105+
public function detach($ids = [], $touch = true)
106+
{
107+
if ($ids instanceof Model) {
108+
$ids = (array) $ids->getKey();
109+
}
110+
111+
$query = $this->newRelatedQuery();
112+
113+
// If associated IDs were passed to the method we will only delete those
114+
// associations, otherwise all of the association ties will be broken.
115+
// We'll return the numbers of affected rows when we do the deletes.
116+
$ids = (array) $ids;
117+
118+
// Detach all ids from the parent model.
119+
$this->parent->pull($this->relatedPivotKey, $ids);
120+
121+
// Prepare the query to select all related objects.
122+
if (count($ids) > 0) {
123+
$query->whereIn($this->related->getKeyName(), $ids);
124+
}
125+
126+
// Remove the relation to the parent.
127+
// $query->pull($this->foreignPivotKey, $this->foreignPivotKey, $this->parent->getKey());
128+
$query->pull($this->table, [
129+
$this->foreignPivotKey => $this->parent->getKey(),
130+
$this->morphType => $this->parent instanceof Model ? $this->parent->getMorphClass() : null,
131+
]);
132+
133+
if ($touch) {
134+
$this->touchIfTouching();
135+
}
136+
137+
return count($ids);
138+
}
139+
140+
141+
/**
142+
* Get the foreign key "type" name.
143+
*
144+
* @return string
145+
*/
146+
public function getMorphType()
147+
{
148+
return $this->morphType;
149+
}
150+
151+
/**
152+
* Get the class name of the parent model.
153+
*
154+
* @return string
155+
*/
156+
public function getMorphClass()
157+
{
158+
return $this->morphClass;
159+
}
160+
161+
/**
162+
* Get the indicator for a reverse relationship.
163+
*
164+
* @return bool
165+
*/
166+
public function getInverse()
167+
{
168+
return $this->inverse;
169+
}
170+
171+
/**
172+
* Set the where clause for the relation query.
173+
*
174+
* @return $this
175+
*/
176+
protected function setWhere()
177+
{
178+
$foreign = $this->getForeignKey();
179+
180+
if ($this->getInverse()) {
181+
$this->query->where($foreign, '=', $this->parent->getKey());
182+
} else {
183+
$relatedModels = $this->parent->{$this->relatedPivotKey} ?? [];
184+
$this->query->whereIn($this->relatedKey, $relatedModels);
185+
}
186+
187+
return $this;
188+
}
189+
}

0 commit comments

Comments
 (0)