Passed
Push — master ( 5574d5...24b486 )
by Jasper
04:36
created

Item::getRelationValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 16
ccs 6
cts 7
cp 0.8571
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3.0261
1
<?php
2
3
namespace Swis\JsonApi\Client;
4
5
use Illuminate\Support\Str;
6
use Jenssegers\Model\Model;
7
use Swis\JsonApi\Client\Interfaces\DataInterface;
8
use Swis\JsonApi\Client\Interfaces\ItemInterface;
9
use Swis\JsonApi\Client\Interfaces\ManyRelationInterface;
10
use Swis\JsonApi\Client\Interfaces\OneRelationInterface;
11
use Swis\JsonApi\Client\Relations\HasManyRelation;
12
use Swis\JsonApi\Client\Relations\HasOneRelation;
13
use Swis\JsonApi\Client\Relations\MorphToManyRelation;
14
use Swis\JsonApi\Client\Relations\MorphToRelation;
15
use Swis\JsonApi\Client\Traits\HasLinks;
16
use Swis\JsonApi\Client\Traits\HasMeta;
17
use Swis\JsonApi\Client\Traits\HasType;
18
19
/**
20
 * @property string|null id
21
 */
22
class Item extends Model implements ItemInterface
23
{
24
    use HasLinks;
25
    use HasMeta;
26
    use HasType;
27
28
    /**
29
     * @var string|null
30
     */
31
    protected $id;
32
33
    /**
34
     * Contains the initial values.
35
     *
36
     * @var array
37
     */
38
    protected $initial = [];
39
40
    /**
41
     * @var \Swis\JsonApi\Client\Interfaces\OneRelationInterface[]|\Swis\JsonApi\Client\Interfaces\ManyRelationInterface[]
42
     */
43
    protected $relationships = [];
44
45
    /**
46
     * Available relations need to be explicitly set.
47
     *
48
     * @var array
49
     */
50
    protected $availableRelations = [];
51
52
    /**
53
     * @var array
54
     */
55
    protected $guarded = ['id'];
56
57
    /**
58
     * @return array
59
     */
60 115
    public function toJsonApiArray(): array
61
    {
62
        $data = [
63 115
            'type' => $this->getType(),
64
        ];
65
66 115
        if ($this->hasId()) {
67 70
            $data['id'] = $this->getId();
68
        }
69
70 115
        $attributes = $this->toArray();
71 115
        if (!empty($attributes)) {
72 10
            $data['attributes'] = $attributes;
73
        }
74
75 115
        $relationships = $this->getRelationships();
76 115
        if (!empty($relationships)) {
77 80
            $data['relationships'] = $relationships;
78
        }
79
80 115
        $links = $this->getLinks();
81 115
        if ($links !== null) {
82 5
            $data['links'] = $links->toArray();
83
        }
84
85 115
        $meta = $this->getMeta();
86 115
        if ($meta !== null) {
87 5
            $data['meta'] = $meta->toArray();
88
        }
89
90 115
        return $data;
91
    }
92
93
    /**
94
     * @return bool
95
     */
96 15
    public function isNew(): bool
97
    {
98 15
        return !$this->hasId();
99
    }
100
101
    /**
102
     * @return bool
103
     */
104 140
    public function hasId(): bool
105
    {
106 140
        return isset($this->id);
107
    }
108
109
    /**
110
     * @return string|null
111
     */
112 260
    public function getId(): ? string
113
    {
114 260
        return $this->id;
115
    }
116
117
    /**
118
     * @param string|null $id
119
     *
120
     * @return static
121
     */
122 270
    public function setId(? string $id)
123
    {
124 270
        $this->id = $id;
125
126 270
        return $this;
127
    }
128
129
    /**
130
     * @return array
131
     */
132 120
    public function getRelationships(): array
133
    {
134 120
        $relationships = [];
135
136 120
        foreach ($this->relationships as $name => $relationship) {
137 85
            if ($relationship instanceof OneRelationInterface) {
138 45
                $relationships[$name] = ['data' => null];
139
140 45
                if ($relationship->getIncluded() !== null) {
141 35
                    $relationships[$name] = [
142
                        'data' => [
143 35
                            'type' => $relationship->getIncluded()->getType(),
144 45
                            'id'   => $relationship->getIncluded()->getId(),
145
                        ],
146
                    ];
147
                }
148 40
            } elseif ($relationship instanceof ManyRelationInterface) {
149 40
                $relationships[$name]['data'] = [];
150
151 40
                foreach ($relationship->getIncluded() as $item) {
152 30
                    $relationships[$name]['data'][] =
153
                        [
154 30
                            'type' => $item->getType(),
155 41
                            'id'   => $item->getId(),
156
                        ];
157
                }
158
            }
159
        }
160
161 120
        return $relationships;
162
    }
163
164
    /**
165
     * @TODO: MEGA TODO. Set up a serializer for the Item so that we can remove this, getRelationships etc
166
     *
167
     * @return \Swis\JsonApi\Client\Collection
168
     */
169 35
    public function getIncluded(): Collection
170
    {
171 35
        $included = new Collection();
172
173 35
        foreach ($this->relationships as $name => $relationship) {
174
            if ($relationship->shouldOmitIncluded() || !$relationship->hasIncluded()) {
175
                continue;
176
            }
177
178
            if ($relationship instanceof OneRelationInterface) {
179
                /** @var \Swis\JsonApi\Client\Interfaces\ItemInterface $item */
180
                $item = $relationship->getIncluded();
181
                if ($item->canBeIncluded()) {
182
                    $included->push($item->toJsonApiArray());
183
                }
184
                $included = $included->merge($item->getIncluded());
185
            } elseif ($relationship instanceof ManyRelationInterface) {
186
                $relationship->getIncluded()->each(
187
                    function (ItemInterface $item) use (&$included) {
188
                        if ($item->canBeIncluded()) {
189
                            $included->push($item->toJsonApiArray());
190
                        }
191
                        $included = $included->merge($item->getIncluded());
192
                    }
193
                );
194
            }
195
        }
196
197
        return $included
198 35
            ->unique(
199 14
                function (array $item) {
200
                    return $item['type'].':'.$item['id'];
201 35
                }
202
            )
203 35
            ->values();
204
    }
205
206
    /**
207
     * @return bool
208
     */
209
    public function canBeIncluded(): bool
210
    {
211
        if (empty($this->getType())) {
212
            return false;
213
        }
214
215
        if (null === $this->getId()) {
216
            return false;
217
        }
218
219
        if (empty($this->relationships) && empty($this->toArray())) {
220
            return false;
221
        }
222
223
        return true;
224
    }
225
226
    /**
227
     * @param string $key
228
     *
229
     * @return mixed
230
     */
231 35
    public function __get($key)
232
    {
233 35
        if ($key === 'id') {
234 5
            return $this->getId();
235
        }
236
237 30
        return parent::__get($key);
238
    }
239
240
    /**
241
     * @param string $key
242
     *
243
     * @return \Swis\JsonApi\Client\Interfaces\DataInterface|mixed
244
     */
245 55
    public function getAttribute($key)
246
    {
247 55
        if ($this->hasAttribute($key) || $this->hasGetMutator($key)) {
248 35
            return parent::getAttribute($key);
249
        }
250
251 20
        return $this->getRelationValue($key);
252
    }
253
254
    /**
255
     * @param string $key
256
     *
257
     * @return bool
258
     */
259 55
    public function hasAttribute($key): bool
260
    {
261 55
        return array_key_exists($key, $this->attributes);
262
    }
263
264
    /**
265
     * @param string $key
266
     * @param mixed  $value
267
     */
268 5
    public function __set($key, $value)
269
    {
270 5
        if ($key === 'id') {
271 5
            $this->setId($value);
272
273 5
            return;
274
        }
275
276
        parent::__set($key, $value);
277
    }
278
279
    /**
280
     * Get the relationship data.
281
     *
282
     * @param string $key
283
     *
284
     * @return \Swis\JsonApi\Client\Interfaces\DataInterface|null
285
     */
286 20
    public function getRelationValue($key)
287
    {
288
        // If the "attribute" exists as a method on the model, we will just assume
289
        // it is a relationship and will load and return the included items in the relationship
290 20
        $method = Str::camel($key);
291 20
        if (method_exists($this, $method)) {
292 10
            return $this->$method()->getIncluded();
293
        }
294
295
        // If the "attribute" exists as a relationship on the model, we will return
296
        // the included items in the relationship
297 10
        if ($this->hasRelationship($key)) {
298 10
            return $this->getRelationship($key)->getIncluded();
299
        }
300
301
        return null;
302
    }
303
304
    /**
305
     * Determine if an attribute exists on the model.
306
     *
307
     * @param string $key
308
     *
309
     * @return bool
310
     */
311 5
    public function __isset($key)
312
    {
313 5
        if ($key === 'id') {
314 5
            return $this->hasId();
315
        }
316
317
        return parent::__isset($key) || $this->hasRelationship($key) || $this->hasRelationship(Str::snake($key));
318
    }
319
320
    /**
321
     * @param string $name
322
     *
323
     * @return \Swis\JsonApi\Client\Interfaces\OneRelationInterface|\Swis\JsonApi\Client\Interfaces\ManyRelationInterface
324
     */
325 80
    public function getRelationship(string $name)
326
    {
327 80
        return $this->relationships[$name];
328
    }
329
330
    /**
331
     * @param string $name
332
     *
333
     * @return bool
334
     */
335 25
    public function hasRelationship(string $name): bool
336
    {
337 25
        return array_key_exists($name, $this->relationships);
338
    }
339
340
    /**
341
     * @param $name
342
     *
343
     * @return static
344
     */
345
    public function removeRelationship(string $name)
346
    {
347
        unset($this->relationships[$name]);
348
349
        return $this;
350
    }
351
352
    /**
353
     * Create a singular relation to another item.
354
     *
355
     * @param string      $class
356
     * @param string|null $relationName
357
     *
358
     * @return \Swis\JsonApi\Client\Relations\HasOneRelation
359
     */
360 40
    public function hasOne(string $class, string $relationName = null)
361
    {
362 40
        $relationName = $relationName ?: Str::snake(debug_backtrace()[1]['function']);
363
364 40
        if (!array_key_exists($relationName, $this->relationships)) {
365 40
            $this->relationships[$relationName] = new HasOneRelation((new $class())->getType());
366
        }
367
368 40
        return $this->relationships[$relationName];
369
    }
370
371
    /**
372
     * Create a plural relation to another item.
373
     *
374
     * @param string      $class
375
     * @param string|null $relationName
376
     *
377
     * @return \Swis\JsonApi\Client\Relations\HasManyRelation
378
     */
379 20
    public function hasMany(string $class, string $relationName = null)
380
    {
381 20
        $relationName = $relationName ?: Str::snake(debug_backtrace()[1]['function']);
382
383 20
        if (!array_key_exists($relationName, $this->relationships)) {
384 20
            $this->relationships[$relationName] = new HasManyRelation((new $class())->getType());
385
        }
386
387 20
        return $this->relationships[$relationName];
388
    }
389
390
    /**
391
     * Create a singular relation to another item.
392
     *
393
     * @param string|null $relationName
394
     *
395
     * @return \Swis\JsonApi\Client\Relations\MorphToRelation
396
     */
397 50
    public function morphTo(string $relationName = null)
398
    {
399 50
        $relationName = $relationName ?: Str::snake(debug_backtrace()[1]['function']);
400
401 50
        if (!array_key_exists($relationName, $this->relationships)) {
402 50
            $this->relationships[$relationName] = new MorphToRelation();
403
        }
404
405 50
        return $this->relationships[$relationName];
406
    }
407
408
    /**
409
     * Create a plural relation to another item.
410
     *
411
     * @param string|null $relationName
412
     *
413
     * @return \Swis\JsonApi\Client\Relations\MorphToManyRelation
414
     */
415 55
    public function morphToMany(string $relationName = null)
416
    {
417 55
        $relationName = $relationName ?: Str::snake(debug_backtrace()[1]['function']);
418
419 55
        if (!array_key_exists($relationName, $this->relationships)) {
420 55
            $this->relationships[$relationName] = new MorphToManyRelation();
421
        }
422
423 55
        return $this->relationships[$relationName];
424
    }
425
426
    /**
427
     * Sets the initial values of an Item.
428
     *
429
     * @param array $initial
430
     *
431
     * @return static
432
     */
433 10
    public function setInitial(array $initial)
434
    {
435 10
        $this->initial = $initial;
436
437 10
        return $this;
438
    }
439
440
    /**
441
     * Returns the initial values of an Item.
442
     *
443
     * @param string|null $key
444
     *
445
     * @return array|mixed
446
     */
447 5
    public function getInitial($key = null)
448
    {
449 5
        if (null === $key) {
450 5
            return $this->initial;
451
        }
452
453
        return $this->initial[$key];
454
    }
455
456
    /**
457
     * @param string $key
458
     *
459
     * @return bool
460
     */
461
    public function hasInitial($key): bool
462
    {
463
        return isset($this->getInitial()[$key]);
464
    }
465
466
    /**
467
     * Prefills the model with values from $initial, when adding new item.
468
     *
469
     * @return static
470
     */
471 5
    public function useInitial()
472
    {
473 5
        $this->fill($this->initial);
474
475 5
        return $this;
476
    }
477
478
    /**
479
     * @return array
480
     */
481 130
    public function getAvailableRelations(): array
482
    {
483 130
        return $this->availableRelations;
484
    }
485
486
    /**
487
     * Set the specific relationship on the model.
488
     *
489
     * @param string                                        $relation
490
     * @param \Swis\JsonApi\Client\Interfaces\DataInterface $value
491
     * @param \Swis\JsonApi\Client\Links|null               $links
492
     * @param \Swis\JsonApi\Client\Meta|null                $meta
493
     *
494
     * @return static
495
     */
496 30
    public function setRelation(string $relation, DataInterface $value, Links $links = null, Meta $meta = null)
497
    {
498 30
        if (method_exists($this, $relation)) {
499
            /** @var \Swis\JsonApi\Client\Interfaces\OneRelationInterface|\Swis\JsonApi\Client\Interfaces\ManyRelationInterface $relationObject */
500 15
            $relationObject = $this->$relation();
501 20
        } elseif ($value instanceof Collection) {
502 10
            $relationObject = $this->morphToMany(Str::snake($relation));
503
        } else {
504 15
            $relationObject = $this->morphTo(Str::snake($relation));
505
        }
506
507 30
        $relationObject->associate($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type Swis\JsonApi\Client\Collection; however, parameter $included of Swis\JsonApi\Client\Rela...neRelation::associate() does only seem to accept Swis\JsonApi\Client\Interfaces\ItemInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

507
        $relationObject->associate(/** @scrutinizer ignore-type */ $value);
Loading history...
Bug introduced by
It seems like $value can also be of type Swis\JsonApi\Client\Collection; however, parameter $included of Swis\JsonApi\Client\Inte...nInterface::associate() does only seem to accept Swis\JsonApi\Client\Interfaces\ItemInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

507
        $relationObject->associate(/** @scrutinizer ignore-type */ $value);
Loading history...
508 30
        $relationObject->setLinks($links);
509 30
        $relationObject->setMeta($meta);
510
511 30
        return $this;
512
    }
513
}
514