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 ( 90cf5c...e17caf )
by Sergey
02:42
created

AnnotationLoader   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 306
Duplicated Lines 7.19 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 92.7%

Importance

Changes 10
Bugs 1 Features 3
Metric Value
wmc 53
c 10
b 1
f 3
lcom 1
cbo 10
dl 22
loc 306
ccs 127
cts 137
cp 0.927
rs 7.4757

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 26 7
B loadObjectMetadata() 10 22 5
A loadDocumentMetadata() 12 21 4
B loadPropertyMetadata() 0 29 4
B parseDataType() 0 20 5
D parseDataTypeString() 0 40 9
D loadDiscriminatorMetadata() 0 29 9
B getDataPath() 0 15 5

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