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 ( 0f07bb...d93f36 )
by Sergey
05:25
created

AnnotationLoader   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 318
Duplicated Lines 6.92 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 95.21%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 55
c 2
b 0
f 0
lcom 1
cbo 10
dl 22
loc 318
ccs 159
cts 167
cp 0.9521
rs 6.8

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A loadClassMetadata() 0 12 3
A isScalarDataType() 0 4 1
C loadResourceMetadata() 0 27 7
B loadObjectMetadata() 10 22 5
A loadDocumentMetadata() 12 21 4
B loadPropertyMetadata() 0 30 4
B parseDataType() 0 20 5
D parseDataTypeString() 0 43 10
D loadDiscriminatorMetadata() 0 30 9
B getDataPath() 0 21 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AnnotationLoader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationLoader, and based on these observations, apply Extract Interface, too.

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