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 ( 28df83...572572 )
by Sergey
04:37 queued 02:17
created

AnnotationLoader::isScalarDataType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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