GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

JsonApiSerializer   C
last analyzed

Coupling/Cohesion

Components 1
Dependencies 2

Complexity

Total Complexity 69

Size/Duplication

Total Lines 558
Duplicated Lines 3.41 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 69
c 0
b 0
f 0
lcom 1
cbo 2
dl 19
loc 558
ccs 219
cts 219
cp 1
rs 5.6445

28 Methods

Rating   Name   Duplication   Size   Complexity  
A sideloadIncludes() 0 4 1
A createIncludeObjects() 0 12 2
A createRootObjects() 0 8 2
A __construct() 0 5 1
A collection() 0 10 2
B item() 0 30 4
B paginator() 0 30 3
A meta() 0 15 3
A null() 0 6 1
C includedData() 9 26 8
A injectData() 0 10 2
A filterIncludes() 0 17 2
A filterRootObject() 0 4 1
A setRootObjects() 0 6 1
A isRootObject() 0 5 1
A isCollection() 0 5 2
A isNull() 0 4 2
A isEmpty() 0 4 2
A fillRelationships() 0 14 4
A parseRelationships() 0 12 3
A getIdFromData() 0 9 2
B pullOutNestedIncludedData() 10 24 6
A shouldIncludeLinks() 0 4 1
A fillRelationshipAsCollection() 0 17 3
A fillRelationshipAsSingleResource() 0 16 2
B buildRelationships() 0 27 4
A addIncludekeyToRelationsIfNotSet() 0 9 2
A addIncludedDataToRelationship() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like JsonApiSerializer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JsonApiSerializer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the League\Fractal package.
5
 *
6
 * (c) Phil Sturgeon <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\Fractal\Serializer;
13
14
use InvalidArgumentException;
15
use League\Fractal\Pagination\PaginatorInterface;
16
use League\Fractal\Resource\ResourceInterface;
17
18
class JsonApiSerializer extends ArraySerializer
19
{
20
    protected $baseUrl;
21
    protected $rootObjects;
22
23
    /**
24
     * JsonApiSerializer constructor.
25
     *
26
     * @param string $baseUrl
27
     */
28 29
    public function __construct($baseUrl = null)
29
    {
30 29
        $this->baseUrl = $baseUrl;
31 29
        $this->rootObjects = [];
32 29
    }
33
34
    /**
35
     * Serialize a collection.
36
     *
37
     * @param string $resourceKey
38
     * @param array $data
39
     *
40
     * @return array
41
     */
42 19
    public function collection($resourceKey, array $data)
43
    {
44 19
        $resources = [];
45
46 19
        foreach ($data as $resource) {
47 18
            $resources[] = $this->item($resourceKey, $resource)['data'];
48 19
        }
49
50 19
        return ['data' => $resources];
51
    }
52
53
    /**
54
     * Serialize an item.
55
     *
56
     * @param string $resourceKey
57
     * @param array $data
58
     *
59
     * @return array
60
     */
61 29
    public function item($resourceKey, array $data)
62
    {
63 29
        $id = $this->getIdFromData($data);
64
65
        $resource = [
66
            'data' => [
67 28
                'type' => $resourceKey,
68 28
                'id' => "$id",
69 28
                'attributes' => $data,
70 28
            ],
71 28
        ];
72
73 28
        unset($resource['data']['attributes']['id']);
74
75 28
        if(isset($resource['data']['attributes']['links'])) {
76 1
            $custom_links = $data['links'];
77 1
            unset($resource['data']['attributes']['links']);
78 1
        }
79
80 28
        if ($this->shouldIncludeLinks()) {
81 11
            $resource['data']['links'] = [
82 11
                'self' => "{$this->baseUrl}/$resourceKey/$id",
83
            ];
84 11
            if(isset($custom_links)) {
85 1
                $resource['data']['links'] = array_merge($custom_links, $resource['data']['links']);
86 1
            }
87 11
        }
88
89 28
        return $resource;
90
    }
91
92
    /**
93
     * Serialize the paginator.
94
     *
95
     * @param PaginatorInterface $paginator
96
     *
97
     * @return array
98
     */
99 3
    public function paginator(PaginatorInterface $paginator)
100
    {
101 3
        $currentPage = (int)$paginator->getCurrentPage();
102 3
        $lastPage = (int)$paginator->getLastPage();
103
104
        $pagination = [
105 3
            'total' => (int)$paginator->getTotal(),
106 3
            'count' => (int)$paginator->getCount(),
107 3
            'per_page' => (int)$paginator->getPerPage(),
108 3
            'current_page' => $currentPage,
109 3
            'total_pages' => $lastPage,
110 3
        ];
111
112 3
        $pagination['links'] = [];
113
114 3
        $pagination['links']['self'] = $paginator->getUrl($currentPage);
115 3
        $pagination['links']['first'] = $paginator->getUrl(1);
116
117 3
        if ($currentPage > 1) {
118 2
            $pagination['links']['prev'] = $paginator->getUrl($currentPage - 1);
119 2
        }
120
121 3
        if ($currentPage < $lastPage) {
122 2
            $pagination['links']['next'] = $paginator->getUrl($currentPage + 1);
123 2
        }
124
125 3
        $pagination['links']['last'] = $paginator->getUrl($lastPage);
126
127 3
        return ['pagination' => $pagination];
128
    }
129
130
    /**
131
     * Serialize the meta.
132
     *
133
     * @param array $meta
134
     *
135
     * @return array
136
     */
137 28
    public function meta(array $meta)
138
    {
139 28
        if (empty($meta)) {
140 23
            return [];
141
        }
142
143 5
        $result['meta'] = $meta;
144
145 5
        if (array_key_exists('pagination', $result['meta'])) {
146 3
            $result['links'] = $result['meta']['pagination']['links'];
147 3
            unset($result['meta']['pagination']['links']);
148 3
        }
149
150 5
        return $result;
151
    }
152
153
    /**
154
     * @return array
155
     */
156 2
    public function null()
157
    {
158
        return [
159 2
            'data' => null,
160 2
        ];
161
    }
162
163
    /**
164
     * Serialize the included data.
165
     *
166
     * @param ResourceInterface $resource
167
     * @param array $data
168
     *
169
     * @return array
170
     */
171 28
    public function includedData(ResourceInterface $resource, array $data)
172
    {
173 28
        list($serializedData, $linkedIds) = $this->pullOutNestedIncludedData($data);
174
175 28
        foreach ($data as $value) {
176 28
            foreach ($value as $includeObject) {
177 17
                if ($this->isNull($includeObject) || $this->isEmpty($includeObject)) {
178 4
                    continue;
179
                }
180
181 15
                $includeObjects = $this->createIncludeObjects($includeObject);
182
183 15 View Code Duplication
                foreach ($includeObjects as $object) {
184 15
                    $includeType = $object['type'];
185 15
                    $includeId = $object['id'];
186 15
                    $cacheKey = "$includeType:$includeId";
187 15
                    if (!array_key_exists($cacheKey, $linkedIds)) {
188 15
                        $serializedData[] = $object;
189 15
                        $linkedIds[$cacheKey] = $object;
190 15
                    }
191 15
                }
192 28
            }
193 28
        }
194
195 28
        return empty($serializedData) ? [] : ['included' => $serializedData];
196
    }
197
198
    /**
199
     * Indicates if includes should be side-loaded.
200
     *
201
     * @return bool
202
     */
203 29
    public function sideloadIncludes()
204
    {
205 29
        return true;
206
    }
207
208
    /**
209
     * @param array $data
210
     * @param array $includedData
211
     *
212
     * @return array
213
     */
214 28
    public function injectData($data, $includedData)
215
    {
216 28
        $relationships = $this->parseRelationships($includedData);
217
218 28
        if (!empty($relationships)) {
219 17
            $data = $this->fillRelationships($data, $relationships);
220 17
        }
221
222 28
        return $data;
223
    }
224
225
    /**
226
     * Hook to manipulate the final sideloaded includes.
227
     * The JSON API specification does not allow the root object to be included
228
     * into the sideloaded `included`-array. We have to make sure it is
229
     * filtered out, in case some object links to the root object in a
230
     * relationship.
231
     *
232
     * @param array $includedData
233
     * @param array $data
234
     *
235
     * @return array
236
     */
237 28
    public function filterIncludes($includedData, $data)
238
    {
239 28
        if (!isset($includedData['included'])) {
240 13
            return $includedData;
241
        }
242
243
        // Create the RootObjects
244 15
        $this->createRootObjects($data);
245
246
        // Filter out the root objects
247 15
        $filteredIncludes = array_filter($includedData['included'], [$this, 'filterRootObject']);
248
249
        // Reset array indizes
250 15
        $includedData['included'] = array_merge([], $filteredIncludes);
251
252 15
        return $includedData;
253
    }
254
255
    /**
256
     * Filter function to delete root objects from array.
257
     *
258
     * @param array $object
259
     *
260
     * @return bool
261
     */
262 15
    protected function filterRootObject($object)
263
    {
264 15
        return !$this->isRootObject($object);
265
    }
266
267
    /**
268
     * Set the root objects of the JSON API tree.
269
     *
270
     * @param array $objects
271
     */
272
    protected function setRootObjects(array $objects = [])
273
    {
274 15
        $this->rootObjects = array_map(function ($object) {
275 15
            return "{$object['type']}:{$object['id']}";
276 15
        }, $objects);
277 15
    }
278
279
    /**
280
     * Determines whether an object is a root object of the JSON API tree.
281
     *
282
     * @param array $object
283
     *
284
     * @return bool
285
     */
286 15
    protected function isRootObject($object)
287
    {
288 15
        $objectKey = "{$object['type']}:{$object['id']}";
289 15
        return in_array($objectKey, $this->rootObjects);
290
    }
291
292
    /**
293
     * @param array $data
294
     *
295
     * @return bool
296
     */
297 17
    protected function isCollection($data)
298
    {
299 17
        return array_key_exists('data', $data) &&
300 17
        array_key_exists(0, $data['data']);
301
    }
302
303
    /**
304
     * @param array $data
305
     *
306
     * @return bool
307
     */
308 17
    protected function isNull($data)
309
    {
310 17
        return array_key_exists('data', $data) && $data['data'] === null;
311
    }
312
313
    /**
314
     * @param array $data
315
     *
316
     * @return bool
317
     */
318 16
    protected function isEmpty($data)
319
    {
320 16
        return array_key_exists('data', $data) && $data['data'] === [];
321
    }
322
323
    /**
324
     * @param array $data
325
     * @param array $relationships
326
     *
327
     * @return array
328
     */
329 17
    protected function fillRelationships($data, $relationships)
330
    {
331 17
        if ($this->isCollection($data)) {
332 9
            foreach ($relationships as $key => $relationship) {
333 9
                $data = $this->fillRelationshipAsCollection($data, $relationship, $key);
334 9
            }
335 9
        } else { // Single resource
336 10
            foreach ($relationships as $key => $relationship) {
337 10
                $data = $this->fillRelationshipAsSingleResource($data, $relationship, $key);
338 10
            }
339
        }
340
341 17
        return $data;
342
    }
343
344
    /**
345
     * @param array $includedData
346
     *
347
     * @return array
348
     */
349 28
    protected function parseRelationships($includedData)
350
    {
351 28
        $relationships = [];
352
353 28
        foreach ($includedData as $key => $inclusion) {
354 28
            foreach ($inclusion as $includeKey => $includeObject) {
355 17
                $relationships = $this->buildRelationships($includeKey, $relationships, $includeObject, $key);
356 28
            }
357 28
        }
358
359 28
        return $relationships;
360
    }
361
362
    /**
363
     * @param array $data
364
     *
365
     * @return integer
366
     */
367 29
    protected function getIdFromData(array $data)
368
    {
369 29
        if (!array_key_exists('id', $data)) {
370 1
            throw new InvalidArgumentException(
371
                'JSON API resource objects MUST have a valid id'
372 1
            );
373
        }
374 28
        return $data['id'];
375
    }
376
377
    /**
378
     * Keep all sideloaded inclusion data on the top level.
379
     *
380
     * @param array $data
381
     *
382
     * @return array
383
     */
384 28
    protected function pullOutNestedIncludedData(array $data)
385
    {
386 28
        $includedData = [];
387 28
        $linkedIds = [];
388
389 28
        foreach ($data as $value) {
390 28
            foreach ($value as $includeObject) {
391 17
                if (isset($includeObject['included'])) {
392 3 View Code Duplication
                    foreach ($includeObject['included'] as $object) {
393 3
                        $includeType = $object['type'];
394 3
                        $includeId = $object['id'];
395 3
                        $cacheKey = "$includeType:$includeId";
396
397 3
                        if (!array_key_exists($cacheKey, $linkedIds)) {
398 3
                            $includedData[] = $object;
399 3
                            $linkedIds[$cacheKey] = $object;
400 3
                        }
401 3
                    }
402 3
                }
403 28
            }
404 28
        }
405
406 28
        return [$includedData, $linkedIds];
407
    }
408
409
    /**
410
     * Whether or not the serializer should include `links` for resource objects.
411
     *
412
     * @return bool
413
     */
414 28
    protected function shouldIncludeLinks()
415
    {
416 28
        return $this->baseUrl !== null;
417
    }
418
419
    /**
420
     * Check if the objects are part of a collection or not
421
     *
422
     * @param $includeObject
423
     *
424
     * @return array
425
     */
426 15
    private function createIncludeObjects($includeObject)
427
    {
428 15
        if ($this->isCollection($includeObject)) {
429 8
            $includeObjects = $includeObject['data'];
430
431 8
            return $includeObjects;
432
        } else {
433 10
            $includeObjects = [$includeObject['data']];
434
435 10
            return $includeObjects;
436
        }
437
    }
438
439
    /**
440
     * Sets the RootObjects, either as collection or not.
441
     *
442
     * @param $data
443
     */
444 15
    private function createRootObjects($data)
445
    {
446 15
        if ($this->isCollection($data)) {
447 8
            $this->setRootObjects($data['data']);
448 8
        } else {
449 7
            $this->setRootObjects([$data['data']]);
450
        }
451 15
    }
452
453
454
    /**
455
     * Loops over the relationships of the provided data and formats it
456
     *
457
     * @param $data
458
     * @param $relationship
459
     * @param $key
460
     *
461
     * @return array
462
     */
463 9
    private function fillRelationshipAsCollection($data, $relationship, $key)
464
    {
465 9
        foreach ($relationship as $index => $relationshipData) {
466 9
            $data['data'][$index]['relationships'][$key] = $relationshipData;
467
468 9
            if ($this->shouldIncludeLinks()) {
469 2
                $data['data'][$index]['relationships'][$key] = array_merge([
470
                    'links' => [
471 2
                        'self' => "{$this->baseUrl}/{$data['data'][$index]['type']}/{$data['data'][$index]['id']}/relationships/$key",
472 2
                        'related' => "{$this->baseUrl}/{$data['data'][$index]['type']}/{$data['data'][$index]['id']}/$key",
473 2
                    ],
474 2
                ], $data['data'][$index]['relationships'][$key]);
475 2
            }
476 9
        }
477
478 9
        return $data;
479
    }
480
481
482
    /**
483
     * @param $data
484
     * @param $relationship
485
     * @param $key
486
     *
487
     * @return array
488
     */
489 10
    private function fillRelationshipAsSingleResource($data, $relationship, $key)
490
    {
491 10
        $data['data']['relationships'][$key] = $relationship[0];
492
493 10
        if ($this->shouldIncludeLinks()) {
494 2
            $data['data']['relationships'][$key] = array_merge([
495
                'links' => [
496 2
                    'self' => "{$this->baseUrl}/{$data['data']['type']}/{$data['data']['id']}/relationships/$key",
497 2
                    'related' => "{$this->baseUrl}/{$data['data']['type']}/{$data['data']['id']}/$key",
498 2
                ],
499 2
            ], $data['data']['relationships'][$key]);
500
501 2
            return $data;
502
        }
503 8
        return $data;
504
    }
505
506
    /**
507
     * @param $includeKey
508
     * @param $relationships
509
     * @param $includeObject
510
     * @param $key
511
     *
512
     * @return array
513
     */
514 17
    private function buildRelationships($includeKey, $relationships, $includeObject, $key)
515
    {
516 17
        $relationships = $this->addIncludekeyToRelationsIfNotSet($includeKey, $relationships);
517
518 17
        if ($this->isNull($includeObject)) {
519 2
            $relationship = $this->null();
520 17
        } elseif ($this->isEmpty($includeObject)) {
521
            $relationship = [
522 2
                'data' => [],
523 2
            ];
524 16
        } elseif ($this->isCollection($includeObject)) {
525 8
            $relationship = ['data' => []];
526
527 8
            $relationship = $this->addIncludedDataToRelationship($includeObject, $relationship);
528 8
        } else {
529
            $relationship = [
530
                'data' => [
531 10
                    'type' => $includeObject['data']['type'],
532 10
                    'id' => $includeObject['data']['id'],
533 10
                ],
534 10
            ];
535
        }
536
537 17
        $relationships[$includeKey][$key] = $relationship;
538
539 17
        return $relationships;
540
    }
541
542
    /**
543
     * @param $includeKey
544
     * @param $relationships
545
     *
546
     * @return array
547
     */
548 17
    private function addIncludekeyToRelationsIfNotSet($includeKey, $relationships)
549
    {
550 17
        if (!array_key_exists($includeKey, $relationships)) {
551 17
            $relationships[$includeKey] = [];
552 17
            return $relationships;
553
        }
554
555 9
        return $relationships;
556
    }
557
558
    /**
559
     * @param $includeObject
560
     * @param $relationship
561
     *
562
     * @return array
563
     */
564 8
    private function addIncludedDataToRelationship($includeObject, $relationship)
565
    {
566 8
        foreach ($includeObject['data'] as $object) {
567 8
            $relationship['data'][] = [
568 8
                'type' => $object['type'],
569 8
                'id' => $object['id'],
570
            ];
571 8
        }
572
573 8
        return $relationship;
574
    }
575
}
576