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 ( 572572...8f3a3d )
by Sergey
02:12
created

AnnotationLoader::loadClassMetadata()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

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