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 ( dad22a...9ee3c2 )
by Sergey
03:24
created

AnnotationLoader::loadPropertyMetadata()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4.0023

Importance

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