Passed
Push — master ( e8c812...d43699 )
by Asmir
06:04 queued 03:41
created

getMethodAnnotations()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
nc 4
nop 1
dl 0
loc 18
rs 9.9666
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Metadata\Driver;
6
7
use Doctrine\Common\Annotations\Reader;
8
use JMS\Serializer\Annotation\Accessor;
9
use JMS\Serializer\Annotation\AccessorOrder;
10
use JMS\Serializer\Annotation\AccessType;
11
use JMS\Serializer\Annotation\Discriminator;
12
use JMS\Serializer\Annotation\Exclude;
13
use JMS\Serializer\Annotation\ExclusionPolicy;
14
use JMS\Serializer\Annotation\Expose;
15
use JMS\Serializer\Annotation\Groups;
16
use JMS\Serializer\Annotation\Inline;
17
use JMS\Serializer\Annotation\MaxDepth;
18
use JMS\Serializer\Annotation\PostDeserialize;
19
use JMS\Serializer\Annotation\PostSerialize;
20
use JMS\Serializer\Annotation\PreSerialize;
21
use JMS\Serializer\Annotation\ReadOnlyProperty;
22
use JMS\Serializer\Annotation\SerializedName;
23
use JMS\Serializer\Annotation\Since;
24
use JMS\Serializer\Annotation\SkipWhenEmpty;
25
use JMS\Serializer\Annotation\Type;
26
use JMS\Serializer\Annotation\Until;
27
use JMS\Serializer\Annotation\VirtualProperty;
28
use JMS\Serializer\Annotation\XmlAttribute;
29
use JMS\Serializer\Annotation\XmlAttributeMap;
30
use JMS\Serializer\Annotation\XmlDiscriminator;
31
use JMS\Serializer\Annotation\XmlElement;
32
use JMS\Serializer\Annotation\XmlKeyValuePairs;
33
use JMS\Serializer\Annotation\XmlList;
34
use JMS\Serializer\Annotation\XmlMap;
35
use JMS\Serializer\Annotation\XmlNamespace;
36
use JMS\Serializer\Annotation\XmlRoot;
37
use JMS\Serializer\Annotation\XmlValue;
38
use JMS\Serializer\Exception\InvalidMetadataException;
39
use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface;
40
use JMS\Serializer\Metadata\ClassMetadata;
41
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
42
use JMS\Serializer\Metadata\PropertyMetadata;
43
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
44
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
45
use JMS\Serializer\Type\Parser;
46
use JMS\Serializer\Type\ParserInterface;
47
use Metadata\ClassMetadata as BaseClassMetadata;
48
use Metadata\Driver\DriverInterface;
49
use Metadata\MethodMetadata;
50
51
class AnnotationOrAttributeDriver implements DriverInterface
52
{
53
    use ExpressionMetadataTrait;
54
55
    /**
56
     * @var ParserInterface
57
     */
58
    private $typeParser;
59
60
    /**
61
     * @var PropertyNamingStrategyInterface
62
     */
63
    private $namingStrategy;
64
65
    /**
66
     * @var Reader
67
     */
68
    private $reader;
69
70
    public function __construct(PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null, ?Reader $reader = null)
71
    {
72
        $this->typeParser = $typeParser ?: new Parser();
73
        $this->namingStrategy = $namingStrategy;
74
        $this->expressionEvaluator = $expressionEvaluator;
75
        $this->reader = $reader;
76
    }
77
78
    public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata
79
    {
80
        $configured = false;
81
82
        $classMetadata = new ClassMetadata($name = $class->name);
83
        $fileResource =  $class->getFilename();
84
85
        if (false !== $fileResource) {
86
            $classMetadata->fileResources[] = $fileResource;
87
        }
88
89
        $propertiesMetadata = [];
90
        $propertiesAnnotations = [];
91
92
        $exclusionPolicy = ExclusionPolicy::NONE;
93
        $excludeAll = false;
94
        $classAccessType = PropertyMetadata::ACCESS_TYPE_PROPERTY;
95
        $readOnlyClass = false;
96
97
        foreach ($this->getClassAnnotations($class) as $annot) {
98
            $configured = true;
99
100
            if ($annot instanceof ExclusionPolicy) {
101
                $exclusionPolicy = $annot->policy;
102
            } elseif ($annot instanceof XmlRoot) {
103
                $classMetadata->xmlRootName = $annot->name;
104
                $classMetadata->xmlRootNamespace = $annot->namespace;
105
                $classMetadata->xmlRootPrefix = $annot->prefix;
106
            } elseif ($annot instanceof XmlNamespace) {
107
                $classMetadata->registerNamespace($annot->uri, $annot->prefix);
0 ignored issues
show
Bug introduced by
It seems like $annot->uri can also be of type null; however, parameter $uri of JMS\Serializer\Metadata\...ta::registerNamespace() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

107
                $classMetadata->registerNamespace(/** @scrutinizer ignore-type */ $annot->uri, $annot->prefix);
Loading history...
108
            } elseif ($annot instanceof Exclude) {
109
                if (null !== $annot->if) {
110
                    $classMetadata->excludeIf = $this->parseExpression($annot->if);
111
                } else {
112
                    $excludeAll = true;
113
                }
114
            } elseif ($annot instanceof AccessType) {
115
                $classAccessType = $annot->type;
116
            } elseif ($annot instanceof ReadOnlyProperty) {
117
                $readOnlyClass = true;
118
            } elseif ($annot instanceof AccessorOrder) {
119
                $classMetadata->setAccessorOrder($annot->order, $annot->custom);
0 ignored issues
show
Bug introduced by
It seems like $annot->order can also be of type null; however, parameter $order of JMS\Serializer\Metadata\...ata::setAccessorOrder() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
                $classMetadata->setAccessorOrder(/** @scrutinizer ignore-type */ $annot->order, $annot->custom);
Loading history...
120
            } elseif ($annot instanceof Discriminator) {
121
                if ($annot->disabled) {
122
                    $classMetadata->discriminatorDisabled = true;
123
                } else {
124
                    $classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups);
125
                }
126
            } elseif ($annot instanceof XmlDiscriminator) {
127
                $classMetadata->xmlDiscriminatorAttribute = (bool) $annot->attribute;
128
                $classMetadata->xmlDiscriminatorCData = (bool) $annot->cdata;
129
                $classMetadata->xmlDiscriminatorNamespace = $annot->namespace ? (string) $annot->namespace : null;
130
            } elseif ($annot instanceof VirtualProperty) {
131
                $virtualPropertyMetadata = new ExpressionPropertyMetadata(
132
                    $name,
133
                    $annot->name,
0 ignored issues
show
Bug introduced by
It seems like $annot->name can also be of type null; however, parameter $fieldName of JMS\Serializer\Metadata\...Metadata::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
                    /** @scrutinizer ignore-type */ $annot->name,
Loading history...
134
                    $this->parseExpression($annot->exp)
0 ignored issues
show
Bug introduced by
It seems like $annot->exp can also be of type null; however, parameter $expression of JMS\Serializer\Metadata\...iver::parseExpression() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
                    $this->parseExpression(/** @scrutinizer ignore-type */ $annot->exp)
Loading history...
135
                );
136
                $propertiesMetadata[] = $virtualPropertyMetadata;
137
                $propertiesAnnotations[] = $annot->options;
138
            }
139
        }
140
141
        foreach ($class->getMethods() as $method) {
142
            if ($method->class !== $name) {
143
                continue;
144
            }
145
146
            $methodAnnotations = $this->getMethodAnnotations($method);
147
148
            foreach ($methodAnnotations as $annot) {
149
                $configured = true;
150
151
                if ($annot instanceof PreSerialize) {
152
                    $classMetadata->addPreSerializeMethod(new MethodMetadata($name, $method->name));
153
                    continue 2;
154
                } elseif ($annot instanceof PostDeserialize) {
155
                    $classMetadata->addPostDeserializeMethod(new MethodMetadata($name, $method->name));
156
                    continue 2;
157
                } elseif ($annot instanceof PostSerialize) {
158
                    $classMetadata->addPostSerializeMethod(new MethodMetadata($name, $method->name));
159
                    continue 2;
160
                } elseif ($annot instanceof VirtualProperty) {
161
                    $virtualPropertyMetadata = new VirtualPropertyMetadata($name, $method->name);
162
                    $propertiesMetadata[] = $virtualPropertyMetadata;
163
                    $propertiesAnnotations[] = $methodAnnotations;
164
                    continue 2;
165
                }
166
            }
167
        }
168
169
        if (!$excludeAll) {
170
            foreach ($class->getProperties() as $property) {
171
                if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) {
172
                    continue;
173
                }
174
175
                $propertiesMetadata[] = new PropertyMetadata($name, $property->getName());
176
                $propertiesAnnotations[] = $this->getPropertyAnnotations($property);
177
            }
178
179
            foreach ($propertiesMetadata as $propertyKey => $propertyMetadata) {
180
                $isExclude = false;
181
                $isExpose = $propertyMetadata instanceof VirtualPropertyMetadata
182
                    || $propertyMetadata instanceof ExpressionPropertyMetadata;
183
                $propertyMetadata->readOnly = $propertyMetadata->readOnly || $readOnlyClass;
184
                $accessType = $classAccessType;
185
                $accessor = [null, null];
186
187
                $propertyAnnotations = $propertiesAnnotations[$propertyKey];
188
189
                foreach ($propertyAnnotations as $annot) {
190
                    $configured = true;
191
192
                    if ($annot instanceof Since) {
193
                        $propertyMetadata->sinceVersion = $annot->version;
194
                    } elseif ($annot instanceof Until) {
195
                        $propertyMetadata->untilVersion = $annot->version;
196
                    } elseif ($annot instanceof SerializedName) {
197
                        $propertyMetadata->serializedName = $annot->name;
198
                    } elseif ($annot instanceof SkipWhenEmpty) {
199
                        $propertyMetadata->skipWhenEmpty = true;
200
                    } elseif ($annot instanceof Expose) {
201
                        $isExpose = true;
202
                        if (null !== $annot->if) {
203
                            $propertyMetadata->excludeIf = $this->parseExpression('!(' . $annot->if . ')');
204
                        }
205
                    } elseif ($annot instanceof Exclude) {
206
                        if (null !== $annot->if) {
207
                            $propertyMetadata->excludeIf = $this->parseExpression($annot->if);
208
                        } else {
209
                            $isExclude = true;
210
                        }
211
                    } elseif ($annot instanceof Type) {
212
                        $propertyMetadata->setType($this->typeParser->parse($annot->name));
0 ignored issues
show
Bug introduced by
It seems like $annot->name can also be of type null; however, parameter $type of JMS\Serializer\Type\ParserInterface::parse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

212
                        $propertyMetadata->setType($this->typeParser->parse(/** @scrutinizer ignore-type */ $annot->name));
Loading history...
213
                    } elseif ($annot instanceof XmlElement) {
214
                        $propertyMetadata->xmlAttribute = false;
215
                        $propertyMetadata->xmlElementCData = $annot->cdata;
216
                        $propertyMetadata->xmlNamespace = $annot->namespace;
217
                    } elseif ($annot instanceof XmlList) {
218
                        $propertyMetadata->xmlCollection = true;
219
                        $propertyMetadata->xmlCollectionInline = $annot->inline;
220
                        $propertyMetadata->xmlEntryName = $annot->entry;
221
                        $propertyMetadata->xmlEntryNamespace = $annot->namespace;
222
                        $propertyMetadata->xmlCollectionSkipWhenEmpty = $annot->skipWhenEmpty;
223
                    } elseif ($annot instanceof XmlMap) {
224
                        $propertyMetadata->xmlCollection = true;
225
                        $propertyMetadata->xmlCollectionInline = $annot->inline;
226
                        $propertyMetadata->xmlEntryName = $annot->entry;
227
                        $propertyMetadata->xmlEntryNamespace = $annot->namespace;
228
                        $propertyMetadata->xmlKeyAttribute = $annot->keyAttribute;
229
                    } elseif ($annot instanceof XmlKeyValuePairs) {
230
                        $propertyMetadata->xmlKeyValuePairs = true;
231
                    } elseif ($annot instanceof XmlAttribute) {
232
                        $propertyMetadata->xmlAttribute = true;
233
                        $propertyMetadata->xmlNamespace = $annot->namespace;
234
                    } elseif ($annot instanceof XmlValue) {
235
                        $propertyMetadata->xmlValue = true;
236
                        $propertyMetadata->xmlElementCData = $annot->cdata;
237
                    } elseif ($annot instanceof AccessType) {
238
                        $accessType = $annot->type;
239
                    } elseif ($annot instanceof ReadOnlyProperty) {
240
                        $propertyMetadata->readOnly = $annot->readOnly;
241
                    } elseif ($annot instanceof Accessor) {
242
                        $accessor = [$annot->getter, $annot->setter];
243
                    } elseif ($annot instanceof Groups) {
244
                        $propertyMetadata->groups = $annot->groups;
245
                        foreach ((array) $propertyMetadata->groups as $groupName) {
246
                            if (false !== strpos($groupName, ',')) {
247
                                throw new InvalidMetadataException(sprintf(
248
                                    'Invalid group name "%s" on "%s", did you mean to create multiple groups?',
249
                                    implode(', ', $propertyMetadata->groups),
250
                                    $propertyMetadata->class . '->' . $propertyMetadata->name
251
                                ));
252
                            }
253
                        }
254
                    } elseif ($annot instanceof Inline) {
255
                        $propertyMetadata->inline = true;
256
                    } elseif ($annot instanceof XmlAttributeMap) {
257
                        $propertyMetadata->xmlAttributeMap = true;
258
                    } elseif ($annot instanceof MaxDepth) {
259
                        $propertyMetadata->maxDepth = $annot->depth;
260
                    }
261
                }
262
263
                if ($propertyMetadata->inline) {
264
                    $classMetadata->isList = $classMetadata->isList || PropertyMetadata::isCollectionList($propertyMetadata->type);
265
                    $classMetadata->isMap = $classMetadata->isMap || PropertyMetadata::isCollectionMap($propertyMetadata->type);
266
267
                    if ($classMetadata->isMap && $classMetadata->isList) {
268
                        throw new InvalidMetadataException('Can not have an inline map and and inline map on the same class');
269
                    }
270
                }
271
272
                if (!$propertyMetadata->serializedName) {
273
                    $propertyMetadata->serializedName = $this->namingStrategy->translateName($propertyMetadata);
274
                }
275
276
                foreach ($propertyAnnotations as $annot) {
277
                    if ($annot instanceof VirtualProperty && null !== $annot->name) {
278
                        $propertyMetadata->name = $annot->name;
279
                    }
280
                }
281
282
                if (
283
                    (ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
284
                    || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)
285
                ) {
286
                    $propertyMetadata->setAccessor($accessType, $accessor[0], $accessor[1]);
287
                    $classMetadata->addPropertyMetadata($propertyMetadata);
288
                }
289
            }
290
        }
291
292
        if (!$configured) {
293
            return null;
294
        }
295
296
        return $classMetadata;
297
    }
298
299
    /**
300
     * @return list<object>
0 ignored issues
show
Bug introduced by
The type JMS\Serializer\Metadata\Driver\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
301
     */
302
    protected function getClassAnnotations(\ReflectionClass $class): array
303
    {
304
        $annotations = [];
305
306
        if (PHP_VERSION_ID >= 80000) {
307
            $annotations = array_map(
308
                static function (\ReflectionAttribute $attribute): object {
309
                    return $attribute->newInstance();
310
                },
311
                $class->getAttributes()
312
            );
313
        }
314
315
        if (null !== $this->reader) {
316
            $annotations = array_merge($annotations, $this->reader->getClassAnnotations($class));
317
        }
318
319
        return $annotations;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $annotations returns the type array which is incompatible with the documented return type JMS\Serializer\Metadata\Driver\list.
Loading history...
320
    }
321
322
    /**
323
     * @return list<object>
324
     */
325
    protected function getMethodAnnotations(\ReflectionMethod $method): array
326
    {
327
        $annotations = [];
328
329
        if (PHP_VERSION_ID >= 80000) {
330
            $annotations = array_map(
331
                static function (\ReflectionAttribute $attribute): object {
332
                    return $attribute->newInstance();
333
                },
334
                $method->getAttributes()
335
            );
336
        }
337
338
        if (null !== $this->reader) {
339
            $annotations = array_merge($annotations, $this->reader->getMethodAnnotations($method));
340
        }
341
342
        return $annotations;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $annotations returns the type array which is incompatible with the documented return type JMS\Serializer\Metadata\Driver\list.
Loading history...
343
    }
344
345
    /**
346
     * @return list<object>
347
     */
348
    protected function getPropertyAnnotations(\ReflectionProperty $property): array
349
    {
350
        $annotations = [];
351
352
        if (PHP_VERSION_ID >= 80000) {
353
            $annotations = array_map(
354
                static function (\ReflectionAttribute $attribute): object {
355
                    return $attribute->newInstance();
356
                },
357
                $property->getAttributes()
358
            );
359
        }
360
361
        if (null !== $this->reader) {
362
            $annotations = array_merge($annotations, $this->reader->getPropertyAnnotations($property));
363
        }
364
365
        return $annotations;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $annotations returns the type array which is incompatible with the documented return type JMS\Serializer\Metadata\Driver\list.
Loading history...
366
    }
367
}
368