Issues (115)

src/Services/RelationshipService.php (11 issues)

Labels
Severity
1
<?php
2
3
namespace VGirol\JsonApi\Services;
4
5
use Illuminate\Database\Eloquent\Collection;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\BelongsTo;
8
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
9
use Illuminate\Database\Eloquent\Relations\HasMany;
10
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
11
use Illuminate\Database\Eloquent\Relations\HasOne;
12
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
13
use Illuminate\Database\Eloquent\Relations\MorphMany;
14
use Illuminate\Database\Eloquent\Relations\MorphOne;
15
use Illuminate\Database\Eloquent\Relations\MorphTo;
16
use Illuminate\Database\Eloquent\Relations\MorphToMany;
17
use Illuminate\Database\Eloquent\Relations\Relation;
18
use Illuminate\Support\Arr;
19
use Illuminate\Support\Collection as SupportCollection;
20
use VGirol\JsonApi\Exceptions\JsonApi403Exception;
21
use VGirol\JsonApi\Exceptions\JsonApi404Exception;
22
use VGirol\JsonApi\Exceptions\JsonApi500Exception;
23
use VGirol\JsonApi\Exceptions\JsonApiException;
24
use VGirol\JsonApi\Messages\Messages;
25
use VGirol\JsonApi\Model\Related;
26
use VGirol\JsonApiConstant\Members;
27
28
class RelationshipService
29
{
30
    public const ERROR_BAD_TYPE =
31
        'The parameter $related must be a VGirol\JsonApi\Model\Related instance.';
32
    public const ERROR_NOT_A_RELATION =
33
        'The parameter $relation must be a Illuminate\Database\Eloquent\Relations\Relation instance.';
34
35
    /**
36
     * Undocumented variable
37
     *
38
     * @var FetchService
39
     */
40
    private $fetch;
41
42
    /**
43
     * Class constructor
44
     *
45
     * @param FetchService $fetchService
46
     *
47
     * @return void
48
     */
49
    public function __construct(FetchService $fetchService)
50
    {
51
        $this->fetch = $fetchService;
52
    }
53
54
    /**
55
     * Save all resource's relationships
56
     *
57
     * @param array $data
58
     * @param Model $parent
59
     *
60
     * @return void
61
     * @throws JsonApi403Exception
62
     * @throws JsonApi404Exception
63
     * @throws JsonApi500Exception
64
     */
65
    public function saveAll($data, $parent): void
66
    {
67
        $this->dispatchFromRequest($data, $parent, 'create');
68
    }
69
70
    /**
71
     * Save all resource's relationships
72
     *
73
     * @param array $data
74
     * @param Model $model
75
     *
76
     * @return void
77
     * @throws JsonApi403Exception
78
     * @throws JsonApi404Exception
79
     * @throws JsonApi500Exception
80
     */
81
    public function updateAll($data, $parent): void
82
    {
83
        $this->dispatchFromRequest($data, $parent, 'update');
84
    }
85
86
    /**
87
     * Undocumented function
88
     *
89
     * @param Relation               $relation
90
     * @param Collection|Model|array $related  If $related is an array, it comes from the json content of the request
91
     *
92
     * @return void
93
     * @throws JsonApi500Exception
94
     */
95
    public function create($relation, $related): void
96
    {
97
        $this->dispatch($relation, 'create', $related);
98
    }
99
100
    /**
101
     * Undocumented function
102
     *
103
     * @param Relation               $relation
104
     * @param Collection|Model|array $related  If $related is an array, it comes from the json content of the request
105
     *
106
     * @return void
107
     * @throws JsonApi500Exception
108
     */
109
    public function update($relation, $related): void
110
    {
111
        if ($relation->isToMany() && !config('jsonapi.relationshipFullReplacementIsAllowed')) {
112
            throw new JsonApi403Exception(Messages::RELATIONSHIP_FULL_REPLACEMENT);
113
        }
114
115
        $this->dispatch($relation, 'update', $related);
116
    }
117
118
    /**
119
     * Undocumented function
120
     *
121
     * @param Relation $relation
122
     *
123
     * @return void
124
     */
125
    public function clear($relation): void
126
    {
127
        $this->dispatch($relation, 'clear');
128
    }
129
130
    /**
131
     * Undocumented function
132
     *
133
     * @param Relation               $relation
134
     * @param Collection|Model|array $related  If $related is an array, it comes from the json content of the request
135
     *
136
     * @return void
137
     */
138
    public function remove($relation, $related): void
139
    {
140
        $this->dispatch($relation, 'remove', $related);
141
    }
142
143
    /**
144
     * Undocumented function
145
     *
146
     * @param Relation                    $relation
147
     * @param string                      $action
148
     * @param Collection|Model|array|null $related
149
     *
150
     * @return void
151
     * @throws JsonApi500Exception
152
     * @throws JsonApiException
153
     */
154
    private function dispatch($relation, string $action, $related = null): void
155
    {
156
        if (!is_a($relation, Relation::class)) {
157
            throw new JsonApi500Exception(self::ERROR_NOT_A_RELATION);
158
        }
159
160
        $related = $this->fetch->extractRelated($related);
161
162
        if (($action != 'clear') && !$relation->isToMany() && !is_a($related, Related::class)) {
163
            throw new JsonApiException(self::ERROR_BAD_TYPE);
164
        }
165
166
        $method = $this->getInternalMethod($relation, $action);
167
        $this->{$method}($relation, $related);
168
    }
169
170
    /**
171
     * Save all resource's relationships
172
     *
173
     * @param array  $data
174
     * @param Model  $model
175
     * @param string $fn
176
     *
177
     * @return void
178
     * @throws JsonApi403Exception
179
     * @throws JsonApi404Exception
180
     * @throws JsonApi500Exception
181
     */
182
    private function dispatchFromRequest($data, $parent, string $fn): void
183
    {
184
        // Looks for relationships
185
        $relationships = Arr::get($data, Members::RELATIONSHIPS, null);
186
187
        // If no relationships, returns
188
        if (is_null($relationships)) {
189
            return;
190
        }
191
192
        // Iterates through relationships
193
        foreach ($relationships as $name => $values) {
194
            $relation = $this->fetch->getRelationFromModel($parent, $name);
195
            $this->{$fn}($relation, $values[Members::DATA]);
196
        }
197
    }
198
199
    /**
200
     * Undocumented function
201
     *
202
     * @param Relation $relation
203
     * @param Related  $related
204
     *
205
     * @return void
206
     */
207
    private function createHasOne($relation, $related): void
208
    {
209
        $relation->save($related->model);
0 ignored issues
show
The method save() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloq...\Relations\HasOneOrMany or Illuminate\Database\Eloq...Relations\BelongsToMany or Illuminate\Database\Eloquent\Relations\MorphTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
        $relation->/** @scrutinizer ignore-call */ 
210
                   save($related->model);
Loading history...
210
    }
211
212
    /**
213
     * Undocumented function
214
     *
215
     * @param Relation $relation
216
     * @param Related  $related
217
     *
218
     * @return void
219
     */
220
    private function updateHasOne($relation, $related): void
221
    {
222
        // Detach old one ...
223
        $this->removeHasOne($relation);
224
225
        // ... and attach new one
226
        $this->createHasOne($relation, $related);
227
    }
228
229
    /**
230
     * Undocumented function
231
     *
232
     * @param Relation $relation
233
     *
234
     * @return void
235
     */
236
    private function clearHasOne($relation): void
237
    {
238
        $this->removeHasOne($relation);
239
    }
240
241
    /**
242
     * Undocumented function
243
     *
244
     * @param Relation     $relation
245
     * @param Related|null $related
246
     *
247
     * @return void
248
     */
249
    private function removeHasOne($relation, $related = null): void
250
    {
251
        $old = $relation->getResults();
0 ignored issues
show
The method getResults() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of said class. However, the method does not exist in Illuminate\Database\Eloq...\Relations\HasOneOrMany or Illuminate\Database\Eloq...elations\MorphOneOrMany. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

251
        /** @scrutinizer ignore-call */ 
252
        $old = $relation->getResults();
Loading history...
252
        if ($old === null) {
253
            return;
254
        }
255
256
        if (($related !== null) && $old->isNot($related->model)) {
257
            return;
258
        }
259
260
        $old->update([
261
            $relation->getParent()->getKeyName() => null
0 ignored issues
show
The method getParent() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloquent\Relations\MorphTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

261
            $relation->/** @scrutinizer ignore-call */ 
262
                       getParent()->getKeyName() => null
Loading history...
262
        ]);
263
    }
264
265
    /**
266
     * Undocumented function
267
     *
268
     * @param Relation $relation
269
     * @param Related  $related
270
     *
271
     * @return void
272
     */
273
    private function createBelongsTo($relation, $related): void
274
    {
275
        $this->updateBelongsTo($relation, $related);
276
    }
277
278
    /**
279
     * Undocumented function
280
     *
281
     * @param Relation $relation
282
     * @param Related  $related
283
     *
284
     * @return void
285
     */
286
    private function updateBelongsTo($relation, $related): void
287
    {
288
        $relation->associate($related->model);
0 ignored issues
show
The method associate() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloquent\Relations\BelongsTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

288
        $relation->/** @scrutinizer ignore-call */ 
289
                   associate($related->model);
Loading history...
289
        $relation->getParent()->save();
290
    }
291
292
    /**
293
     * Undocumented function
294
     *
295
     * @param Relation $relation
296
     *
297
     * @return void
298
     */
299
    private function clearBelongsTo($relation): void
300
    {
301
        $this->removeBelongsTo($relation);
302
    }
303
304
    /**
305
     * Undocumented function
306
     *
307
     * @param Relation     $relation
308
     * @param Related|null $related
309
     *
310
     * @return void
311
     */
312
    private function removeBelongsTo($relation, $related = null): void
313
    {
314
        $old = $relation->getResults();
315
        if ($old === null) {
316
            return;
317
        }
318
319
        if (($related !== null) && $old->isNot($related->model)) {
320
            return;
321
        }
322
323
        $relation->dissociate();
0 ignored issues
show
The method dissociate() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloquent\Relations\BelongsTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

323
        $relation->/** @scrutinizer ignore-call */ 
324
                   dissociate();
Loading history...
324
        $relation->getParent()->save();
325
    }
326
327
    /**
328
     * Undocumented function
329
     *
330
     * @param Relation           $relation
331
     * @param Related|Collection $related
332
     *
333
     * @return void
334
     */
335
    private function createBelongsToMany($relation, $related): void
336
    {
337
        $this->updateBelongsToMany($relation, $related);
338
    }
339
340
    /**
341
     * Undocumented function
342
     *
343
     * @param Relation           $relation
344
     * @param Related|Collection $related
345
     *
346
     * @return void
347
     */
348
    private function updateBelongsToMany($relation, $related): void
349
    {
350
        $detaching = true;
351
        $relcollection = is_a($related, SupportCollection::class) ? $related : collect([$related]);
352
353
        $relatedIds = $relcollection->pluck(
354
            'metaAttributes',
355
            'model.' . $relcollection->first()->model->getKeyName()
356
        )->toArray();
357
        // $relatedModel = $relation->getRelated();
358
        // if ($relatedModel->whereIn($relatedModel->getKeyName(), $relatedIds)->count() == 0) {
359
        //     throw new JsonApi404Exception(sprintf(Messages::UPDATING_REQUEST_RELATED_NOT_FOUND, $name));
360
        // }
361
362
        $relation->sync($relatedIds, $detaching);
0 ignored issues
show
The method sync() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloq...Relations\BelongsToMany or Illuminate\Database\Eloquent\Relations\MorphTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

362
        $relation->/** @scrutinizer ignore-call */ 
363
                   sync($relatedIds, $detaching);
Loading history...
363
    }
364
365
    /**
366
     * Undocumented function
367
     *
368
     * @param Relation $relation
369
     *
370
     * @return void
371
     */
372
    private function clearBelongsToMany($relation): void
373
    {
374
        $relation->sync([], true);
375
    }
376
377
    /**
378
     * Undocumented function
379
     *
380
     * @param Relation           $relation
381
     * @param Related|Collection $related
382
     *
383
     * @return void
384
     */
385
    private function removeBelongsToMany($relation, $related): void
386
    {
387
        $related = is_a($related, Related::class) ? collect([$related]) : $related;
388
389
        $relation->detach(
0 ignored issues
show
The method detach() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloq...Relations\BelongsToMany or Illuminate\Database\Eloquent\Relations\MorphTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

389
        $relation->/** @scrutinizer ignore-call */ 
390
                   detach(
Loading history...
390
            $related->pluck(
391
                'model.' . $related->first()->model->getKeyName()
392
            )->toArray()
393
        );
394
    }
395
396
    /**
397
     * Undocumented function
398
     *
399
     * @param Relation           $relation
400
     * @param Related|Collection $related
401
     *
402
     * @return void
403
     */
404
    private function updateHasMany($relation, $related): void
405
    {
406
        $this->clearHasMany($relation);
407
        $this->createHasMany($relation, $related);
408
    }
409
410
    /**
411
     * Undocumented function
412
     *
413
     * @param Relation           $relation
414
     * @param Related|Collection $related
415
     *
416
     * @return void
417
     */
418
    private function createHasMany($relation, $related): void
419
    {
420
        $related = is_iterable($related) ? $related : collect([$related]);
421
        $relation->saveMany($related->pluck('model'));
0 ignored issues
show
The method pluck() does not exist on VGirol\JsonApi\Model\Related. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

421
        $relation->saveMany($related->/** @scrutinizer ignore-call */ pluck('model'));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
The method saveMany() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloq...\Relations\HasOneOrMany or Illuminate\Database\Eloq...Relations\BelongsToMany or Illuminate\Database\Eloquent\Relations\MorphTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

421
        $relation->/** @scrutinizer ignore-call */ 
422
                   saveMany($related->pluck('model'));
Loading history...
422
    }
423
424
    /**
425
     * Undocumented function
426
     *
427
     * @param Relation $relation
428
     *
429
     * @return void
430
     */
431
    private function clearHasMany($relation): void
432
    {
433
        $relation->update([$relation->getForeignKeyName() => null]);
0 ignored issues
show
The method update() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of Illuminate\Database\Eloquent\Relations\Relation such as Illuminate\Database\Eloquent\Relations\MorphTo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

433
        $relation->/** @scrutinizer ignore-call */ 
434
                   update([$relation->getForeignKeyName() => null]);
Loading history...
The method getForeignKeyName() does not exist on Illuminate\Database\Eloquent\Relations\Relation. It seems like you code against a sub-type of said class. However, the method does not exist in Illuminate\Database\Eloq...Relations\BelongsToMany or Illuminate\Database\Eloquent\Relations\MorphToMany. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

433
        $relation->update([$relation->/** @scrutinizer ignore-call */ getForeignKeyName() => null]);
Loading history...
434
    }
435
436
    /**
437
     * Undocumented function
438
     *
439
     * @param Relation $relation
440
     * @param Related|Collection $related
441
     *
442
     * @return void
443
     */
444
    private function removeHasMany($relation, $related): void
445
    {
446
        $related = is_a($related, Related::class) ? collect([$related]) : $related;
447
448
        foreach ($related as $item) {
449
            $item->model->setAttribute($relation->getForeignKeyName(), null);
450
            $item->model->save();
451
        };
452
    }
453
454
    /**
455
     * Undocumented function
456
     *
457
     * @param Relation $relation
458
     * @param string   $action
459
     *
460
     * @return string
461
     */
462
    private function getInternalMethod($relation, string $action): string
463
    {
464
        $class = get_class($relation);
465
466
        return $action . substr($class, strrpos($class, '\\') + 1);
467
    }
468
}
469