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 ( b5d278...796de0 )
by Sergey
17s queued 11s
created

DataParser::parseRelationship()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 39

Duplication

Lines 18
Ratio 46.15 %

Code Coverage

Tests 23
CRAP Score 8

Importance

Changes 0
Metric Value
dl 18
loc 39
ccs 23
cts 23
cp 1
rs 8.0515
c 0
b 0
f 0
cc 8
nc 13
nop 3
crap 8
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 19
    public function __construct(MetadataFactoryInterface $factory, CallbackResolverInterface $callbackResolver)
80
    {
81 19
        $this->factory = $factory;
82 19
        $this->callbackResolver = $callbackResolver;
83 19
        $this->accessor = PropertyAccess::createPropertyAccessorBuilder()
84 19
            ->enableExceptionOnInvalidIndex()
85 19
            ->getPropertyAccessor();
86
        
87 19
        $this->initPathStack();
88 19
        $this->initContext();
89 19
    }
90
91
    /**
92
     * @inheritdoc
93
     */
94 14
    public function setPath($path)
95
    {
96 14
        $this->path->push($this->preparePathSegment($path));
97
98 14
        return $this;
99
    }
100
101
    /**
102
     * @inheritdoc
103
     */
104 14
    public function restorePath()
105
    {
106 14
        $this->path->pop();
107
108 14
        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 14
    public function hasValue($data, $path)
147
    {
148 14
        return $this->accessor->isReadable($data, $path);
149
    }
150
151
    /**
152
     * @inheritdoc
153
     */
154 14
    public function getValue($data, $path)
155
    {
156 14
        return $this->accessor->getValue($data, $path);
157
    }
158
159
    /**
160
     * @inheritdoc
161
     */
162 8
    public function parseString($data, $path)
163
    {
164 8
        $this->setPath($path);
165
166 8
        $pathValue = null;
167 8
        if ($this->hasValue($data, $path)) {
168 8
            $value = $this->getValue($data, $path);
169 8
            if ((null === $value) || (is_string($value))) {
170 8
                $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 8
        $this->restorePath();
179
180 8
        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 1
    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 1
        $this->setPath($path);
205
206 1
        $pathValue = null;
207 1
        if ($this->hasValue($data, $path)) {
208 1
            $pathValue = $this->getValue($data, $path);
209
        }
210
211 1
        $this->restorePath();
212
213 1
        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 6
    public function parseArray($data, $path, \Closure $itemsParser)
295
    {
296 6
        $this->setPath($path);
297
298 6
        $pathValue = null;
299 6
        if ($this->hasValue($data, $path)) {
300 4
            $value = $this->getValue($data, $path);
301 4 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 4
            $pathValue = [];
309 4
            $keys = array_keys($value);
310 4
            foreach ($keys as $key) {
311 4
                $arrayPath = sprintf("[%s]", $key);
312
313 4
                $pathValue[$key] = $itemsParser($value, $arrayPath, $this);
314
            }
315
        }
316
317 5
        $this->restorePath();
318
319 5
        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 4
    public function parseDocument($data, $docType)
422
    {
423
        try {
424 4
            $this->initPathStack();
425 4
            $this->initContext();
426
427 4
            $metadata = $this->factory->getMetadataFor($docType);
428 4
            if (!$metadata instanceof DocumentMetadataInterface) {
429 1
                throw new \InvalidArgumentException(sprintf("Failed to parse %s as JSON API document", $docType));
430
            }
431
432
            /* @var $metadata \Reva2\JsonApi\Contracts\Decoders\Mapping\DocumentMetadataInterface */
433
434 3
            $docClass = $metadata->getClassName();
435 3
            $doc = new $docClass();
436
437 3
            $this->parseLinkedResources($data);
438
439 3
            $this->parseProperty($data, $doc, $metadata->getContentMetadata());
440
441 2
            $docMetadata = $metadata->getMetadata();
442 2
            if ($docMetadata !== null) {
443 1
                $this->parseProperty($data, $doc, $metadata->getMetadata());
444
            }
445
446 2
            return $doc;
447 2
        } catch (JsonApiException $e) {
448
            throw $e;
449 2
        } catch (\Exception $e) {
450 2
            throw  $this->convertToApiException($e, 'document');
451
        }
452
    }
453
454
    /**
455
     * @inheritdoc
456
     */
457 3
    public function parseQueryParams($data, $paramsType)
458
    {
459
        try {
460 3
            $this->initPathStack();
461 3
            $this->initContext();
462
463 3
            $query = $this->parseObjectValue($data, $paramsType);
464 3
            if (!$query instanceof EncodingParametersInterface) {
465
                throw new \InvalidArgumentException(sprintf(
466
                    "Query parameters object must implement interface %s",
467
                    EncodingParametersInterface::class
468
                ));
469
            }
470
471 3
            return $query;
472
        } catch (JsonApiException $e) {
473
            throw $e;
474
        } catch (\Exception $e) {
475
            throw  $this->convertToApiException($e, 'query');
476
        }
477
    }
478
479
    /**
480
     * Prepare path segment
481
     *
482
     * @param string $path
483
     * @return string
484
     */
485 14
    protected function preparePathSegment($path)
486
    {
487 14
        return trim(preg_replace('~[\/]+~si', '/', str_replace(['.', '[', ']'], '/', (string) $path)), '/');
488
    }
489
490
    /**
491
     * Initialize stack that store current path
492
     */
493 19
    protected function initPathStack()
494
    {
495 19
        $this->path = new \SplStack();
496 19
    }
497
498
    /**
499
     * Initialize decoder context
500
     */
501 19
    protected function initContext()
502
    {
503 19
        $this->context = new Context();
504 19
    }
505
506
    /**
507
     * Parse numeric value
508
     *
509
     * @param mixed $data
510
     * @param string $path
511
     * @param string $type
512
     * @return float|int|null
513
     */
514 5
    protected function parseNumeric($data, $path, $type)
515
    {
516 5
        $this->setPath($path);
517
518 5
        $pathValue = null;
519 5
        if ($this->hasValue($data, $path)) {
520 5
            $value = $this->getValue($data, $path);
521 5
            $rightType = ('int' === $type) ? is_int($value) : is_float($value);
522 5
            if ($rightType) {
523 4
                $pathValue = $value;
524 4
            } elseif (is_numeric($value)) {
525 4
                $pathValue = ('int' === $type) ? (int) $value : (float) $value;
526 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...
527 2
                throw new \InvalidArgumentException(
528 2
                    sprintf("Value expected to be %s, but %s given", $type, gettype($value)),
529 2
                    400
530
                );
531
            }
532
        }
533
534 5
        $this->restorePath();
535
536 5
        return $pathValue;
537
    }
538
539
    /**
540
     * Parse property of specified object
541
     *
542
     * @param object|array $data
543
     * @param object $obj
544
     * @param PropertyMetadataInterface $metadata
545
     * @param string|null $path
546
     */
547 7
    private function parseProperty($data, $obj, PropertyMetadataInterface $metadata, $path = null)
548
    {
549 7
        if (null === $path) {
550 7
            $path = $metadata->getDataPath();
551
        }
552
553 7
        if ((false === $this->hasValue($data, $path)) ||
554 7
            (true === $this->isExcludedProperty($metadata))
555
        ) {
556 6
            return;
557
        }
558
559 7
        if ('custom' === $metadata->getDataType()) {
560 4
            $value = $this->parseCallback($data, $path, [$obj, $metadata->getDataTypeParams()]);
561
        } else {
562 6
            $value = $this->parsePropertyValue($data, $path, $metadata);
563
        }
564
565 7
        if (null !== ($converter = $metadata->getConverter())) {
566 3
            $callback = $this->callbackResolver->resolveCallback($converter);
567
568 3
            $value = call_user_func($callback, $value);
569
        }
570
571 7
        $this->setProperty($obj, $value, $metadata);
572 7
    }
573
574
    /**
575
     * Parse value of specified property
576
     *
577
     * @param object|array $data
578
     * @param string $path
579
     * @param PropertyMetadataInterface $metadata
580
     * @return mixed|null
581
     */
582 6
    private function parsePropertyValue($data, $path, PropertyMetadataInterface $metadata)
583
    {
584 6
        switch ($metadata->getDataType()) {
585 6
            case 'scalar':
586 6
                return $this->parseScalarValue($data, $path, $metadata->getDataTypeParams());
587
588 5
            case 'datetime':
589 1
                $format = $metadata->getDataTypeParams();
590 1
                if (empty($format)) {
591 1
                    $format = 'Y-m-d';
592
                }
593
594 1
                return $this->parseDateTime($data, $path, $format);
595
596 5
            case 'array':
597 3
                return $this->parseArrayValue($data, $path, $metadata->getDataTypeParams(), $metadata);
598
599 5
            case 'object':
600 5
                return $this->parseResourceOrObject($data, $path, $metadata->getDataTypeParams(), $metadata);
601
602 1
            case 'raw':
603 1
                return $this->parseRaw($data, $path);
604
605
            default:
606
                throw new \InvalidArgumentException(sprintf(
607
                    "Unsupported property data type '%s'",
608
                    $metadata->getDataType()
609
                ));
610
        }
611
    }
612
613
    /**
614
     * Parse value as JSON API resource or object
615
     *
616
     * @param object|array $data
617
     * @param string $path
618
     * @param string $objClass
619
     * @param PropertyMetadataInterface $propMetadata
620
     * @return mixed|null
621
     */
622 5
    public function parseResourceOrObject($data, $path, $objClass, PropertyMetadataInterface $propMetadata)
623
    {
624 5
        $objMetadata = $this->factory->getMetadataFor($objClass);
625 5
        if ($objMetadata instanceof ResourceMetadataInterface) {
626 4
            $loader = null;
627 4
            foreach ($propMetadata->getLoaders() as $group => $groupLoader) {
628
                if (in_array($group, $this->serializationGroups)) {
629
                    $loader = $groupLoader;
630
                }
631
            }
632
633 4
            return $this->parseResource($data, $path, $objClass, $loader);
634
        }
635
636 2
        return $this->parseObject($data, $path, $objClass);
637
    }
638
639
    /**
640
     * Parse value that contains JSON API object
641
     *
642
     * @param object|array $data
643
     * @param string $objType
644
     * @return mixed
645
     */
646 5
    public function parseObjectValue($data, $objType)
647
    {
648 5
        $metadata = $this->factory->getMetadataFor($objType);
649 5
        if (!$metadata instanceof ObjectMetadataInterface) {
650
            throw new \InvalidArgumentException('Invalid object metadata');
651
        }
652
653 5
        $discClass = $this->getClassByDiscriminator($metadata, $data);
654 5
        if ((null !== $discClass) && ($discClass !== $objType)) {
655 1
            return $this->parseObjectValue($data, $discClass);
656
        }
657
658 5
        $objClass = $metadata->getClassName();
659 5
        $obj = new $objClass();
660
661 5
        $properties = $metadata->getProperties();
662 5
        foreach ($properties as $property) {
663 5
            $this->parseProperty($data, $obj, $property);
664
        }
665
666 5
        return $obj;
667
    }
668
669
    /**
670
     * Parse value that contains array
671
     *
672
     * @param object|array $data
673
     * @param string $path
674
     * @param array $params
675
     * @param PropertyMetadataInterface $propMetadata
676
     * @return array|null
677
     */
678 3
    public function parseArrayValue($data, $path, array $params, PropertyMetadataInterface $propMetadata)
679
    {
680 3
        $type = $params[0];
681 3
        $typeParams = $params[1];
682
683
        switch ($type) {
684 3
            case 'scalar':
685 1
                return $this->parseArray(
686 1
                    $data,
687 1
                    $path,
688 1
                    function ($data, $path, DataParser $parser) use ($typeParams) {
689 1
                        return $parser->parseScalarValue($data, $path, $typeParams);
690 1
                    }
691
                );
692
693 3
            case 'datetime':
694 1
                $format = (!empty($typeParams)) ? $typeParams : 'Y-m-d';
695 1
                return $this->parseArray(
696 1
                    $data,
697 1
                    $path,
698 1
                    function ($data, $path, DataParser $parser) use ($format) {
699 1
                        return $parser->parseDateTime($data, $path, $format);
700 1
                    }
701
                );
702
703 3
            case 'object':
704 3
                return $this->parseArray(
705 3
                    $data,
706 3
                    $path,
707 3
                    function ($data, $path, DataParser $parser) use ($typeParams, $propMetadata) {
708 3
                        return $parser->parseResourceOrObject($data, $path, $typeParams, $propMetadata);
709 3
                    }
710
                );
711
712 1
            case 'array':
713 1
                return $this->parseArray(
714 1
                    $data,
715 1
                    $path,
716 1
                    function ($data, $path, DataParser $parser) use ($typeParams, $propMetadata) {
717 1
                        return $parser->parseArrayValue($data, $path, $typeParams, $propMetadata);
718 1
                    }
719
                );
720
721 1
            case 'raw':
722 1
                return $this->parseArray(
723 1
                    $data,
724 1
                    $path,
725 1
                    function ($data, $path, DataParser $parser) {
726 1
                        return $parser->parseRaw($data, $path);
727 1
                    }
728
                );
729
730
            default:
731
                throw new \InvalidArgumentException(sprintf(
732
                    "Unsupported array item type '%s' specified",
733
                    $type
734
                ));
735
        }
736
    }
737
738
    /**
739
     * Parse scalar value
740
     *
741
     * @param object|array $data
742
     * @param string $path
743
     * @param string $type
744
     * @return bool|float|int|null|string
745
     */
746 6
    public function parseScalarValue($data, $path, $type)
747
    {
748
        switch ($type) {
749 6
            case 'string':
750 5
                return $this->parseString($data, $path);
751
752 3
            case 'bool':
753 3
            case 'boolean':
754 1
                return $this->parseBool($data, $path);
755
756 3
            case 'int':
757 3
            case 'integer':
758 3
                return $this->parseInt($data, $path);
759
760 1
            case 'float':
761 1
            case 'double':
762 1
                return $this->parseFloat($data, $path);
763
764
            default:
765
                throw new \InvalidArgumentException(sprintf("Unsupported scalar type '%s' specified", $type));
766
        }
767
    }
768
769
    /**
770
     * Convert any exception to JSON API exception
771
     *
772
     * @param \Exception $e
773
     * @param string $objType
774
     * @return JsonApiException
775
     */
776 2
    private function convertToApiException(\Exception $e, $objType)
777
    {
778 2
        $status = $e->getCode();
779 2
        $message = 'Failed to parse request';
780 2
        if (empty($status)) {
781 1
            $message = 'Internal server error';
782 1
            $status = 500;
783
        }
784
785 2
        $source = null;
786
        switch ($objType) {
787 2
            case 'document':
788 2
                $source = ['pointer' => $this->getPath()];
789 2
                break;
790
791
            case 'query':
792
                $source = ['parameter' => $this->getPath()];
793
                break;
794
        }
795
796 2
        $error = new Error(rand(), null, $status, self::ERROR_CODE, $message, $e->getMessage(), $source);
797
798 2
        return new JsonApiException($error, $status, $e);
799
    }
800
801
    /**
802
     * Returns appropriate discriminator class for specified data
803
     *
804
     * @param ClassMetadataInterface $metadata
805
     * @param array|object $data
806
     * @return string|null
807
     */
808 8
    private function getClassByDiscriminator(ClassMetadataInterface $metadata, $data)
809
    {
810 8
        if (null === ($discField = $metadata->getDiscriminatorField())) {
811 6
            return null;
812
        }
813
814 6
        $discValue = $this->parseString($data, $discField->getDataPath());
815 6
        if (empty($discValue)) {
816 1
            $this->setPath($discField->getDataPath());
817
818 1
            throw new \InvalidArgumentException("Field value required and can not be empty", 422);
819
        }
820
821 5
        return $metadata->getDiscriminatorClass($discValue);
822
    }
823
824
    /**
825
     * Check if specified property should be excluded
826
     *
827
     * @param PropertyMetadataInterface $metadata
828
     * @return bool
829
     */
830 7
    private function isExcludedProperty(PropertyMetadataInterface $metadata)
831
    {
832 7
        $propertyGroups = $metadata->getGroups();
833 7
        foreach ($propertyGroups as $group) {
834 7
            if (in_array($group, $this->serializationGroups)) {
835 7
                return false;
836
            }
837
        }
838
839
        return true;
840
    }
841
842
    /**
843
     *
844
     * @param mixed $data
845
     */
846 3
    private function parseLinkedResources($data)
847
    {
848 3
        if (false === $this->hasValue($data, 'included')) {
849 2
            return;
850
        }
851
852 1
        $linkedData = $this->getValue($data, 'included');
853 1
        if (!is_array($linkedData)) {
854
            return;
855
        }
856
857 1
        foreach ($linkedData as $idx => $resData) {
858 1
            $id = null;
859 1
            if ($this->hasValue($resData, 'id')) {
860 1
                $id = $this->getValue($resData, 'id');
861
            }
862
863 1
            $type = null;
864 1
            if ($this->hasValue($resData, 'type')) {
865 1
                $type = $this->getValue($resData, 'type');
866
            }
867
868 1
            if (empty($id) || empty($type) || !is_string($id) || !is_string($type)) {
869
                continue;
870
            }
871
872 1
            $this->context->addLinkedData($type, $id, $idx, $resData);
873
        }
874 1
    }
875
876
    /**
877
     * Parse specified relationship data
878
     *
879
     * @param mixed $data
880
     * @param mixed $pathValue
881
     * @param PropertyMetadataInterface $relationship
882
     * @return void
883
     */
884 4
    private function parseRelationship($data, $pathValue, PropertyMetadataInterface $relationship)
885
    {
886 4
        if ('array' === $relationship->getDataType()) {
887 3
            return $this->parseArrayRelationship($data, $pathValue, $relationship);
888
        }
889
890 4
        $resType = null;
891 4
        if ($this->hasValue($data, $relationship->getDataPath() . '.type')) {
892 4
            $resType = $this->parseString($data, $relationship->getDataPath() . '.type');
893
        }
894
895 4
        $resId = null;
896 4
        if ($this->hasValue($data, $relationship->getDataPath() . '.id')) {
897 4
            $resId = $this->getValue($data, $relationship->getDataPath() . '.id');
898
        }
899
900 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...
901 4
            (null !== $resType) &&
902 4
            (null !== ($res = $this->context->getResource($resType, $resId)))
903
        ) {
904 1
            return $this->setProperty($pathValue, $res, $relationship);
905
        }
906
907
908 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...
909 1
            $idx = $this->context->getLinkedDataIndex($resType, $resId);
910 1
            $prevPath = $this->path;
911
912 1
            $this->initPathStack();
913 1
            $this->setPath('included')->setPath($idx);
914
915 1
            $this->parseProperty([$idx => $linkedData], $pathValue, $relationship, '[' . $idx . ']');
916 1
            $this->path = $prevPath;
917
918 1
            return;
919
        }
920
921 4
        $this->parseProperty($data, $pathValue, $relationship);
922 3
    }
923
924
    /**
925
     * Parse data for relationship that contains array of resources
926
     *
927
     * @param mixed $data
928
     * @param mixed $pathValue
929
     * @param PropertyMetadataInterface $relationship
930
     */
931
    private function parseArrayRelationship($data, $pathValue, PropertyMetadataInterface $relationship)
932
    {
933
934 3
        $data = $this->parseArray($data, $relationship->getDataPath(), function ($data, $path) use ($relationship) {
935 1
            $resType = null;
936 1
            if ($this->hasValue($data, $path . '.type')) {
937 1
                $resType = $this->parseString($data, $path . '.type');
938
            }
939
940 1
            $resId = null;
941 1
            if ($this->hasValue($data, $path .'.id')) {
942 1
                $resId = $this->getValue($data, $path . '.id');
943
            }
944
945 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...
946 1
                (null !== $resId) &&
947 1
                (null !== ($parsed = $this->context->getResource($resType, $resId)))
948
            ) {
949 1
                return $parsed;
950
            }
951
952 1
            $params = $relationship->getDataTypeParams();
953
954 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...
955 1
                $idx = $this->context->getLinkedDataIndex($resType, $resId);
956
957 1
                $prevPath = $this->path;
958 1
                $this->initPathStack();
959 1
                $this->setPath('included')->setPath($idx);
960
961 1
                $parsed = $this->parseResourceOrObject(
962 1
                    [$idx => $linkedData],
963 1
                    '[' . $idx .']',
964 1
                    $params[1],
965 1
                    $relationship
966
                );
967
968 1
                $this->path = $prevPath;
969
970 1
                return $parsed;
971
            }
972
973
            return $this->parseResourceOrObject($data, $path, $params[1], $relationship);
974 3
        });
975
976 3
        $this->setProperty($pathValue, $data, $relationship);
977 3
    }
978
979
    /**
980
     * Sets property value using metadata
981
     *
982
     * @param mixed $obj
983
     * @param mixed $value
984
     * @param PropertyMetadataInterface $metadata
985
     */
986 7
    private function setProperty($obj, $value, PropertyMetadataInterface $metadata)
987
    {
988 7
        $setter = $metadata->getSetter();
989 7
        if (null !== $setter) {
990 6
            $obj->{$setter}($value);
991
        } else {
992 5
            $setter = $metadata->getPropertyName();
993 5
            $obj->{$setter} = $value;
994
        }
995 7
    }
996
}
997