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 ( 393393...642c1a )
by Sergey
11s
created

AnnotationLoader::parseLoaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
ccs 6
cts 9
cp 0.6667
rs 9.7
cc 3
nc 3
nop 1
crap 3.3332
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
12
namespace Reva2\JsonApi\Decoders\Mapping\Loader;
13
14
use Doctrine\Common\Annotations\Reader;
15
use Doctrine\ORM\Proxy\Proxy;
16
use Reva2\JsonApi\Annotations\Attribute;
17
use Reva2\JsonApi\Annotations\ApiDocument;
18
use Reva2\JsonApi\Annotations\Id;
19
use Reva2\JsonApi\Annotations\ApiResource;
20
use Reva2\JsonApi\Annotations\ApiObject;
21
use Reva2\JsonApi\Annotations\Content as ApiContent;
22
use Reva2\JsonApi\Annotations\Loader;
23
use Reva2\JsonApi\Annotations\Metadata;
24
use Reva2\JsonApi\Annotations\Property;
25
use Reva2\JsonApi\Annotations\Relationship;
26
use Reva2\JsonApi\Annotations\VirtualAttribute;
27
use Reva2\JsonApi\Annotations\VirtualProperty;
28
use Reva2\JsonApi\Annotations\VirtualRelationship;
29
use Reva2\JsonApi\Contracts\Decoders\Mapping\Loader\LoaderInterface;
30
use Reva2\JsonApi\Contracts\Decoders\Mapping\ObjectMetadataInterface;
31
use Reva2\JsonApi\Decoders\Mapping\ClassMetadata;
32
use Reva2\JsonApi\Decoders\Mapping\DocumentMetadata;
33
use Reva2\JsonApi\Decoders\Mapping\ObjectMetadata;
34
use Reva2\JsonApi\Decoders\Mapping\PropertyMetadata;
35
use Reva2\JsonApi\Decoders\Mapping\ResourceMetadata;
36
37
/**
38
 * Loads JSON API metadata using a Doctrine annotations
39
 *
40
 * @package Reva2\JsonApi\Decoders\Mapping\Loader
41
 * @author Sergey Revenko <[email protected]>
42
 */
43
class AnnotationLoader implements LoaderInterface
44
{
45
    /**
46
     * @var Reader
47
     */
48
    protected $reader;
49
50
    /**
51
     * Constructor
52
     *
53
     * @param Reader $reader
54
     */
55 28
    public function __construct(Reader $reader)
56
    {
57 28
        $this->reader = $reader;
58 28
    }
59
60
    /**
61
     * @inheritdoc
62
     */
63 18
    public function loadClassMetadata(\ReflectionClass $class)
64
    {
65 18
        if ($class->implementsInterface(Proxy::class)) {
66
            return $this->loadClassMetadata($class->getParentClass());
67
        }
68
69 18
        if (null !== ($resource = $this->reader->getClassAnnotation($class, ApiResource::class))) {
70
            /* @var $resource ApiResource */
71 10
            return $this->loadResourceMetadata($resource, $class);
72 11
        } elseif (null !== ($document = $this->reader->getClassAnnotation($class, ApiDocument::class))) {
73
            /* @var $document ApiDocument */
74 4
            return $this->loadDocumentMetadata($document, $class);
75
        } else {
76 9
            $object = $this->reader->getClassAnnotation($class, ApiObject::class);
77
78 9
            return $this->loadObjectMetadata($class, $object);
79
        }
80
    }
81
82
    /**
83
     * Parse JSON API resource metadata
84
     *
85
     * @param ApiResource $resource
86
     * @param \ReflectionClass $class
87
     * @return ResourceMetadata
88
     */
89 10
    private function loadResourceMetadata(ApiResource $resource, \ReflectionClass $class)
90
    {
91 10
        $metadata = new ResourceMetadata($class->name);
92 10
        $metadata->setName($resource->name);
93 10
        $metadata->setLoader($resource->loader);
94
95 10
        $properties = $class->getProperties();
96 10
        foreach ($properties as $property) {
97 10
            if ($property->getDeclaringClass()->name !== $class->name) {
98 5
                continue;
99
            }
100
101 10
            foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
102 10
                if ($annotation instanceof Attribute) {
103 10
                    $metadata->addAttribute($this->loadPropertyMetadata($annotation, $property));
104 10
                } elseif ($annotation instanceof Relationship) {
105 10
                    $metadata->addRelationship($this->loadPropertyMetadata($annotation, $property));
106 10
                } elseif ($annotation instanceof Id) {
107 10
                    $metadata->setIdMetadata($this->loadPropertyMetadata($annotation, $property));
108
                }
109
            }
110
        }
111
112 10
        $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
113 10
        foreach ($methods as $method) {
114 9
            if ($method->getDeclaringClass()->name !== $class->name) {
115 5
                continue;
116
            }
117
118 9
            foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
119 9
                if ($annotation instanceof VirtualAttribute) {
120 9
                    $metadata->addAttribute($this->loadVirtualMetadata($annotation, $method));
121 9
                } else if ($annotation instanceof VirtualRelationship) {
122 9
                    $metadata->addRelationship($this->loadVirtualMetadata($annotation, $method));
123
                }
124
            }
125
        }
126
127 10
        $this->loadDiscriminatorMetadata($resource, $metadata);
128
129 10
        return $metadata;
130
    }
131
132
    /**
133
     * @param \ReflectionClass $class
134
     * @param ApiObject|null $object
135
     * @return ObjectMetadata
136
     */
137 9
    private function loadObjectMetadata(\ReflectionClass $class, ApiObject $object = null)
138
    {
139 9
        $metadata = new ObjectMetadata($class->name);
140
141 9
        $properties = $class->getProperties();
142 9 View Code Duplication
        foreach ($properties as $property) {
143 9
            if ($property->getDeclaringClass()->name !== $class->name) {
144 5
                continue;
145
            }
146
147 9
            $annotation = $this->reader->getPropertyAnnotation($property, Property::class);
148
            /* @var $annotation Property */
149 9
            if (null === $annotation) {
150
                continue;
151
            }
152
153 9
            $metadata->addProperty($this->loadPropertyMetadata($annotation, $property));
154
        }
155
156 7
        $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
157 7 View Code Duplication
        foreach ($methods as $method) {
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...
158 6
            if ($method->getDeclaringClass()->name !== $class->name) {
159 5
                continue;
160
            }
161
162 6
            $annotation = $this->reader->getMethodAnnotation($method, VirtualProperty::class);
163
            /* @var $annotation VirtualProperty */
164 6
            if ($annotation === null) {
165 6
                continue;
166
            }
167
168 2
            $metadata->addProperty($this->loadVirtualMetadata($annotation, $method));
169
        }
170
171 7
        if (null !== $object) {
172 3
            $this->loadDiscriminatorMetadata($object, $metadata);
173
        }
174
175 7
        return $metadata;
176
    }
177
178
    /**
179
     * Parse JSON API document metadata
180
     *
181
     * @param ApiDocument $document
182
     * @param \ReflectionClass $class
183
     * @return DocumentMetadata
184
     */
185 4
    private function loadDocumentMetadata(ApiDocument $document, \ReflectionClass $class)
186
    {
187 4
        $metadata = new DocumentMetadata($class->name);
188 4
        $metadata->setAllowEmpty($document->allowEmpty);
189
190 4
        $properties = $class->getProperties();
191 4
        foreach ($properties as $property) {
192 4
            if ($property->getDeclaringClass()->name !== $class->name) {
193
                continue;
194
            }
195
196
197 4
            foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
198 4
                if ($annotation instanceof ApiContent) {
199 4
                    $metadata->setContentMetadata($this->loadPropertyMetadata($annotation, $property));
200 3
                } elseif ($annotation instanceof Metadata) {
201 4
                    $metadata->setMetadata($this->loadPropertyMetadata($annotation, $property));
202
                }
203
            }
204
        }
205
206 4
        return $metadata;
207
    }
208
209
    /**
210
     * Parse property metadata
211
     *
212
     * @param Property $annotation
213
     * @param \ReflectionProperty $property
214
     * @return PropertyMetadata
215
     */
216 18
    private function loadPropertyMetadata(Property $annotation, \ReflectionProperty $property)
217
    {
218 18
        $metadata = new PropertyMetadata($property->name, $property->class);
219
220 18
        list($dataType, $dataTypeParams) = $this->parseDataType($annotation, $property);
221
222
        $metadata
223 17
            ->setDataType($dataType)
224 17
            ->setDataTypeParams($dataTypeParams)
225 17
            ->setDataPath($this->getDataPath($annotation, $property))
226 17
            ->setConverter($annotation->converter)
227 17
            ->setGroups($annotation->groups)
228 17
            ->setLoaders($this->parseLoaders($annotation->loaders));
229
230 17
        if ($annotation->setter) {
231
            $metadata->setSetter($annotation->setter);
232 17
        } elseif (false === $property->isPublic()) {
233 9
            $setter = 'set' . ucfirst($property->name);
234 9
            if (false === $property->getDeclaringClass()->hasMethod($setter)) {
235 1
                throw new \RuntimeException(sprintf(
236 1
                    "Couldn't find setter for non public property: %s:%s",
237 1
                    $property->class,
238 1
                    $property->name
239
                ));
240
            }
241
242 8
            $metadata->setSetter($setter);
243
        }
244
245 16
        return $metadata;
246
    }
247
248
    /**
249
     * Parse virtual property metadata
250
     *
251
     * @param VirtualProperty $annotation
252
     * @param \ReflectionMethod $method
253
     * @return PropertyMetadata
254
     */
255 11
    private function loadVirtualMetadata(VirtualProperty $annotation, \ReflectionMethod $method)
256
    {
257 11
        if (empty($annotation->name)) {
258
            throw new \InvalidArgumentException(sprintf(
259
                "Virtual property name not specified: %s:%s()",
260
                $method->class,
261
                $method->name
262
            ));
263
        }
264
265 11
        list($dataType, $dataTypeParams) = $this->parseVirtualDataType($annotation, $method);
266
267 11
        $metadata = new PropertyMetadata($annotation->name, $method->class);
268
        $metadata
269 11
            ->setDataType($dataType)
270 11
            ->setDataTypeParams($dataTypeParams)
271 11
            ->setDataPath($this->getVirtualDataPath($annotation, $method))
272 11
            ->setConverter($annotation->converter)
273 11
            ->setGroups($annotation->groups)
274 11
            ->setSetter($method->name);
275
276 11
        return $metadata;
277
    }
278
279
    /**
280
     * Parse property data type
281
     *
282
     * @param Property $annotation
283
     * @param \ReflectionProperty $property
284
     * @return array
285
     */
286 18
    private function parseDataType(Property $annotation, \ReflectionProperty $property)
287
    {
288 18
        if (!empty($annotation->parser)) {
289 6 View Code Duplication
            if (!$property->getDeclaringClass()->hasMethod($annotation->parser)) {
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...
290 1
                throw new \InvalidArgumentException(sprintf(
291 1
                    "Custom parser function %s:%s() for property '%s' does not exist",
292 1
                    $property->class,
293 1
                    $annotation->parser,
294 1
                    $property->name
295
                ));
296
            }
297 5
            return ['custom', $annotation->parser];
298 17
        } elseif (!empty($annotation->type)) {
299 15
            return $this->parseDataTypeString($annotation->type);
300 15
        } elseif (preg_match('~@var\s(.*?)\s~si', $property->getDocComment(), $matches)) {
301 15
            return $this->parseDataTypeString($matches[1]);
302
        } else {
303 2
            return ['raw', null];
304
        }
305
    }
306
307
    /**
308
     * Parse virtual property data type
309
     *
310
     * @param VirtualProperty $annotation
311
     * @param \ReflectionMethod $method
312
     * @return array
313
     */
314 11
    private function parseVirtualDataType(VirtualProperty $annotation, \ReflectionMethod $method)
315
    {
316 11
        if (!empty($annotation->parser)) {
317 View Code Duplication
            if (!$method->getDeclaringClass()->hasMethod($annotation->parser)) {
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...
318
                throw new \InvalidArgumentException(sprintf(
319
                    "Custom parser function %s:%s() for virtual property '%s' does not exist",
320
                    $method->class,
321
                    $annotation->parser,
322
                    $annotation->name
323
                ));
324
            }
325
            return ['custom', $annotation->parser];
326 11
        } elseif (!empty($annotation->type)) {
327 11
            return $this->parseDataTypeString($annotation->type);
328
        } else {
329
            return ['raw', null];
330
        }
331
    }
332
333
    /**
334
     * Parse data type string
335
     *
336
     * @param string $type
337
     * @return array
338
     */
339 17
    private function parseDataTypeString($type)
340
    {
341 17
        $params = null;
342
343 17
        if ('raw' === $type) {
344 2
            $dataType = 'raw';
345 2
            $params = null;
346 17
        } elseif ($this->isScalarDataType($type)) {
347 16
            $dataType = 'scalar';
348 16
            $params = $type;
349 13
        } elseif (preg_match('~^DateTime(<(.*?)>)?$~', $type, $matches)) {
350 2
            $dataType = 'datetime';
351 2
            if (3 === count($matches)) {
352 2
                $params = $matches[2];
353
            }
354
        } elseif (
355 13
            (preg_match('~Array(<(.*?)>)?$~si', $type, $matches)) ||
356 13
            (preg_match('~^(.*?)\[\]$~si', $type, $matches))
357
        ) {
358 6
            $dataType = 'array';
359 6
            if (3 === count($matches)) {
360 5
                $params = $this->parseDataTypeString($matches[2]);
361 3
            } elseif (2 === count($matches)) {
362 3
                $params = $this->parseDataTypeString($matches[1]);
363
            } else {
364 6
                $params = ['raw', null];
365
            }
366
        } else {
367 13
            $type = ltrim($type, '\\');
368
369 13
            if (!class_exists($type)) {
370
                throw new \InvalidArgumentException(sprintf(
371
                    "Unknown object type '%s' specified",
372
                    $type
373
                ));
374
            }
375
376 13
            $dataType = 'object';
377 13
            $params = $type;
378
        }
379
380 17
        return [$dataType, $params];
381
    }
382
383
    /**
384
     * Returns true if specified type scalar. False otherwise.
385
     *
386
     * @param string $type
387
     * @return bool
388
     */
389 17
    private function isScalarDataType($type)
390
    {
391 17
        return in_array($type, ['string', 'bool', 'boolean', 'int', 'integer', 'float', 'double']);
392
    }
393
394
    /**
395
     * Load discriminator metadata
396
     *
397
     * @param ApiObject $object
398
     * @param ClassMetadata $metadata
399
     */
400 12
    private function loadDiscriminatorMetadata(ApiObject $object, ClassMetadata $metadata)
401
    {
402 12
        if (!$object->discField) {
403 6
            return;
404
        }
405
406 11
        $fieldMeta = null;
407 11
        $field = $object->discField;
408 11
        if ($metadata instanceof ObjectMetadataInterface) {
409 2
            $properties = $metadata->getProperties();
410 2
            if (array_key_exists($field, $properties)) {
411 2
                $fieldMeta = $properties[$field];
412
            }
413 9
        } elseif ($metadata instanceof ResourceMetadata) {
414 9
            $attributes = $metadata->getAttributes();
415 9
            if (array_key_exists($field, $attributes)) {
416 9
                $fieldMeta = $attributes[$field];
417
            }
418
        }
419
420 11
        if (null === $fieldMeta) {
421
            throw new \InvalidArgumentException("Specified discriminator field not found in object properties");
422 11
        } elseif (('scalar' !== $fieldMeta->getDataType()) || ('string' !== $fieldMeta->getDataTypeParams())) {
423
            throw new \InvalidArgumentException("Discriminator field must point to property that contain string value");
424
        }
425
426 11
        $metadata->setDiscriminatorField($fieldMeta);
427 11
        $metadata->setDiscriminatorMap($object->discMap);
428 11
        $metadata->setDiscriminatorError($object->discError);
429 11
    }
430
431
    /**
432
     * Returns data path
433
     *
434
     * @param Property $annotation
435
     * @param \ReflectionProperty $property
436
     * @return string
437
     */
438 17
    private function getDataPath(Property $annotation, \ReflectionProperty $property)
439
    {
440 17
        $prefix = '';
441 17
        $suffix = '';
442 17
        if ($annotation instanceof Attribute) {
443 10
            $prefix = 'attributes.';
444 17
        } elseif ($annotation instanceof Relationship) {
445 10
            $prefix = 'relationships.';
446 10
            $suffix = '.data';
447
        }
448
449 17 View Code Duplication
        if (!empty($prefix) || !empty($suffix)) {
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...
450 10
            if (null !== $annotation->path) {
451
                return $prefix . $annotation->path . $suffix;
452
            }
453
454 10
            return $prefix . $property->name . $suffix;
455
        }
456
457 17
        return $annotation->path;
458
    }
459
460
    /**
461
     * Returns data path for virtual property
462
     *
463
     * @param VirtualProperty $annotation
464
     * @param \ReflectionMethod $method
465
     * @return string
466
     */
467 11
    private function getVirtualDataPath(VirtualProperty $annotation, \ReflectionMethod $method)
0 ignored issues
show
Unused Code introduced by
The parameter $method 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...
468
    {
469 11
        $prefix = '';
470 11
        $suffix = '';
471 11
        if ($annotation instanceof VirtualAttribute) {
472 9
            $prefix = 'attributes.';
473 11
        } elseif ($annotation instanceof VirtualRelationship) {
474 9
            $prefix = 'relationships.';
475 9
            $suffix = '.data';
476
        }
477
478 11 View Code Duplication
        if (!empty($prefix) || !empty($suffix)) {
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...
479 9
            if (null !== $annotation->path) {
480
                return $prefix . $annotation->path . $suffix;
481
            }
482
483 9
            return $prefix . $annotation->name . $suffix;
484
        }
485
486 2
        return $annotation->path;
487
    }
488
489
    /**
490
     * Parse property loaders
491
     *
492
     * @param array|Loader[] $loaders
493
     * @return array
494
     */
495 17
    private function parseLoaders(array $loaders)
496
    {
497 17
        $propLoaders = [];
498
499 17
        foreach ($loaders as $loader) {
500 1
            if (array_key_exists($loader->group, $propLoaders)) {
501
                throw new \InvalidArgumentException(sprintf(
502
                    "Only one loader for serialization group '%s' can be specified",
503
                    $loader->group
504
                ));
505
            }
506
507 1
            $propLoaders[$loader->group] = $loader->loader;
508
        }
509
510 17
        return $propLoaders;
511
    }
512
}
513