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 ( 6cf3d3...a1e77f )
by Sergey
06:20 queued 02:49
created

DataParser::parseBool()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 25
ccs 18
cts 18
cp 1
rs 6.7272
cc 7
eloc 17
nc 6
nop 2
crap 7
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\DataParserInterface;
17
use Reva2\JsonApi\Contracts\Decoders\Mapping\ClassMetadataInterface;
18
use Reva2\JsonApi\Contracts\Decoders\Mapping\Factory\MetadataFactoryInterface;
19
use Reva2\JsonApi\Contracts\Decoders\Mapping\ObjectMetadataInterface;
20
use Reva2\JsonApi\Contracts\Decoders\Mapping\PropertyMetadataInterface;
21
use Reva2\JsonApi\Contracts\Decoders\Mapping\ResourceMetadataInterface;
22
use Symfony\Component\PropertyAccess\PropertyAccess;
23
use Symfony\Component\PropertyAccess\PropertyAccessor;
24
25
/**
26
 * Data parser
27
 *
28
 * @package Reva2\JsonApi\Decoders
29
 * @author Sergey Revenko <[email protected]>
30
 */
31
class DataParser implements DataParserInterface
32
{
33
    const ERROR_CODE = 'ee2c1d49-ba40-4077-a6bb-b06baceb3e97';
34
35
    /**
36
     * Current path
37
     *
38
     * @var \SplStack
39
     */
40
    protected $path;
41
42
    /**
43
     * Resource decoders factory
44
     *
45
     * @var MetadataFactoryInterface
46
     */
47
    protected $factory;
48
49
    /**
50
     * @var PropertyAccessor
51
     */
52
    protected $accessor;
53
54
    /**
55
     * Constructor
56
     *
57
     * @param MetadataFactoryInterface $factory
58
     */
59 17
    public function __construct(MetadataFactoryInterface $factory)
60
    {
61 17
        $this->factory = $factory;
62 17
        $this->accessor = PropertyAccess::createPropertyAccessor();
63
        
64 17
        $this->initPathStack();
65 17
    }
66
67
    /**
68
     * @inheritdoc
69
     */
70 13
    public function setPath($path)
71
    {
72 13
        $this->path->push($this->preparePathSegment($path));
73
74 13
        return $this;
75
    }
76
77
    /**
78
     * @inheritdoc
79
     */
80 13
    public function restorePath()
81
    {
82 13
        $this->path->pop();
83
84 13
        return $this;
85
    }
86
87
    /**
88
     * @inheritdoc
89
     */
90 1
    public function getPath()
91
    {
92 1
        $segments = [];
93 1
        foreach ($this->path as $segment) {
94 1
            $segments[] = $segment;
95 1
        }
96
97 1
        return '/' . implode('/', array_reverse($segments));
98
    }
99
100
    /**
101
     * @inheritdoc
102
     */
103 13
    public function hasValue($data, $path)
104
    {
105 13
        return $this->accessor->isReadable($data, $path);
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111 13
    public function getValue($data, $path)
112
    {
113 13
        return $this->accessor->getValue($data, $path);
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119 8
    public function parseString($data, $path)
120
    {
121 7
        $this->setPath($path);
122
123 7
        $pathValue = null;
124 7
        if ($this->hasValue($data, $path)) {
125 7
            $value = $this->getValue($data, $path);
126 7
            if ((null === $value) || (is_string($value))) {
127 7
                $pathValue = $value;
128 7
            } else {
129 1
                throw new \InvalidArgumentException(
130 1
                    sprintf("Value expected to be a string, but %s given", gettype($value)),
131
                    400
132 1
                );
133
            }
134 8
        }
135 7
        $this->restorePath();
136
137 7
        return $pathValue;
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143 4
    public function parseInt($data, $path)
144
    {
145 4
        return $this->parseNumeric($data, $path, 'int');
146
    }
147
148
    /**
149
     * @inheritdoc
150
     */
151 2
    public function parseFloat($data, $path)
152
    {
153 2
        return $this->parseNumeric($data, $path, 'float');
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159 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...
160
    {
161 1
        $this->setPath($path);
162
163 1
        $pathValue = null;
164 1
        if ($this->hasValue($data, $path)) {
165 1
            $pathValue = $this->getValue($data, $path);
166 1
        }
167
168 1
        $this->restorePath();
169
170 1
        return $pathValue;
171
    }
172
173
    /**
174
     * @inheritdoc
175
     */
176 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...
177
    {
178 4
        $this->setPath($path);
179
180 4
        $pathValue = null;
181 4
        if ($this->hasValue($data, $path)) {
182 4
            $pathValue = call_user_func($callback, $this->getValue($data, $path));
183 4
        }
184
185 4
        $this->restorePath();
186
187 4
        return $pathValue;
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193 2
    public function parseBool($data, $path)
194
    {
195 2
        $this->setPath($path);
196
197 2
        $pathValue = null;
198 2
        if ($this->hasValue($data, $path)) {
199 2
            $value = $this->getValue($data, $path);
200 2
            if ((null === $value) || (is_bool($value))) {
201 1
                $pathValue = $value;
202 2
            } elseif (is_string($value)) {
203 2
                $pathValue = (in_array($value, ['true', 'yes', 'y', 'on', 'enabled'])) ? true : false;
204 2
            } elseif (is_numeric($value)) {
205 1
                $pathValue = (bool) $value;
206 1
            } else {
207 1
                throw new \InvalidArgumentException(
208 1
                    sprintf("Value expected to be a boolean, but %s given", gettype($value)),
209
                    400
210 1
                );
211
            }
212 2
        }
213
214 2
        $this->restorePath();
215
216 2
        return $pathValue;
217
    }
218
219
    /**
220
     * @inheritdoc
221
     */
222 2
    public function parseDateTime($data, $path, $format = 'Y-m-d')
223
    {
224 2
        $this->setPath($path);
225
226 2
        $pathValue = null;
227 2
        if ($this->hasValue($data, $path)) {
228 2
            $value = $this->getValue($data, $path);
229 2
            if (null !== $value) {
230 2
                if (is_string($value)) {
231 2
                    $pathValue = \DateTimeImmutable::createFromFormat($format, $value);
232 2
                }
233
234 2
                if (!$pathValue instanceof \DateTimeImmutable) {
235 1
                    throw new \InvalidArgumentException(
236 1
                        sprintf("Value expected to be a date/time string in '%s' format", $format),
237
                        400
238 1
                    );
239
                }
240 2
            }
241 2
        }
242
243 2
        $this->restorePath();
244
245 2
        return $pathValue;
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 5
    public function parseArray($data, $path, \Closure $itemsParser)
252
    {
253 4
        $this->setPath($path);
254
255 4
        $pathValue = null;
256 4
        if ($this->hasValue($data, $path)) {
257 4
            $value = $this->getValue($data, $path);
258 4
            if ((null !== $value) && (false === is_array($value))) {
259 1
                throw new \InvalidArgumentException(
260 1
                    sprintf("Value expected to be an array, but %s given", gettype($value)),
261
                    400
262 1
                );
263 4
            } elseif (is_array($value)) {
264 4
                $pathValue = [];
265 5
                $keys = array_keys($value);
266 4
                foreach ($keys as $key) {
267 4
                    $arrayPath = sprintf("[%s]", $key);
268
269 4
                    $pathValue[$key] = $itemsParser($value, $arrayPath, $this);
270 3
                }
271 3
            }
272 3
        }
273
274 3
        $this->restorePath();
275
276 3
        return $pathValue;
277
    }
278
279
    /**
280
     * Parse data object value at specified path as object of specified class
281
     *
282
     * @param array|object $data
283
     * @param string $path
284
     * @param string $objType
285
     * @return null
286
     */
287 1
    public function parseObject($data, $path, $objType)
288
    {
289 1
        $this->setPath($path);
290
291 1
        $pathValue = null;
292 1
        if ((true === $this->hasValue($data, $path)) &&
293 1
            (null !== ($value = $this->getValue($data, $path)))
294 1
        ) {
295 1
            $this->restorePath();
296
297 1
            $pathValue = $this->parseObjectValue($value, $objType);
298 1
        }
299
300 1
        return $pathValue;
301
    }
302
303
    /**
304
     * @inheritdoc
305
     */
306 4
    public function parseResource($data, $path, $resType)
307
    {
308 4
        $this->setPath($path);
309
310 4
        $pathValue = null;
311 4
        if ((true === $this->hasValue($data, $path)) &&
312 4
            (null !== ($value = $this->getValue($data, $path)))
313 4
        ) {
314 4
            $metadata = $this->factory->getMetadataFor($resType);
315
            /* @var $metadata ResourceMetadataInterface */
316
317 4
            $discClass = $this->getClassByDiscriminator($metadata, $value);
318 4
            if ((null !== $discClass) && ($discClass !== $resType)) {
319 4
                $this->restorePath();
320
321 4
                return $this->parseResource($data, $path, $discClass);
322
            }
323
324 4
            $name = $this->parseString($value, 'type');
325 4
            if ($name !== $metadata->getName()) {
326 1
                throw new \InvalidArgumentException(
327 1
                    sprintf("Value must contain resource of type '%s'", $metadata->getName()),
328
                    409
329 1
                );
330
            }
331
332 4
            $objClass = $metadata->getClassName();
333 4
            $pathValue = new $objClass();
334
335 4
            $this->parseProperty($value, $pathValue, $metadata->getIdMetadata());
336
337 4
            foreach ($metadata->getAttributes() as $attribute) {
338 4
                $this->parseProperty($value, $pathValue, $attribute);
339 4
            }
340
341 4
            foreach ($metadata->getRelationships() as $relationship) {
342 4
                $this->parseProperty($value, $pathValue, $relationship);
343 3
            }
344 3
        }
345
346 3
        $this->restorePath();
347
348 3
        return $pathValue;
349
    }
350
351
    /**
352
     * @inheritdoc
353
     */
354 3
    public function parseDocument($data, $docType)
355
    {
356
        try {
357 3
            $this->initPathStack();
358
359 3
            $metadata = $this->factory->getMetadataFor($docType);
360
            /* @var $metadata \Reva2\JsonApi\Contracts\Decoders\Mapping\DocumentMetadataInterface */
361
362 3
            $docClass = $metadata->getClassName();
363 3
            $doc = new $docClass();
364
365 3
            $this->parseProperty($data, $doc, $metadata->getContentMetadata());
366
367 2
            return $doc;
368 1
        } catch (JsonApiException $e) {
369
            throw $e;
370 1
        } catch (\Exception $e) {
371 1
            throw  $this->convertToApiException($e, 'document');
372
        }
373
    }
374
375
    /**
376
     * @inheritdoc
377
     */
378 3
    public function parseQueryParams($data, $paramsType)
379
    {
380
        try {
381 3
            $this->initPathStack();
382
383 3
            $query = $this->parseObjectValue($data, $paramsType);
384 3
            if (!$query instanceof EncodingParametersInterface) {
385
                throw new \InvalidArgumentException(sprintf(
386
                    "Query parameters object must implement interface %s",
387
                    EncodingParametersInterface::class
388
                ));
389
            }
390
391 3
            return $query;
392
        } catch (JsonApiException $e) {
393
            throw $e;
394
        } catch (\Exception $e) {
395
            throw  $this->convertToApiException($e, 'query');
396
        }
397
    }
398
399
    /**
400
     * Prepare path segment
401
     *
402
     * @param string $path
403
     * @return string
404
     */
405 13
    protected function preparePathSegment($path)
406
    {
407 13
        return trim(preg_replace('~[\/]+~si', '/', str_replace(['.', '[', ']'], '/', (string) $path)), '/');
408
    }
409
410
    /**
411
     * Initialize stack that store current path
412
     */
413 17
    protected function initPathStack()
414
    {
415 17
        $this->path = new \SplStack();
416 17
    }
417
418
    /**
419
     * Parse numeric value
420
     *
421
     * @param mixed $data
422
     * @param string $path
423
     * @param string $type
424
     * @return float|int|null
425
     */
426 5
    protected function parseNumeric($data, $path, $type)
427
    {
428 5
        $this->setPath($path);
429
430 5
        $pathValue = null;
431 5
        if ($this->hasValue($data, $path)) {
432 5
            $value = $this->getValue($data, $path);
433 5
            $rightType = ('int' === $type) ? is_int($value) : is_float($value);
434 5
            if ($rightType) {
435 3
                $pathValue = $value;
436 5
            } elseif (is_numeric($value)) {
437 4
                $pathValue = ('int' === $type) ? (int) $value : (float) $value;
438 5
            } elseif (null !== $value) {
439 2
                throw new \InvalidArgumentException(
440 2
                    sprintf("Value expected to be %s, but %s given", $type, gettype($value)),
441
                    400
442 2
                );
443
            }
444 5
        }
445
446 5
        $this->restorePath();
447
448 5
        return $pathValue;
449
    }
450
451
    /**
452
     * Parse property of specified object
453
     *
454
     * @param object|array $data
455
     * @param object $obj
456
     * @param PropertyMetadataInterface $metadata
457
     */
458 7
    private function parseProperty($data, $obj, PropertyMetadataInterface $metadata)
459
    {
460 7
        $path = $metadata->getDataPath();
461
462 7
        if (false === $this->hasValue($data, $path)) {
463 4
            return;
464
        }
465
466 7
        if ('custom' === $metadata->getDataType()) {
467 4
            $value = $this->parseCallback($data, $path, [$obj, $metadata->getDataTypeParams()]);
468 4
        } else {
469 7
            $value = $this->parsePropertyValue($data, $path, $metadata);
470
        }
471
472 7
        $setter = $metadata->getSetter();
473 7
        if (null !== $setter) {
474 6
            $obj->{$setter}($value);
475 6
        } else {
476 5
            $setter = $metadata->getPropertyName();
477 5
            $obj->{$setter} = $value;
478
        }
479 7
    }
480
481
    /**
482
     * Parse value of specified property
483
     *
484
     * @param object|array $data
485
     * @param string $path
486
     * @param PropertyMetadataInterface $metadata
487
     * @return mixed|null
488
     */
489 7
    private function parsePropertyValue($data, $path, PropertyMetadataInterface $metadata)
490
    {
491 7
        switch ($metadata->getDataType()) {
492 7
            case 'scalar':
493 7
                return $this->parseScalarValue($data, $path, $metadata->getDataTypeParams());
494
495 5
            case 'datetime':
496 1
                $format = $metadata->getDataTypeParams();
497 1
                if (empty($format)) {
498 1
                    $format = 'Y-m-d';
499 1
                }
500
501 1
                return $this->parseDateTime($data, $path, $format);
502
503 5
            case 'array':
504 3
                return $this->parseArrayValue($data, $path, $metadata->getDataTypeParams());
505
506 5
            case 'object':
507 5
                return $this->parseResourceOrObject($data, $path, $metadata->getDataTypeParams());
508
509 1
            case 'raw':
510 1
                return $this->parseRaw($data, $path);
511
512
            default:
513
                throw new \InvalidArgumentException(sprintf(
514
                    "Unsupported property data type '%s'",
515
                    $metadata->getDataType()
516
                ));
517
        }
518
    }
519
520
    /**
521
     * Parse value as JSON API resource or object
522
     *
523
     * @param object|array $data
524
     * @param string $path
525
     * @param string $objClass
526
     * @return mixed|null
527
     */
528 5
    public function parseResourceOrObject($data, $path, $objClass)
529
    {
530 5
        $metadata = $this->factory->getMetadataFor($objClass);
531
532 5
        if ($metadata instanceof ResourceMetadataInterface) {
533 4
            return $this->parseResource($data, $path, $objClass);
534
        } else {
535 1
            return $this->parseObject($data, $path, $objClass);
536
        }
537
    }
538
539
    /**
540
     * Parse value that contains JSON API object
541
     *
542
     * @param object|array $data
543
     * @param string $objType
544
     * @return mixed
545
     */
546 4
    public function parseObjectValue($data, $objType)
547
    {
548 4
        $metadata = $this->factory->getMetadataFor($objType);
549 4
        if (!$metadata instanceof ObjectMetadataInterface) {
550
            throw new \InvalidArgumentException('Invalid object metadata');
551
        }
552
553 4
        $discClass = $this->getClassByDiscriminator($metadata, $data);
554 4
        if ((null !== $discClass) && ($discClass !== $objType)) {
555 1
            return $this->parseObjectValue($data, $discClass);
556
        }
557
558 4
        $objClass = $metadata->getClassName();
559 4
        $obj = new $objClass();
560
561 4
        $properties = $metadata->getProperties();
562 4
        foreach ($properties as $property) {
563 4
            $this->parseProperty($data, $obj, $property);
564 4
        }
565
566 4
        return $obj;
567
    }
568
569
    /**
570
     * Parse value that contains array
571
     *
572
     * @param object|array $data
573
     * @param string $path
574
     * @param array $params
575
     * @return array|null
576
     */
577 3
    public function parseArrayValue($data, $path, array $params)
578
    {
579 3
        $type = $params[0];
580 3
        $typeParams = $params[1];
581
582
        switch ($type) {
583 3
            case 'scalar':
584 1
                return $this->parseArray(
585 1
                    $data,
586 1
                    $path,
587
                    function ($data, $path, DataParser $parser) use ($typeParams) {
588 1
                        return $parser->parseScalarValue($data, $path, $typeParams);
589
                    }
590 1
                );
591
592 3
            case 'datetime':
593 1
                $format = (!empty($typeParams)) ? $typeParams : 'Y-m-d';
594 1
                return $this->parseArray(
595 1
                    $data,
596 1
                    $path,
597
                    function ($data, $path, DataParser $parser) use ($format) {
598 1
                        return $parser->parseDateTime($data, $path, $format);
599
                    }
600 1
                );
601
602 3
            case 'object':
603 3
                return $this->parseArray(
604 3
                    $data,
605 3
                    $path,
606
                    function ($data, $path, DataParser $parser) use ($typeParams) {
607 3
                        return $parser->parseResourceOrObject($data, $path, $typeParams);
608
                    }
609 3
                );
610
611 1
            case 'array':
612 1
                return $this->parseArray(
613 1
                    $data,
614 1
                    $path,
615
                    function ($data, $path, DataParser $parser) use ($typeParams) {
616 1
                        return $parser->parseArrayValue($data, $path, $typeParams);
617
                    }
618 1
                );
619
620 1
            case 'raw':
621 1
                return $this->parseArray(
622 1
                    $data,
623 1
                    $path,
624 1
                    function ($data, $path, DataParser $parser) {
625 1
                        return $parser->parseRaw($data, $path);
626
                    }
627 1
                );
628
629
            default:
630
                throw new \InvalidArgumentException(sprintf(
631
                    "Unsupported array item type '%s' specified",
632
                    $type
633
                ));
634
        }
635
    }
636
637
    /**
638
     * Parse scalar value
639
     *
640
     * @param object|array $data
641
     * @param string $path
642
     * @param string $type
643
     * @return bool|float|int|null|string
644
     */
645 7
    public function parseScalarValue($data, $path, $type)
646
    {
647
        switch ($type) {
648 7
            case 'string':
649 5
                return $this->parseString($data, $path);
650
651 3
            case 'bool':
652 3
            case 'boolean':
653 1
                return $this->parseBool($data, $path);
654
655 3
            case 'int':
656 3
            case 'integer':
657 3
                return $this->parseInt($data, $path);
658
659 1
            case 'float':
660 1
            case 'double':
661 1
                return $this->parseFloat($data, $path);
662
663
            default:
664
                throw new \InvalidArgumentException(sprintf("Unsupported scalar type '%s' specified", $type));
665
        }
666
    }
667
668
    /**
669
     * Convert any exception to JSON API exception
670
     *
671
     * @param \Exception $e
672
     * @param string $objType
673
     * @return JsonApiException
674
     */
675 1
    private function convertToApiException(\Exception $e, $objType)
676
    {
677 1
        $status = $e->getCode();
678 1
        $message = 'Failed to parse request';
679 1
        if (empty($status)) {
680
            $message = 'Internal server error';
681
            $status = 500;
682
        }
683
684 1
        $source = null;
685
        switch ($objType) {
686 1
            case 'document':
687 1
                $source = ['pointer' => $this->getPath()];
688 1
                break;
689
690
            case 'query':
691
                $source = ['parameter' => $this->getPath()];
692
                break;
693
        }
694
695 1
        $error = new Error(rand(), null, $status, self::ERROR_CODE, $message, $e->getMessage(), $source);
696
697 1
        return new JsonApiException($error, $status, $e);
698
    }
699
700
    /**
701
     * Returns appropriate discriminator class for specified data
702
     *
703
     * @param ClassMetadataInterface $metadata
704
     * @param array|object $data
705
     * @return string|null
706
     */
707 7
    private function getClassByDiscriminator(ClassMetadataInterface $metadata, $data)
708
    {
709 7
        if (null === ($discField = $metadata->getDiscriminatorField())) {
710 7
            return null;
711
        }
712
713 5
        $discValue = $this->parseString($data, $discField->getDataPath());
714 5
        if (null === $discValue) {
715
            return null;
716
        }
717
718 5
        return $metadata->getDiscriminatorClass($discValue);
719
    }
720
}
721