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 ( 721b3b...eac8ca )
by Sergey
09:42
created

AnnotationLoader::loadDiscriminatorMetadata()   D

Complexity

Conditions 9
Paths 16

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 9

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 29
ccs 3
cts 3
cp 1
rs 4.909
cc 9
eloc 19
nc 16
nop 2
crap 9
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\Document as ApiDocument;
17
use Reva2\JsonApi\Annotations\Id;
18
use Reva2\JsonApi\Annotations\Resource as ApiResource;
19
use Reva2\JsonApi\Annotations\Object as 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\Contracts\Decoders\Mapping\Loader\LoaderInterface;
24
use Reva2\JsonApi\Contracts\Decoders\Mapping\ObjectMetadataInterface;
25
use Reva2\JsonApi\Decoders\Mapping\ClassMetadata;
26
use Reva2\JsonApi\Decoders\Mapping\DocumentMetadata;
27
use Reva2\JsonApi\Decoders\Mapping\ObjectMetadata;
28
use Reva2\JsonApi\Decoders\Mapping\PropertyMetadata;
29
use Reva2\JsonApi\Decoders\Mapping\ResourceMetadata;
30
31
/**
32
 * Loads JSON API metadata using a Doctrine annotations
33
 *
34
 * @package Reva2\JsonApi\Decoders\Mapping\Loader
35
 * @author Sergey Revenko <[email protected]>
36
 */
37
class AnnotationLoader implements LoaderInterface
38
{
39
    /**
40
     * @var Reader
41
     */
42
    protected $reader;
43
44
    /**
45
     * Constructor
46
     *
47 6
     * @param Reader $reader
48
     */
49 6
    public function __construct(Reader $reader)
50 6
    {
51
        $this->reader = $reader;
52
    }
53
54
    /**
55 6
     * @inheritdoc
56
     */
57 6
    public function loadClassMetadata(\ReflectionClass $class)
58 3
    {
59 3
        if (null !== ($resource = $this->reader->getClassAnnotation($class, ApiResource::class))) {
60 1
            return $this->loadResourceMetadata($resource, $class);
61
        } elseif (null !== ($document = $this->reader->getClassAnnotation($class, ApiDocument::class))) {
62 2
            return $this->loadDocumentMetadata($document, $class);
63
        } else {
64 2
            $object = $this->reader->getClassAnnotation($class, ApiObject::class);
65
66
            return $this->loadObjectMetadata($class, $object);
67
        }
68
    }
69
70
    /**
71
     * Parse JSON API resource metadata
72
     *
73
     * @param ApiResource $resource
74
     * @param \ReflectionClass $class
75 3
     * @return ResourceMetadata
76
     */
77 3
    private function loadResourceMetadata(ApiResource $resource, \ReflectionClass $class)
78 3
    {
79
        $metadata = new ResourceMetadata($class->name);
80 3
        $metadata->setName($resource->name);
81
82 3
        $properties = $class->getProperties();
83 3
        foreach ($properties as $property) {
84 3
            if ($property->getDeclaringClass()->name !== $class->name) {
85 1
                continue;
86
            }
87
88 3
            foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
89 3
                if ($annotation instanceof Attribute) {
90 3
                    $metadata->addAttribute($this->loadPropertyMetadata($annotation, $property));
91 3
                } elseif ($annotation instanceof Relationship) {
92 3
                    $metadata->addRelationship($this->loadPropertyMetadata($annotation, $property));
93 3
                } elseif ($annotation instanceof Id) {
94 3
                    $metadata->setIdMetadata($this->loadPropertyMetadata($annotation, $property));
95 3
                }
96
            }
97 3
        }
98
99
        $this->loadDiscriminatorMetadata($resource, $metadata);
100
101
        return $metadata;
102
    }
103
104
    /**
105 2
     * @param \ReflectionClass $class
106
     * @param ApiObject|null $object
107 2
     * @return ObjectMetadata
108
     */
109 2
    private function loadObjectMetadata(\ReflectionClass $class, ApiObject $object = null)
110 1
    {
111 1
        $metadata = new ObjectMetadata($class->name);
112
113 2
        $properties = $class->getProperties();
114 2 View Code Duplication
        foreach ($properties as $property) {
1 ignored issue
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...
115 2
            if ($property->getDeclaringClass()->name !== $class->name) {
116 1
                continue;
117
            }
118
119 2
            $annotation = $this->reader->getPropertyAnnotation($property, Property::class);
120 2
            if (null !== $annotation) {
121 2
                $metadata->addProperty($this->loadPropertyMetadata($annotation, $property));
122 2
            }
123 2
        }
124
125 2
        if (null !== $object) {
126
            $this->loadDiscriminatorMetadata($object, $metadata);
127
        }
128
129
        return $metadata;
130
    }
131
132
    /**
133
     * Parse JSON API document metadata
134
     *
135 1
     * @param ApiDocument $document
136
     * @param \ReflectionClass $class
137 1
     * @return DocumentMetadata
138 1
     */
139
    private function loadDocumentMetadata(ApiDocument $document, \ReflectionClass $class)
140 1
    {
141 1
        $metadata = new DocumentMetadata($class->name);
142 1
        $metadata->setAllowEmpty($document->allowEmpty);
143
144
        $properties = $class->getProperties();
145 View Code Duplication
        foreach ($properties as $property) {
1 ignored issue
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...
146 1
            if ($property->getDeclaringClass()->name !== $class->name) {
147 1
                continue;
148 1
            }
149
150 1
            $annotation = $this->reader->getPropertyAnnotation($property, ApiContent::class);
151
            if (null !== $annotation) {
152 1
                $metadata->setContentMetadata($this->loadPropertyMetadata($annotation, $property));
153
154 1
                break;
155
            }
156
        }
157
158
        return $metadata;
159
    }
160
161
    /**
162
     * Parse property metadata
163
     *
164 6
     * @param Property $annotation
165
     * @param \ReflectionProperty $property
166 6
     * @return PropertyMetadata
167
     */
168 6
    private function loadPropertyMetadata(Property $annotation, \ReflectionProperty $property)
169
    {
170
        $metadata = new PropertyMetadata($property->name, $property->class);
171 6
172 6
        list($dataType, $dataTypeParams) = $this->parseDataType($annotation, $property);
173 6
174
        $metadata
175 6
            ->setDataType($dataType)
176
            ->setDataTypeParams($dataTypeParams)
177 6
            ->setDataPath($this->getDataPath($annotation, $property))
178 1
            ->setOrmEntityClass($annotation->ormEntity);
179 1
180
        if ($annotation->setter) {
181
            $metadata->setSetter($annotation->setter);
182
        } elseif (false === $property->isPublic()) {
183
            $setter = 'set' . ucfirst($property->name);
184
            if (false === $property->getDeclaringClass()->hasMethod($setter)) {
185
                throw new \RuntimeException(sprintf(
186
                    "Couldn't find setter for non public property: %s:%s",
187 1
                    $property->class,
188 1
                    $property->name
189
                ));
190 6
            }
191
192
            $metadata->setSetter($setter);
193
        }
194
195
        return $metadata;
196
    }
197
198
    /**
199
     * Parse property data type
200 6
     *
201
     * @param Property $annotation
202 6
     * @param \ReflectionProperty $property
203 1
     * @return array
204
     */
205
    private function parseDataType(Property $annotation, \ReflectionProperty $property)
206
    {
207
        if (!empty($annotation->parser)) {
208
            if (!$property->getDeclaringClass()->hasMethod($annotation->parser)) {
209
                throw new \InvalidArgumentException(sprintf(
210
                    "Custom parser function %s:%s() for property '%s' does not exist",
211 1
                    $property->class,
212 6
                    $annotation->parser,
213 5
                    $property->name
214 5
                ));
215 5
            }
216
            return ['custom', $annotation->parser];
217 1
        } elseif (!empty($annotation->type)) {
218
            return $this->parseDataTypeString($annotation->type);
219
        } elseif (preg_match('~@var\s(.*?)\s~si', $property->getDocComment(), $matches)) {
220
            return $this->parseDataTypeString($matches[1]);
221
        } else {
222
            return ['raw', null];
223
        }
224
    }
225
226
    /**
227 6
     * Parse data type string
228
     *
229 6
     * @param string $type
230
     * @return array
231 6
     */
232 5
    private function parseDataTypeString($type)
233 5
    {
234 6
        $params = null;
235 1
236 1
        if ($this->isScalarDataType($type)) {
237 1
            $dataType = 'scalar';
238 1
            $params = $type;
239 1
        } elseif (preg_match('~^DateTime(<(.*?)>)?$~', $type, $matches)) {
240 5
            $dataType = 'datetime';
241 5
            if (3 === count($matches)) {
242 5
                $params = $matches[2];
243 2
            }
244 2
        } elseif (
245 2
            (preg_match('~Array(<(.*?)>)?$~si', $type, $matches)) ||
246 2
            (preg_match('~^(.*?)\[\]$~si', $type, $matches))
247 1
        ) {
248 1
            $dataType = 'array';
249 1
            if (3 === count($matches)) {
250
                $params = $this->parseDataTypeString($matches[2]);
251 2
            } elseif (2 === count($matches)) {
252 5
                $params = $this->parseDataTypeString($matches[1]);
253
            } else {
254 5
                $params = ['raw', null];
255
            }
256
        } else {
257
            $type = ltrim($type, '\\');
258
259
            if (!class_exists($type)) {
260
                throw new \InvalidArgumentException(sprintf(
261 5
                    "Unknown object type '%s' specified",
262 5
                    $type
263
                ));
264
            }
265 6
266
            $dataType = 'object';
267
            $params = $type;
268
        }
269
270
        return [$dataType, $params];
271
    }
272
273
    /**
274 6
     * Returns true if specified type scalar. False otherwise.
275
     *
276 6
     * @param string $type
277
     * @return bool
278
     */
279
    private function isScalarDataType($type)
280
    {
281
        return in_array($type, ['string', 'bool', 'boolean', 'int', 'integer', 'float', 'double']);
282
    }
283
284
    /**
285 4
     * Load discriminator metadata
286
     *
287 4
     * @param ApiObject $object
288 1
     * @param ClassMetadata $metadata
289
     */
290
    private function loadDiscriminatorMetadata(ApiObject $object, ClassMetadata $metadata)
291 4
    {
292 4
        if (!$object->discField) {
293 4
            return;
294
        }
295
296
        $fieldMeta = null;
297
        $field = $object->discField;
298
        if ($metadata instanceof ObjectMetadataInterface) {
299
            $properties = $metadata->getProperties();
300
            if (array_key_exists($field, $properties)) {
301
                $fieldMeta = $properties[$field];
302
            }
303
        } elseif ($metadata instanceof ResourceMetadata) {
304
            $attributes = $metadata->getAttributes();
305
            if (array_key_exists($field, $attributes)) {
306
                $fieldMeta = $attributes[$field];
307
            }
308
        }
309
310
        if (null === $fieldMeta) {
311
            throw new \InvalidArgumentException("Specified discriminator field not found in object properties");
312
        } elseif (('scalar' !== $fieldMeta->getDataType()) || ('string' !== $fieldMeta->getDataTypeParams())) {
313
            throw new \InvalidArgumentException("Discriminator field must point to property that contain string value");
314
        }
315
316
        $metadata->setDiscriminatorField($fieldMeta);
317
        $metadata->setDiscriminatorMap($object->discMap);
318
    }
319
320
    /**
321
     * Returns data path
322
     *
323
     * @param Property $annotation
324
     * @param \ReflectionProperty $property
325
     * @return string
326
     */
327
    private function getDataPath(Property $annotation, \ReflectionProperty $property)
328
    {
329
        $prefix = '';
330
        if ($annotation instanceof Attribute) {
331
            $prefix = 'attributes.';
332
        } elseif ($annotation instanceof Relationship) {
333
            $prefix = 'relationships.';
334
        }
335
336
        if (!empty($prefix)) {
337
            return (null !== $annotation->path) ? $prefix . $annotation->path : $prefix . $property->name;
338
        }
339
340
        return $annotation->path;
341
    }
342
}