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 ( 3c9ac6...b0ef94 )
by Sergey
02:31
created

AnnotationLoader   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 316
Duplicated Lines 6.96 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 95.54%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 55
c 1
b 0
f 0
lcom 1
cbo 10
dl 22
loc 316
ccs 150
cts 157
cp 0.9554
rs 6.8

11 Methods

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