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
Bug
introduced
by
![]() |
|||||||||
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
![]() |
|||||||||
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
![]() |
|||||||||
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
![]() |
|||||||||
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
![]() |
|||||||||
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
![]() |
|||||||||
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
![]() |
|||||||||
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
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. ![]() 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
![]() |
|||||||||
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
![]() 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
![]() |
|||||||||
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 |