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
Pull Request — master (#8)
by
unknown
02:23
created

AnnotationLoader::loadResourceMetadata()   C

Complexity

Conditions 12
Paths 9

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 42
c 0
b 0
f 0
ccs 26
cts 26
cp 1
rs 6.9666
cc 12
nc 9
nop 2
crap 12

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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