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.
Completed
Push — master ( 796de0...1e3025 )
by Sergey
22s queued 10s
created

DataParser::parseErrors()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 18
cts 18
cp 1
rs 9.472
c 0
b 0
f 0
cc 4
nc 2
nop 1
crap 4
1
<?php
2
/*
3
 * This file is part of the reva2/jsonapi.
4
 *
5
 * (c) Sergey Revenko <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Reva2\JsonApi\Decoders;
12
13
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
14
use Neomerx\JsonApi\Document\Error;
15
use Neomerx\JsonApi\Exceptions\JsonApiException;
16
use Reva2\JsonApi\Contracts\Decoders\CallbackResolverInterface;
17
use Reva2\JsonApi\Contracts\Decoders\DataParserInterface;
18
use Reva2\JsonApi\Contracts\Decoders\Mapping\ClassMetadataInterface;
19
use Reva2\JsonApi\Contracts\Decoders\Mapping\DocumentMetadataInterface;
20
use Reva2\JsonApi\Contracts\Decoders\Mapping\Factory\MetadataFactoryInterface;
21
use Reva2\JsonApi\Contracts\Decoders\Mapping\ObjectMetadataInterface;
22
use Reva2\JsonApi\Contracts\Decoders\Mapping\PropertyMetadataInterface;
23
use Reva2\JsonApi\Contracts\Decoders\Mapping\ResourceMetadataInterface;
24
use Symfony\Component\PropertyAccess\PropertyAccess;
25
use Symfony\Component\PropertyAccess\PropertyAccessor;
26
27
/**
28
 * Data parser
29
 *
30
 * @package Reva2\JsonApi\Decoders
31
 * @author Sergey Revenko <[email protected]>
32
 */
33
class DataParser implements DataParserInterface
34
{
35
    const ERROR_CODE = 'ee2c1d49-ba40-4077-a6bb-b06baceb3e97';
36
37
    /**
38
     * Current path
39
     *
40
     * @var \SplStack
41
     */
42
    protected $path;
43
44
    /**
45
     * @var Context
46
     */
47
    protected $context;
48
49
    /**
50
     * Resource decoders factory
51
     *
52
     * @var MetadataFactoryInterface
53
     */
54
    protected $factory;
55
56
    /**
57
     * @var PropertyAccessor
58
     */
59
    protected $accessor;
60
61
    /**
62
     * @var CallbackResolverInterface
63
     */
64
    protected $callbackResolver;
65
66
    /**
67
     * Serialization groups
68
     *
69
     * @var string[]
70
     */
71
    protected $serializationGroups = ['Default'];
72
73
    /**
74
     * Constructor
75
     *
76
     * @param MetadataFactoryInterface $factory
77
     * @param CallbackResolverInterface $callbackResolver
78
     */
79 20
    public function __construct(MetadataFactoryInterface $factory, CallbackResolverInterface $callbackResolver)
80
    {
81 20
        $this->factory = $factory;
82 20
        $this->callbackResolver = $callbackResolver;
83 20
        $this->accessor = PropertyAccess::createPropertyAccessorBuilder()
84 20
            ->enableExceptionOnInvalidIndex()
85 20
            ->getPropertyAccessor();
86
        
87 20
        $this->initPathStack();
88 20
        $this->initContext();
89 20
    }
90
91
    /**
92
     * @inheritdoc
93
     */
94 15
    public function setPath($path)
95
    {
96 15
        $this->path->push($this->preparePathSegment($path));
97
98 15
        return $this;
99
    }
100
101
    /**
102
     * @inheritdoc
103
     */
104 15
    public function restorePath()
105
    {
106 15
        $this->path->pop();
107
108 15
        return $this;
109
    }
110
111
    /**
112
     * @return string[]
113
     */
114 3
    public function getSerializationGroups()
115
    {
116 3
        return $this->serializationGroups;
117
    }
118
119
    /**
120
     * @param string[] $serializationGroups
121
     * @return $this
122
     */
123 3
    public function setSerializationGroups(array $serializationGroups)
124
    {
125 3
        $this->serializationGroups = $serializationGroups;
126
127 3
        return $this;
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133 2
    public function getPath()
134
    {
135 2
        $segments = [];
136 2
        foreach ($this->path as $segment) {
137 1
            $segments[] = $segment;
138
        }
139
140 2
        return '/' . implode('/', array_reverse($segments));
141
    }
142
143
    /**
144
     * @inheritdoc
145
     */
146 16
    public function hasValue($data, $path)
147
    {
148 16
        return $this->accessor->isReadable($data, $path);
149
    }
150
151
    /**
152
     * @inheritdoc
153
     */
154 15
    public function getValue($data, $path)
155
    {
156 15
        return $this->accessor->getValue($data, $path);
157
    }
158
159
    /**
160
     * @inheritdoc
161
     */
162 9
    public function parseString($data, $path)
163
    {
164 9
        $this->setPath($path);
165
166 9
        $pathValue = null;
167 9
        if ($this->hasValue($data, $path)) {
168 9
            $value = $this->getValue($data, $path);
169 9
            if ((null === $value) || (is_string($value))) {
170 9
                $pathValue = $value;
171
            } else {
172 1
                throw new \InvalidArgumentException(
173 1
                    sprintf("Value expected to be a string, but %s given", gettype($value)),
174 1
                    400
175
                );
176
            }
177
        }
178 9
        $this->restorePath();
179
180 9
        return $pathValue;
181
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186 4
    public function parseInt($data, $path)
187
    {
188 4
        return $this->parseNumeric($data, $path, 'int');
189
    }
190
191
    /**
192
     * @inheritdoc
193
     */
194 2
    public function parseFloat($data, $path)
195
    {
196 2
        return $this->parseNumeric($data, $path, 'float');
197
    }
198
199
    /**
200
     * @inheritdoc
201
     */
202 2
    public function parseRaw($data, $path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204 2
        $this->setPath($path);
205
206 2
        $pathValue = null;
207 2
        if ($this->hasValue($data, $path)) {
208 2
            $pathValue = $this->getValue($data, $path);
209
        }
210
211 2
        $this->restorePath();
212
213 2
        return $pathValue;
214
    }
215
216
    /**
217
     * @inheritdoc
218
     */
219 4
    public function parseCallback($data, $path, $callback)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
    {
221 4
        $this->setPath($path);
222
223 4
        $pathValue = null;
224 4
        if ($this->hasValue($data, $path)) {
225 4
            $pathValue = call_user_func($callback, $this->getValue($data, $path));
226
        }
227
228 4
        $this->restorePath();
229
230 4
        return $pathValue;
231
    }
232
233
    /**
234
     * @inheritdoc
235
     */
236 2
    public function parseBool($data, $path)
237
    {
238 2
        $this->setPath($path);
239
240 2
        $pathValue = null;
241 2
        if ($this->hasValue($data, $path)) {
242 2
            $value = $this->getValue($data, $path);
243 2
            if ((null === $value) || (is_bool($value))) {
244 1
                $pathValue = $value;
245 2
            } elseif (is_string($value)) {
246 2
                $pathValue = (in_array($value, ['true', 'yes', 'y', 'on', 'enabled'])) ? true : false;
247 1
            } elseif (is_numeric($value)) {
248 1
                $pathValue = (bool) $value;
249
            } else {
250 1
                throw new \InvalidArgumentException(
251 1
                    sprintf("Value expected to be a boolean, but %s given", gettype($value)),
252 1
                    400
253
                );
254
            }
255
        }
256
257 2
        $this->restorePath();
258
259 2
        return $pathValue;
260
    }
261
262
    /**
263
     * @inheritdoc
264
     */
265 2
    public function parseDateTime($data, $path, $format = 'Y-m-d')
266
    {
267 2
        $this->setPath($path);
268
269 2
        $pathValue = null;
270 2
        if ($this->hasValue($data, $path)) {
271 2
            $value = $this->getValue($data, $path);
272 2
            if (null !== $value) {
273 2
                if (is_string($value)) {
274 2
                    $pathValue = \DateTimeImmutable::createFromFormat($format, $value);
275
                }
276
277 2
                if (!$pathValue instanceof \DateTimeImmutable) {
278 1
                    throw new \InvalidArgumentException(
279 1
                        sprintf("Value expected to be a date/time string in '%s' format", $format),
280 1
                        400
281
                    );
282
                }
283
            }
284
        }
285
286 2
        $this->restorePath();
287
288 2
        return $pathValue;
289
    }
290
291
    /**
292
     * @inheritdoc
293
     */
294 7
    public function parseArray($data, $path, \Closure $itemsParser)
295
    {
296 7
        $this->setPath($path);
297
298 7
        $pathValue = null;
299 7
        if ($this->hasValue($data, $path)) {
300 5
            $value = $this->getValue($data, $path);
301 5 View Code Duplication
            if (false === is_array($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
302 1
                throw new \InvalidArgumentException(
303 1
                    sprintf("Value expected to be an array, but %s given", gettype($value)),
304 1
                    400
305
                );
306
            }
307
308 5
            $pathValue = [];
309 5
            $keys = array_keys($value);
310 5
            foreach ($keys as $key) {
311 5
                $arrayPath = sprintf("[%s]", $key);
312
313 5
                $pathValue[$key] = $itemsParser($value, $arrayPath, $this);
314
            }
315
        }
316
317 6
        $this->restorePath();
318
319 6
        return $pathValue;
320
    }
321
322
    /**
323
     * Parse data object value at specified path as object of specified class
324
     *
325
     * @param array|object $data
326
     * @param string $path
327
     * @param string $objType
328
     * @return null
329
     */
330 2
    public function parseObject($data, $path, $objType)
331
    {
332 2
        $this->setPath($path);
333
334 2
        $pathValue = null;
335 2
        if ((true === $this->hasValue($data, $path)) &&
336 2
            (null !== ($value = $this->getValue($data, $path)))
337
        ) {
338 2
            $this->restorePath();
339
340 2
            $pathValue = $this->parseObjectValue($value, $objType);
341
        }
342
343 2
        return $pathValue;
344
    }
345
346
    /**
347
     * @inheritdoc
348
     */
349 5
    public function parseResource($data, $path, $resType, $loader = null)
350
    {
351 5
        $this->setPath($path);
352
353 5
        $pathValue = null;
354 5
        if ((true === $this->hasValue($data, $path)) &&
355 5
            (null !== ($value = $this->getValue($data, $path)))
356
        ) {
357 5
            $metadata = $this->factory->getMetadataFor($resType);
358
            /* @var $metadata ResourceMetadataInterface */
359
360 5
            $name = $this->parseString($value, 'type');
361 5
            if ($name !== $metadata->getName()) {
362 1
                throw new \InvalidArgumentException(
363 1
                    sprintf("Value must contain resource of type '%s'", $metadata->getName()),
364 1
                    409
365
                );
366
            }
367
368 5
            $id = ($this->hasValue($value, 'id')) ? $this->getValue($value, 'id') : null;
369
370 5
            $pathValue = null;
371
372 5
            if ($loader === null) {
373 5
                $loader = $metadata->getLoader();
374
            }
375
376 5
            if ((null !== $loader) && (null !== $id)) {
377
                $callback = $this->callbackResolver->resolveCallback($loader);
378
                $pathValue = call_user_func($callback, $id, $metadata);
379
            }
380
381 5
            if (null === $pathValue) {
382 5
                $discClass = $this->getClassByDiscriminator($metadata, $value);
383 4
                if ((null !== $discClass) && ($discClass !== $resType)) {
384 4
                    $metadata = $this->factory->getMetadataFor($discClass);
385
                }
386
387 4
                $objClass = $metadata->getClassName();
388 4
                $pathValue = new $objClass();
389
390 4
                if (null !== ($idMetadata = $metadata->getIdMetadata())) {
391 4
                    $this->parseProperty($value, $pathValue, $idMetadata);
392
                }
393
            } else {
394
                $valueClass = get_class($pathValue);
395
                if ($valueClass !== $resType) {
396
                    $metadata = $this->factory->getMetadataFor($valueClass);
397
                }
398
            }
399
400 4
            foreach ($metadata->getAttributes() as $attribute) {
401 4
                $this->parseProperty($value, $pathValue, $attribute);
402
            }
403
404 4
            foreach ($metadata->getRelationships() as $relationship) {
405 4
                $this->parseRelationship($value, $pathValue, $relationship);
406
            }
407
408 3
            if (null !== $id) {
409 3
                $this->context->registerResource($name, $id, $pathValue);
410
            }
411
        }
412
413 3
        $this->restorePath();
414
415 3
        return $pathValue;
416
    }
417
418
    /**
419
     * @inheritdoc
420
     */
421 5
    public function parseDocument($data, $docType)
422
    {
423
        try {
424 5
            $this->initPathStack();
425 5
            $this->initContext();
426
427 5
            $this->parseErrors($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 421 can also be of type array; however, Reva2\JsonApi\Decoders\DataParser::parseErrors() does only seem to accept object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
428
429 4
            $metadata = $this->factory->getMetadataFor($docType);
430 4
            if (!$metadata instanceof DocumentMetadataInterface) {
431 1
                throw new \InvalidArgumentException(sprintf("Failed to parse %s as JSON API document", $docType));
432
            }
433
434
            /* @var $metadata \Reva2\JsonApi\Contracts\Decoders\Mapping\DocumentMetadataInterface */
435
436 3
            $docClass = $metadata->getClassName();
437 3
            $doc = new $docClass();
438
439 3
            $this->parseLinkedResources($data);
440
441 3
            $this->parseProperty($data, $doc, $metadata->getContentMetadata());
442
443 2
            $docMetadata = $metadata->getMetadata();
444 2
            if ($docMetadata !== null) {
445 1
                $this->parseProperty($data, $doc, $metadata->getMetadata());
446
            }
447
448 2
            return $doc;
449 3
        } catch (JsonApiException $e) {
450 1
            throw $e;
451 2
        } catch (\Exception $e) {
452 2
            throw  $this->convertToApiException($e, 'document');
453
        }
454
    }
455
456
    /**
457
     * @inheritdoc
458
     */
459 3
    public function parseQueryParams($data, $paramsType)
460
    {
461
        try {
462 3
            $this->initPathStack();
463 3
            $this->initContext();
464
465 3
            $query = $this->parseObjectValue($data, $paramsType);
466 3
            if (!$query instanceof EncodingParametersInterface) {
467
                throw new \InvalidArgumentException(sprintf(
468
                    "Query parameters object must implement interface %s",
469
                    EncodingParametersInterface::class
470
                ));
471
            }
472
473 3
            return $query;
474
        } catch (JsonApiException $e) {
475
            throw $e;
476
        } catch (\Exception $e) {
477
            throw  $this->convertToApiException($e, 'query');
478
        }
479
    }
480
481
    /**
482
     * Prepare path segment
483
     *
484
     * @param string $path
485
     * @return string
486
     */
487 15
    protected function preparePathSegment($path)
488
    {
489 15
        return trim(preg_replace('~[\/]+~si', '/', str_replace(['.', '[', ']'], '/', (string) $path)), '/');
490
    }
491
492
    /**
493
     * Initialize stack that store current path
494
     */
495 20
    protected function initPathStack()
496
    {
497 20
        $this->path = new \SplStack();
498 20
    }
499
500
    /**
501
     * Initialize decoder context
502
     */
503 20
    protected function initContext()
504
    {
505 20
        $this->context = new Context();
506 20
    }
507
508
    /**
509
     * Parse numeric value
510
     *
511
     * @param mixed $data
512
     * @param string $path
513
     * @param string $type
514
     * @return float|int|null
515
     */
516 5
    protected function parseNumeric($data, $path, $type)
517
    {
518 5
        $this->setPath($path);
519
520 5
        $pathValue = null;
521 5
        if ($this->hasValue($data, $path)) {
522 5
            $value = $this->getValue($data, $path);
523 5
            $rightType = ('int' === $type) ? is_int($value) : is_float($value);
524 5
            if ($rightType) {
525 4
                $pathValue = $value;
526 4
            } elseif (is_numeric($value)) {
527 4
                $pathValue = ('int' === $type) ? (int) $value : (float) $value;
528 2 View Code Duplication
            } elseif (null !== $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529 2
                throw new \InvalidArgumentException(
530 2
                    sprintf("Value expected to be %s, but %s given", $type, gettype($value)),
531 2
                    400
532
                );
533
            }
534
        }
535
536 5
        $this->restorePath();
537
538 5
        return $pathValue;
539
    }
540
541
    /**
542
     * Parse property of specified object
543
     *
544
     * @param object|array $data
545
     * @param object $obj
546
     * @param PropertyMetadataInterface $metadata
547
     * @param string|null $path
548
     */
549 7
    private function parseProperty($data, $obj, PropertyMetadataInterface $metadata, $path = null)
550
    {
551 7
        if (null === $path) {
552 7
            $path = $metadata->getDataPath();
553
        }
554
555 7
        if ((false === $this->hasValue($data, $path)) ||
556 7
            (true === $this->isExcludedProperty($metadata))
557
        ) {
558 6
            return;
559
        }
560
561 7
        if ('custom' === $metadata->getDataType()) {
562 4
            $value = $this->parseCallback($data, $path, [$obj, $metadata->getDataTypeParams()]);
563
        } else {
564 6
            $value = $this->parsePropertyValue($data, $path, $metadata);
565
        }
566
567 7
        if (null !== ($converter = $metadata->getConverter())) {
568 3
            $callback = $this->callbackResolver->resolveCallback($converter);
569
570 3
            $value = call_user_func($callback, $value);
571
        }
572
573 7
        $this->setProperty($obj, $value, $metadata);
574 7
    }
575
576
    /**
577
     * Parse value of specified property
578
     *
579
     * @param object|array $data
580
     * @param string $path
581
     * @param PropertyMetadataInterface $metadata
582
     * @return mixed|null
583
     */
584 6
    private function parsePropertyValue($data, $path, PropertyMetadataInterface $metadata)
585
    {
586 6
        switch ($metadata->getDataType()) {
587 6
            case 'scalar':
588 6
                return $this->parseScalarValue($data, $path, $metadata->getDataTypeParams());
589
590 5
            case 'datetime':
591 1
                $format = $metadata->getDataTypeParams();
592 1
                if (empty($format)) {
593 1
                    $format = 'Y-m-d';
594
                }
595
596 1
                return $this->parseDateTime($data, $path, $format);
597
598 5
            case 'array':
599 3
                return $this->parseArrayValue($data, $path, $metadata->getDataTypeParams(), $metadata);
600
601 5
            case 'object':
602 5
                return $this->parseResourceOrObject($data, $path, $metadata->getDataTypeParams(), $metadata);
603
604 1
            case 'raw':
605 1
                return $this->parseRaw($data, $path);
606
607
            default:
608
                throw new \InvalidArgumentException(sprintf(
609
                    "Unsupported property data type '%s'",
610
                    $metadata->getDataType()
611
                ));
612
        }
613
    }
614
615
    /**
616
     * Parse value as JSON API resource or object
617
     *
618
     * @param object|array $data
619
     * @param string $path
620
     * @param string $objClass
621
     * @param PropertyMetadataInterface $propMetadata
622
     * @return mixed|null
623
     */
624 5
    public function parseResourceOrObject($data, $path, $objClass, PropertyMetadataInterface $propMetadata)
625
    {
626 5
        $objMetadata = $this->factory->getMetadataFor($objClass);
627 5
        if ($objMetadata instanceof ResourceMetadataInterface) {
628 4
            $loader = null;
629 4
            foreach ($propMetadata->getLoaders() as $group => $groupLoader) {
630
                if (in_array($group, $this->serializationGroups)) {
631
                    $loader = $groupLoader;
632
                }
633
            }
634
635 4
            return $this->parseResource($data, $path, $objClass, $loader);
636
        }
637
638 2
        return $this->parseObject($data, $path, $objClass);
639
    }
640
641
    /**
642
     * Parse value that contains JSON API object
643
     *
644
     * @param object|array $data
645
     * @param string $objType
646
     * @return mixed
647
     */
648 5
    public function parseObjectValue($data, $objType)
649
    {
650 5
        $metadata = $this->factory->getMetadataFor($objType);
651 5
        if (!$metadata instanceof ObjectMetadataInterface) {
652
            throw new \InvalidArgumentException('Invalid object metadata');
653
        }
654
655 5
        $discClass = $this->getClassByDiscriminator($metadata, $data);
656 5
        if ((null !== $discClass) && ($discClass !== $objType)) {
657 1
            return $this->parseObjectValue($data, $discClass);
658
        }
659
660 5
        $objClass = $metadata->getClassName();
661 5
        $obj = new $objClass();
662
663 5
        $properties = $metadata->getProperties();
664 5
        foreach ($properties as $property) {
665 5
            $this->parseProperty($data, $obj, $property);
666
        }
667
668 5
        return $obj;
669
    }
670
671
    /**
672
     * Parse value that contains array
673
     *
674
     * @param object|array $data
675
     * @param string $path
676
     * @param array $params
677
     * @param PropertyMetadataInterface $propMetadata
678
     * @return array|null
679
     */
680 3
    public function parseArrayValue($data, $path, array $params, PropertyMetadataInterface $propMetadata)
681
    {
682 3
        $type = $params[0];
683 3
        $typeParams = $params[1];
684
685 3
        switch ($type) {
686 3
            case 'scalar':
687 1
                return $this->parseArray(
688 1
                    $data,
689
                    $path,
690 1
                    function ($data, $path, DataParser $parser) use ($typeParams) {
691 1
                        return $parser->parseScalarValue($data, $path, $typeParams);
692 1
                    }
693
                );
694
695 3
            case 'datetime':
696 1
                $format = (!empty($typeParams)) ? $typeParams : 'Y-m-d';
697 1
                return $this->parseArray(
698 1
                    $data,
699
                    $path,
700 1
                    function ($data, $path, DataParser $parser) use ($format) {
701 1
                        return $parser->parseDateTime($data, $path, $format);
702 1
                    }
703
                );
704
705 3
            case 'object':
706 3
                return $this->parseArray(
707 3
                    $data,
708
                    $path,
709 3
                    function ($data, $path, DataParser $parser) use ($typeParams, $propMetadata) {
710 3
                        return $parser->parseResourceOrObject($data, $path, $typeParams, $propMetadata);
711 3
                    }
712
                );
713
714 1
            case 'array':
715 1
                return $this->parseArray(
716 1
                    $data,
717
                    $path,
718 1
                    function ($data, $path, DataParser $parser) use ($typeParams, $propMetadata) {
719 1
                        return $parser->parseArrayValue($data, $path, $typeParams, $propMetadata);
720 1
                    }
721
                );
722
723 1
            case 'raw':
724 1
                return $this->parseArray(
725 1
                    $data,
726
                    $path,
727 1
                    function ($data, $path, DataParser $parser) {
728 1
                        return $parser->parseRaw($data, $path);
729 1
                    }
730
                );
731
732
            default:
733
                throw new \InvalidArgumentException(sprintf(
734
                    "Unsupported array item type '%s' specified",
735
                    $type
736
                ));
737
        }
738
    }
739
740
    /**
741
     * Parse scalar value
742
     *
743
     * @param object|array $data
744
     * @param string $path
745
     * @param string $type
746
     * @return bool|float|int|null|string
747
     */
748 6
    public function parseScalarValue($data, $path, $type)
749
    {
750 6
        switch ($type) {
751 6
            case 'string':
752 5
                return $this->parseString($data, $path);
753
754 3
            case 'bool':
755 3
            case 'boolean':
756 1
                return $this->parseBool($data, $path);
757
758 3
            case 'int':
759 3
            case 'integer':
760 3
                return $this->parseInt($data, $path);
761
762 1
            case 'float':
763 1
            case 'double':
764 1
                return $this->parseFloat($data, $path);
765
766
            default:
767
                throw new \InvalidArgumentException(sprintf("Unsupported scalar type '%s' specified", $type));
768
        }
769
    }
770
771
    /**
772
     * Convert any exception to JSON API exception
773
     *
774
     * @param \Exception $e
775
     * @param string $objType
776
     * @return JsonApiException
777
     */
778 2
    private function convertToApiException(\Exception $e, $objType)
779
    {
780 2
        $status = $e->getCode();
781 2
        $message = 'Failed to parse request';
782 2
        if (empty($status)) {
783 1
            $message = 'Internal server error';
784 1
            $status = 500;
785
        }
786
787 2
        $source = null;
788 2
        switch ($objType) {
789 2
            case 'document':
790 2
                $source = ['pointer' => $this->getPath()];
791 2
                break;
792
793
            case 'query':
794
                $source = ['parameter' => $this->getPath()];
795
                break;
796
        }
797
798 2
        $error = new Error(rand(), null, $status, self::ERROR_CODE, $message, $e->getMessage(), $source);
799
800 2
        return new JsonApiException($error, $status, $e);
801
    }
802
803
    /**
804
     * Returns appropriate discriminator class for specified data
805
     *
806
     * @param ClassMetadataInterface $metadata
807
     * @param array|object $data
808
     * @return string|null
809
     */
810 8
    private function getClassByDiscriminator(ClassMetadataInterface $metadata, $data)
811
    {
812 8
        if (null === ($discField = $metadata->getDiscriminatorField())) {
813 6
            return null;
814
        }
815
816 6
        $discValue = $this->parseString($data, $discField->getDataPath());
817 6
        if (empty($discValue)) {
818 1
            $this->setPath($discField->getDataPath());
819
820 1
            throw new \InvalidArgumentException("Field value required and can not be empty", 422);
821
        }
822
823 5
        return $metadata->getDiscriminatorClass($discValue);
824
    }
825
826
    /**
827
     * Check if specified property should be excluded
828
     *
829
     * @param PropertyMetadataInterface $metadata
830
     * @return bool
831
     */
832 7
    private function isExcludedProperty(PropertyMetadataInterface $metadata)
833
    {
834 7
        $propertyGroups = $metadata->getGroups();
835 7
        foreach ($propertyGroups as $group) {
836 7
            if (in_array($group, $this->serializationGroups)) {
837 7
                return false;
838
            }
839
        }
840
841
        return true;
842
    }
843
844
    /**
845
     *
846
     * @param mixed $data
847
     */
848 3
    private function parseLinkedResources($data)
849
    {
850 3
        if (false === $this->hasValue($data, 'included')) {
851 2
            return;
852
        }
853
854 1
        $linkedData = $this->getValue($data, 'included');
855 1
        if (!is_array($linkedData)) {
856
            return;
857
        }
858
859 1
        foreach ($linkedData as $idx => $resData) {
860 1
            $id = null;
861 1
            if ($this->hasValue($resData, 'id')) {
862 1
                $id = $this->getValue($resData, 'id');
863
            }
864
865 1
            $type = null;
866 1
            if ($this->hasValue($resData, 'type')) {
867 1
                $type = $this->getValue($resData, 'type');
868
            }
869
870 1
            if (empty($id) || empty($type) || !is_string($id) || !is_string($type)) {
871
                continue;
872
            }
873
874 1
            $this->context->addLinkedData($type, $id, $idx, $resData);
875
        }
876 1
    }
877
878
    /**
879
     * Parse specified relationship data
880
     *
881
     * @param mixed $data
882
     * @param mixed $pathValue
883
     * @param PropertyMetadataInterface $relationship
884
     * @return void
885
     */
886 4
    private function parseRelationship($data, $pathValue, PropertyMetadataInterface $relationship)
887
    {
888 4
        if ('array' === $relationship->getDataType()) {
889 3
            return $this->parseArrayRelationship($data, $pathValue, $relationship);
890
        }
891
892 4
        $resType = null;
893 4
        if ($this->hasValue($data, $relationship->getDataPath() . '.type')) {
894 4
            $resType = $this->parseString($data, $relationship->getDataPath() . '.type');
895
        }
896
897 4
        $resId = null;
898 4
        if ($this->hasValue($data, $relationship->getDataPath() . '.id')) {
899 4
            $resId = $this->getValue($data, $relationship->getDataPath() . '.id');
900
        }
901
902 4 View Code Duplication
        if ((null !== $resId) &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
903 4
            (null !== $resType) &&
904 4
            (null !== ($res = $this->context->getResource($resType, $resId)))
905
        ) {
906 1
            return $this->setProperty($pathValue, $res, $relationship);
907
        }
908
909
910 4 View Code Duplication
        if (null !== ($linkedData = $this->context->getLinkedData($resType, $resId))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
911 1
            $idx = $this->context->getLinkedDataIndex($resType, $resId);
912 1
            $prevPath = $this->path;
913
914 1
            $this->initPathStack();
915 1
            $this->setPath('included')->setPath($idx);
916
917 1
            $this->parseProperty([$idx => $linkedData], $pathValue, $relationship, '[' . $idx . ']');
918 1
            $this->path = $prevPath;
919
920 1
            return;
921
        }
922
923 4
        $this->parseProperty($data, $pathValue, $relationship);
924 3
    }
925
926
    /**
927
     * Parse data for relationship that contains array of resources
928
     *
929
     * @param mixed $data
930
     * @param mixed $pathValue
931
     * @param PropertyMetadataInterface $relationship
932
     */
933
    private function parseArrayRelationship($data, $pathValue, PropertyMetadataInterface $relationship)
934
    {
935
936 3
        $data = $this->parseArray($data, $relationship->getDataPath(), function ($data, $path) use ($relationship) {
937 1
            $resType = null;
938 1
            if ($this->hasValue($data, $path . '.type')) {
939 1
                $resType = $this->parseString($data, $path . '.type');
940
            }
941
942 1
            $resId = null;
943 1
            if ($this->hasValue($data, $path .'.id')) {
944 1
                $resId = $this->getValue($data, $path . '.id');
945
            }
946
947 1 View Code Duplication
            if ((null !== $resType) &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
948 1
                (null !== $resId) &&
949 1
                (null !== ($parsed = $this->context->getResource($resType, $resId)))
950
            ) {
951 1
                return $parsed;
952
            }
953
954 1
            $params = $relationship->getDataTypeParams();
955
956 1 View Code Duplication
            if (null !== ($linkedData = $this->context->getLinkedData($resType, $resId))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
957 1
                $idx = $this->context->getLinkedDataIndex($resType, $resId);
958
959 1
                $prevPath = $this->path;
960 1
                $this->initPathStack();
961 1
                $this->setPath('included')->setPath($idx);
962
963 1
                $parsed = $this->parseResourceOrObject(
964 1
                    [$idx => $linkedData],
965 1
                    '[' . $idx .']',
966 1
                    $params[1],
967
                    $relationship
968
                );
969
970 1
                $this->path = $prevPath;
971
972 1
                return $parsed;
973
            }
974
975
            return $this->parseResourceOrObject($data, $path, $params[1], $relationship);
976 3
        });
977
978 3
        $this->setProperty($pathValue, $data, $relationship);
979 3
    }
980
981
    /**
982
     * Sets property value using metadata
983
     *
984
     * @param mixed $obj
985
     * @param mixed $value
986
     * @param PropertyMetadataInterface $metadata
987
     */
988 7
    private function setProperty($obj, $value, PropertyMetadataInterface $metadata)
989
    {
990 7
        $setter = $metadata->getSetter();
991 7
        if (null !== $setter) {
992 6
            $obj->{$setter}($value);
993
        } else {
994 5
            $setter = $metadata->getPropertyName();
995 5
            $obj->{$setter} = $value;
996
        }
997 7
    }
998
999
    /**
1000
     * Parse errors from JSON API document
1001
     *
1002
     * @param object $data
1003
     */
1004 5
    private function parseErrors($data)
1005
    {
1006 5
        if (!$this->hasValue($data, 'errors')) {
1007 4
            return;
1008
        }
1009
1010 1
        $errors = $this->parseArray($data, 'errors', function ($data, $path, DataParser $parser) {
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1011 1
            $source = null;
1012 1
            if ($this->hasValue($data, $path . '.source.pointer')) {
1013 1
                $source = ['pointer' => $this->parseString($data, $path . '.source.pointer')];
1014 1
            } elseif ($this->hasValue($data, $path . '.source.parameter')) {
1015 1
                $source = ['parameter' => $this->parseString($data, $path . '.source.parameter')];
1016
            }
1017
1018 1
            return new Error(
1019 1
                $this->parseString($data, $path . '.id'),
1020 1
                null,
1021 1
                $this->parseString($data, $path . '.status'),
1022 1
                $this->parseString($data, $path . '.code'),
1023 1
                $this->parseString($data, $path . '.title'),
1024 1
                $this->parseString($data, $path . '.detail'),
1025
                $source,
1026 1
                $this->parseRaw($data, $path . '.meta')
1027
            );
1028 1
        });
1029
1030
        throw new JsonApiException($errors);
0 ignored issues
show
Documentation introduced by
$errors is of type array|null, but the function expects a object<Neomerx\JsonApi\C...ptions\ErrorCollection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1031
    }
1032
}
1033