Skip to content

Commit e86f00b

Browse files
Add sync and toggle relation helper
1 parent a25ef40 commit e86f00b

File tree

1 file changed

+128
-21
lines changed

1 file changed

+128
-21
lines changed

src/Relations/BelongsToMany.php

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44

55
namespace MongoDB\Laravel\Relations;
66

7+
use BackedEnum;
78
use Illuminate\Database\Eloquent\Builder;
89
use Illuminate\Database\Eloquent\Collection;
10+
use \Illuminate\Support\Collection as BaseCollection;
911
use Illuminate\Database\Eloquent\Model;
1012
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany;
1113
use Illuminate\Support\Arr;
1214

1315
use function array_diff;
14-
use function array_keys;
1516
use function array_map;
16-
use function array_merge;
1717
use function array_values;
1818
use function assert;
1919
use function count;
@@ -107,8 +107,35 @@ public function create(array $attributes = [], array $joining = [], $touch = tru
107107
return $instance;
108108
}
109109

110-
/** @inheritdoc */
111-
public function sync($ids, $detaching = true)
110+
/**
111+
* Format the sync / toggle record list so that it is keyed by ID.
112+
*
113+
* @param array $records
114+
* @return array
115+
*/
116+
protected function formatRecordsList($records): array
117+
{
118+
//Support for an object type id.
119+
//Removal of attribute management because there is no pivot table
120+
return collect($records)->map(function ($id) {
121+
if ($id instanceof BackedEnum) {
122+
$id = $id->value;
123+
}
124+
125+
return $id;
126+
})->all();
127+
}
128+
129+
/**
130+
* Toggles a model (or models) from the parent.
131+
*
132+
* Each existing model is detached, and non existing ones are attached.
133+
*
134+
* @param mixed $ids
135+
* @param bool $touch
136+
* @return array
137+
*/
138+
public function toggle($ids, $touch = true)
112139
{
113140
$changes = [
114141
'attached' => [],
@@ -130,25 +157,95 @@ public function sync($ids, $detaching = true)
130157
false => $this->parent->{$this->relationName} ?: [],
131158
};
132159

133-
if ($current instanceof Collection) {
160+
// Support Base Collection
161+
if ($current instanceof BaseCollection) {
134162
$current = $this->parseIds($current);
135163
}
136164

165+
$current = Arr::wrap($current);
137166
$records = $this->formatRecordsList($ids);
138167

139-
$current = Arr::wrap($current);
168+
$detach = array_values(array_intersect($current, $records));
140169

141-
$detach = array_diff($current, array_keys($records));
170+
if (count($detach) > 0) {
171+
$this->detach($detach, false);
172+
173+
$changes['detached'] = (array) array_map(function ($v) {
174+
return is_numeric($v) ? (int) $v : (string) $v;
175+
}, $detach);
176+
}
177+
178+
// Finally, for all of the records which were not "detached", we'll attach the
179+
// records into the intermediate table. Then, we will add those attaches to
180+
// this change list and get ready to return these results to the callers.
181+
$attach = array_values(array_diff($records, $current));
182+
183+
if (count($attach) > 0) {
184+
$this->attach($attach, [], false);
185+
186+
$changes['attached'] = (array) array_map(function ($v) {
187+
return $this->castKey($v);
188+
}, $attach);
189+
}
190+
191+
// Once we have finished attaching or detaching the records, we will see if we
192+
// have done any attaching or detaching, and if we have we will touch these
193+
// relationships if they are configured to touch on any database updates.
194+
if ($touch && (count($changes['attached']) ||
195+
count($changes['detached']))) {
196+
197+
$this->parent->touch();
198+
$this->newRelatedQuery()->whereIn($this->relatedKey, $ids)->touch();
199+
}
200+
201+
return $changes;
202+
}
142203

143-
// We need to make sure we pass a clean array, so that it is not interpreted
144-
// as an associative array.
145-
$detach = array_values($detach);
204+
205+
/**
206+
* Sync the intermediate tables with a list of IDs or collection of models.
207+
*
208+
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
209+
* @param bool $detaching
210+
* @return array
211+
*/
212+
public function sync($ids, $detaching = true)
213+
{
214+
$changes = [
215+
'attached' => [],
216+
'detached' => [],
217+
'updated' => [],
218+
];
219+
220+
if ($ids instanceof Collection) {
221+
$ids = $this->parseIds($ids);
222+
} elseif ($ids instanceof Model) {
223+
$ids = $this->parseIds($ids);
224+
}
225+
226+
// First we need to attach any of the associated models that are not currently
227+
// in this joining table. We'll spin through the given IDs, checking to see
228+
// if they exist in the array of current ones, and if not we will insert.
229+
$current = match ($this->parent instanceof \MongoDB\Laravel\Eloquent\Model) {
230+
true => $this->parent->{$this->relatedPivotKey} ?: [],
231+
false => $this->parent->{$this->relationName} ?: [],
232+
};
233+
234+
// Support Base Collection
235+
if ($current instanceof BaseCollection) {
236+
$current = $this->parseIds($current);
237+
}
238+
239+
$current = Arr::wrap($current);
240+
$records = $this->formatRecordsList($ids);
241+
242+
$detach = array_values(array_diff($current, $records));
146243

147244
// Next, we will take the differences of the currents and given IDs and detach
148245
// all of the entities that exist in the "current" array but are not in the
149246
// the array of the IDs given to the method which will complete the sync.
150247
if ($detaching && count($detach) > 0) {
151-
$this->detach($detach);
248+
$this->detach($detach, false);
152249

153250
$changes['detached'] = (array) array_map(function ($v) {
154251
return is_numeric($v) ? (int) $v : (string) $v;
@@ -158,13 +255,18 @@ public function sync($ids, $detaching = true)
158255
// Now we are finally ready to attach the new records. Note that we'll disable
159256
// touching until after the entire operation is complete so we don't fire a
160257
// ton of touch operations until we are totally done syncing the records.
161-
$changes = array_merge(
162-
$changes,
163-
$this->attachNew($records, $current, false),
164-
);
258+
foreach ($records as $id) {
259+
// Only non strict check if exist no update s possible beacause no attributtes
260+
if (!in_array($id, $current)) {
261+
$this->attach($id, [], false);
262+
$changes['attached'][] = $this->castKey($id);
263+
}
264+
}
165265

166-
if (count($changes['attached']) || count($changes['updated'])) {
167-
$this->touchIfTouching();
266+
if ((count($changes['attached']) || count($changes['detached']))) {
267+
$touches = array_merge($detach, $records);
268+
$this->parent->touch();
269+
$this->newRelatedQuery()->whereIn($this->relatedKey, $touches)->touch();
168270
}
169271

170272
return $changes;
@@ -207,7 +309,7 @@ public function attach($id, array $attributes = [], $touch = true)
207309
} else {
208310
$id = (array) $id;
209311
}
210-
312+
211313
$this->parent->push($this->relatedPivotKey, $id, true);
212314
} else {
213315
$instance = new $this->related();
@@ -220,13 +322,16 @@ public function attach($id, array $attributes = [], $touch = true)
220322
return;
221323
}
222324

223-
$this->touchIfTouching();
325+
$this->parent->touch();
326+
$this->newRelatedQuery()->whereIn($this->relatedKey, (array) $id);
224327
}
225328

226329
/** @inheritdoc */
227330
public function detach($ids = [], $touch = true)
228331
{
229-
if ($ids instanceof Model) {
332+
if ($ids instanceof Collection) {
333+
$ids = $this->parseIds($ids);
334+
} elseif ($ids instanceof Model) {
230335
$ids = $this->parseIds($ids);
231336
}
232337

@@ -247,6 +352,7 @@ public function detach($ids = [], $touch = true)
247352
} else {
248353
$value = $this->parent->{$this->relationName}
249354
->filter(fn ($rel) => ! in_array($rel->{$this->relatedKey}, $ids));
355+
250356
$this->parent->setRelation($this->relationName, $value);
251357
}
252358

@@ -261,7 +367,8 @@ public function detach($ids = [], $touch = true)
261367
$query->pull($this->foreignPivotKey, $this->parent->{$this->parentKey});
262368

263369
if ($touch) {
264-
$this->touchIfTouching();
370+
$this->parent->touch();
371+
$query->touch();
265372
}
266373

267374
return count($ids);

0 commit comments

Comments
 (0)