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 ( 8f3a3d...7eb519 )
by Sergey
76:54 queued 51:47
created

AnnotationLoader::loadDocumentMetadata()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.0163

Importance

Changes 0
Metric Value
dl 0
loc 23
c 0
b 0
f 0
ccs 12
cts 13
cp 0.9231
rs 8.9297
cc 6
nc 3
nop 2
crap 6.0163
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\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 27
    public function __construct(Reader $reader)
55
    {
56 27
        $this->reader = $reader;
57 27
    }
58
59
    /**
60
     * @inheritdoc
61
     */
62 17
    public function loadClassMetadata(\ReflectionClass $class)
63
    {
64 17
        if ($class->implementsInterface(Proxy::class)) {
65
            return $this->loadClassMetadata($class->getParentClass());
66
        }
67
68 17
        if (null !== ($resource = $this->reader->getClassAnnotation($class, ApiResource::class))) {
69
            /* @var $resource ApiResource */
70 9
            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 9
    private function loadResourceMetadata(ApiResource $resource, \ReflectionClass $class)
89
    {
90 9
        $metadata = new ResourceMetadata($class->name);
91 9
        $metadata->setName($resource->name);
92 9
        $metadata->setLoader($resource->loader);
93
94 9
        $properties = $class->getProperties();
95 9
        foreach ($properties as $property) {
96 9
            if ($property->getDeclaringClass()->name !== $class->name) {
97 5
                continue;
98
            }
99
100 9
            foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
101 9
                if ($annotation instanceof Attribute) {
102 9
                    $metadata->addAttribute($this->loadPropertyMetadata($annotation, $property));
103 9
                } elseif ($annotation instanceof Relationship) {
104 9
                    $metadata->addRelationship($this->loadPropertyMetadata($annotation, $property));
105 9
                } elseif ($annotation instanceof Id) {
106 9
                    $metadata->setIdMetadata($this->loadPropertyMetadata($annotation, $property));
107
                }
108
            }
109
        }
110
111 9
        $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
112 9
        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 9
        $this->loadDiscriminatorMetadata($resource, $metadata);
127
128 9
        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 4
                    $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 17
    private function loadPropertyMetadata(Property $annotation, \ReflectionProperty $property)
216
    {
217 17
        $metadata = new PropertyMetadata($property->name, $property->class);
218
219 17
        list($dataType, $dataTypeParams) = $this->parseDataType($annotation, $property);
220
221
        $metadata
222 16
            ->setDataType($dataType)
223 16
            ->setDataTypeParams($dataTypeParams)
224 16
            ->setDataPath($this->getDataPath($annotation, $property))
225 16
            ->setConverter($annotation->converter)
226 16
            ->setGroups($annotation->groups);
227
228 16
        if ($annotation->setter) {
229
            $metadata->setSetter($annotation->setter);
230 16
        } elseif (false === $property->isPublic()) {
231 9
            $setter = 'set' . ucfirst($property->name);
232 9
            if (false === $property->getDeclaringClass()->hasMethod($setter)) {
233 1
                throw new \RuntimeException(sprintf(
234 1
                    "Couldn't find setter for non public property: %s:%s",
235 1
                    $property->class,
236 1
                    $property->name
237
                ));
238
            }
239
240 8
            $metadata->setSetter($setter);
241
        }
242
243 15
        return $metadata;
244
    }
245
246
    /**
247
     * Parse virtual property metadata
248
     *
249
     * @param VirtualProperty $annotation
250
     * @param \ReflectionMethod $method
251
     * @return PropertyMetadata
252
     */
253 11
    private function loadVirtualMetadata(VirtualProperty $annotation, \ReflectionMethod $method)
254
    {
255 11
        if (empty($annotation->name)) {
256
            throw new \InvalidArgumentException(sprintf(
257
                "Virtual property name not specified: %s:%s()",
258
                $method->class,
259
                $method->name
260
            ));
261
        }
262
263 11
        list($dataType, $dataTypeParams) = $this->parseVirtualDataType($annotation, $method);
264
265 11
        $metadata = new PropertyMetadata($annotation->name, $method->class);
266
        $metadata
267 11
            ->setDataType($dataType)
268 11
            ->setDataTypeParams($dataTypeParams)
269 11
            ->setDataPath($this->getVirtualDataPath($annotation, $method))
270 11
            ->setConverter($annotation->converter)
271 11
            ->setGroups($annotation->groups)
272 11
            ->setSetter($method->name);
273
274 11
        return $metadata;
275
    }
276
277
    /**
278
     * Parse property data type
279
     *
280
     * @param Property $annotation
281
     * @param \ReflectionProperty $property
282
     * @return array
283
     */
284 17
    private function parseDataType(Property $annotation, \ReflectionProperty $property)
285
    {
286 17
        if (!empty($annotation->parser)) {
287 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...
288 1
                throw new \InvalidArgumentException(sprintf(
289 1
                    "Custom parser function %s:%s() for property '%s' does not exist",
290 1
                    $property->class,
291 1
                    $annotation->parser,
292 1
                    $property->name
293
                ));
294
            }
295 5
            return ['custom', $annotation->parser];
296 16
        } elseif (!empty($annotation->type)) {
297 14
            return $this->parseDataTypeString($annotation->type);
298 14
        } elseif (preg_match('~@var\s(.*?)\s~si', $property->getDocComment(), $matches)) {
299 14
            return $this->parseDataTypeString($matches[1]);
300
        } else {
301 2
            return ['raw', null];
302
        }
303
    }
304
305
    /**
306
     * Parse virtual property data type
307
     *
308
     * @param VirtualProperty $annotation
309
     * @param \ReflectionMethod $method
310
     * @return array
311
     */
312 11
    private function parseVirtualDataType(VirtualProperty $annotation, \ReflectionMethod $method)
313
    {
314 11
        if (!empty($annotation->parser)) {
315 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...
316
                throw new \InvalidArgumentException(sprintf(
317
                    "Custom parser function %s:%s() for virtual property '%s' does not exist",
318
                    $method->class,
319
                    $annotation->parser,
320
                    $annotation->name
321
                ));
322
            }
323
            return ['custom', $annotation->parser];
324 11
        } elseif (!empty($annotation->type)) {
325 11
            return $this->parseDataTypeString($annotation->type);
326
        } else {
327
            return ['raw', null];
328
        }
329
    }
330
331
    /**
332
     * Parse data type string
333
     *
334
     * @param string $type
335
     * @return array
336
     */
337 16
    private function parseDataTypeString($type)
338
    {
339 16
        $params = null;
340
341 16
        if ('raw' === $type) {
342 2
            $dataType = 'raw';
343 2
            $params = null;
344 16
        } elseif ($this->isScalarDataType($type)) {
345 15
            $dataType = 'scalar';
346 15
            $params = $type;
347 12
        } elseif (preg_match('~^DateTime(<(.*?)>)?$~', $type, $matches)) {
348 2
            $dataType = 'datetime';
349 2
            if (3 === count($matches)) {
350 2
                $params = $matches[2];
351
            }
352
        } elseif (
353 12
            (preg_match('~Array(<(.*?)>)?$~si', $type, $matches)) ||
354 12
            (preg_match('~^(.*?)\[\]$~si', $type, $matches))
355
        ) {
356 5
            $dataType = 'array';
357 5
            if (3 === count($matches)) {
358 5
                $params = $this->parseDataTypeString($matches[2]);
359 2
            } elseif (2 === count($matches)) {
360 2
                $params = $this->parseDataTypeString($matches[1]);
361
            } else {
362 5
                $params = ['raw', null];
363
            }
364
        } else {
365 12
            $type = ltrim($type, '\\');
366
367 12
            if (!class_exists($type)) {
368
                throw new \InvalidArgumentException(sprintf(
369
                    "Unknown object type '%s' specified",
370
                    $type
371
                ));
372
            }
373
374 12
            $dataType = 'object';
375 12
            $params = $type;
376
        }
377
378 16
        return [$dataType, $params];
379
    }
380
381
    /**
382
     * Returns true if specified type scalar. False otherwise.
383
     *
384
     * @param string $type
385
     * @return bool
386
     */
387 16
    private function isScalarDataType($type)
388
    {
389 16
        return in_array($type, ['string', 'bool', 'boolean', 'int', 'integer', 'float', 'double']);
390
    }
391
392
    /**
393
     * Load discriminator metadata
394
     *
395
     * @param ApiObject $object
396
     * @param ClassMetadata $metadata
397
     */
398 11
    private function loadDiscriminatorMetadata(ApiObject $object, ClassMetadata $metadata)
399
    {
400 11
        if (!$object->discField) {
401 5
            return;
402
        }
403
404 11
        $fieldMeta = null;
405 11
        $field = $object->discField;
406 11
        if ($metadata instanceof ObjectMetadataInterface) {
407 2
            $properties = $metadata->getProperties();
408 2
            if (array_key_exists($field, $properties)) {
409 2
                $fieldMeta = $properties[$field];
410
            }
411 9
        } elseif ($metadata instanceof ResourceMetadata) {
412 9
            $attributes = $metadata->getAttributes();
413 9
            if (array_key_exists($field, $attributes)) {
414 9
                $fieldMeta = $attributes[$field];
415
            }
416
        }
417
418 11
        if (null === $fieldMeta) {
419
            throw new \InvalidArgumentException("Specified discriminator field not found in object properties");
420 11
        } elseif (('scalar' !== $fieldMeta->getDataType()) || ('string' !== $fieldMeta->getDataTypeParams())) {
421
            throw new \InvalidArgumentException("Discriminator field must point to property that contain string value");
422
        }
423
424 11
        $metadata->setDiscriminatorField($fieldMeta);
425 11
        $metadata->setDiscriminatorMap($object->discMap);
426 11
        $metadata->setDiscriminatorError($object->discError);
427 11
    }
428
429
    /**
430
     * Returns data path
431
     *
432
     * @param Property $annotation
433
     * @param \ReflectionProperty $property
434
     * @return string
435
     */
436 16
    private function getDataPath(Property $annotation, \ReflectionProperty $property)
437
    {
438 16
        $prefix = '';
439 16
        $suffix = '';
440 16
        if ($annotation instanceof Attribute) {
441 9
            $prefix = 'attributes.';
442 16
        } elseif ($annotation instanceof Relationship) {
443 9
            $prefix = 'relationships.';
444 9
            $suffix = '.data';
445
        }
446
447 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...
448 9
            if (null !== $annotation->path) {
449
                return $prefix . $annotation->path . $suffix;
450
            }
451
452 9
            return $prefix . $property->name . $suffix;
453
        }
454
455 16
        return $annotation->path;
456
    }
457
458
    /**
459
     * Returns data path for virtual property
460
     *
461
     * @param VirtualProperty $annotation
462
     * @param \ReflectionMethod $method
463
     * @return string
464
     */
465 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...
466
    {
467 11
        $prefix = '';
468 11
        $suffix = '';
469 11
        if ($annotation instanceof VirtualAttribute) {
470 9
            $prefix = 'attributes.';
471 11
        } elseif ($annotation instanceof VirtualRelationship) {
472 9
            $prefix = 'relationships.';
473 9
            $suffix = '.data';
474
        }
475
476 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...
477 9
            if (null !== $annotation->path) {
478
                return $prefix . $annotation->path . $suffix;
479
            }
480
481 9
            return $prefix . $annotation->name . $suffix;
482
        }
483
484 2
        return $annotation->path;
485
    }
486
}
487