4
4
5
5
namespace MongoDB \Laravel \Relations ;
6
6
7
+ use BackedEnum ;
7
8
use Illuminate \Database \Eloquent \Builder ;
8
9
use Illuminate \Database \Eloquent \Collection ;
10
+ use \Illuminate \Support \Collection as BaseCollection ;
9
11
use Illuminate \Database \Eloquent \Model ;
10
12
use Illuminate \Database \Eloquent \Relations \BelongsToMany as EloquentBelongsToMany ;
11
13
use Illuminate \Support \Arr ;
12
14
13
15
use function array_diff ;
14
- use function array_keys ;
15
16
use function array_map ;
16
- use function array_merge ;
17
17
use function array_values ;
18
18
use function assert ;
19
19
use function count ;
@@ -107,8 +107,35 @@ public function create(array $attributes = [], array $joining = [], $touch = tru
107
107
return $ instance ;
108
108
}
109
109
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 )
112
139
{
113
140
$ changes = [
114
141
'attached ' => [],
@@ -130,25 +157,95 @@ public function sync($ids, $detaching = true)
130
157
false => $ this ->parent ->{$ this ->relationName } ?: [],
131
158
};
132
159
133
- if ($ current instanceof Collection) {
160
+ // Support Base Collection
161
+ if ($ current instanceof BaseCollection) {
134
162
$ current = $ this ->parseIds ($ current );
135
163
}
136
164
165
+ $ current = Arr::wrap ($ current );
137
166
$ records = $ this ->formatRecordsList ($ ids );
138
167
139
- $ current = Arr:: wrap ( $ current );
168
+ $ detach = array_values ( array_intersect ( $ current, $ records ) );
140
169
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
+ }
142
203
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 ));
146
243
147
244
// Next, we will take the differences of the currents and given IDs and detach
148
245
// all of the entities that exist in the "current" array but are not in the
149
246
// the array of the IDs given to the method which will complete the sync.
150
247
if ($ detaching && count ($ detach ) > 0 ) {
151
- $ this ->detach ($ detach );
248
+ $ this ->detach ($ detach, false );
152
249
153
250
$ changes ['detached ' ] = (array ) array_map (function ($ v ) {
154
251
return is_numeric ($ v ) ? (int ) $ v : (string ) $ v ;
@@ -158,13 +255,18 @@ public function sync($ids, $detaching = true)
158
255
// Now we are finally ready to attach the new records. Note that we'll disable
159
256
// touching until after the entire operation is complete so we don't fire a
160
257
// 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
+ }
165
265
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 ();
168
270
}
169
271
170
272
return $ changes ;
@@ -207,7 +309,7 @@ public function attach($id, array $attributes = [], $touch = true)
207
309
} else {
208
310
$ id = (array ) $ id ;
209
311
}
210
-
312
+
211
313
$ this ->parent ->push ($ this ->relatedPivotKey , $ id , true );
212
314
} else {
213
315
$ instance = new $ this ->related ();
@@ -220,13 +322,16 @@ public function attach($id, array $attributes = [], $touch = true)
220
322
return ;
221
323
}
222
324
223
- $ this ->touchIfTouching ();
325
+ $ this ->parent ->touch ();
326
+ $ this ->newRelatedQuery ()->whereIn ($ this ->relatedKey , (array ) $ id );
224
327
}
225
328
226
329
/** @inheritdoc */
227
330
public function detach ($ ids = [], $ touch = true )
228
331
{
229
- if ($ ids instanceof Model) {
332
+ if ($ ids instanceof Collection) {
333
+ $ ids = $ this ->parseIds ($ ids );
334
+ } elseif ($ ids instanceof Model) {
230
335
$ ids = $ this ->parseIds ($ ids );
231
336
}
232
337
@@ -247,6 +352,7 @@ public function detach($ids = [], $touch = true)
247
352
} else {
248
353
$ value = $ this ->parent ->{$ this ->relationName }
249
354
->filter (fn ($ rel ) => ! in_array ($ rel ->{$ this ->relatedKey }, $ ids ));
355
+
250
356
$ this ->parent ->setRelation ($ this ->relationName , $ value );
251
357
}
252
358
@@ -261,7 +367,8 @@ public function detach($ids = [], $touch = true)
261
367
$ query ->pull ($ this ->foreignPivotKey , $ this ->parent ->{$ this ->parentKey });
262
368
263
369
if ($ touch ) {
264
- $ this ->touchIfTouching ();
370
+ $ this ->parent ->touch ();
371
+ $ query ->touch ();
265
372
}
266
373
267
374
return count ($ ids );
0 commit comments