1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the API Platform project. |
||
5 | * |
||
6 | * (c) Kévin Dunglas <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | declare(strict_types=1); |
||
13 | |||
14 | namespace ApiPlatform\Core\Util; |
||
15 | |||
16 | use ApiPlatform\Core\Annotation\ApiFilter; |
||
17 | use Doctrine\Common\Annotations\Reader; |
||
18 | use Doctrine\Common\Inflector\Inflector; |
||
19 | |||
20 | /** |
||
21 | * Generates a service id for a generic filter. |
||
22 | * |
||
23 | * @internal |
||
24 | * |
||
25 | * @author Antoine Bluchet <[email protected]> |
||
26 | */ |
||
27 | trait AnnotationFilterExtractorTrait |
||
28 | { |
||
29 | /** |
||
30 | * Filters annotations to get back only ApiFilter annotations. |
||
31 | * |
||
32 | * @param array $miscAnnotations class or property annotations |
||
33 | * |
||
34 | * @return \Iterator only ApiFilter annotations |
||
35 | */ |
||
36 | private function getFilterAnnotations(array $miscAnnotations): \Iterator |
||
37 | { |
||
38 | foreach ($miscAnnotations as $miscAnnotation) { |
||
39 | if (ApiFilter::class === \get_class($miscAnnotation)) { |
||
40 | yield $miscAnnotation; |
||
41 | } |
||
42 | } |
||
43 | } |
||
44 | |||
45 | /** |
||
46 | * Given a filter annotation and reflection elements, find out the properties where the filter is applied. |
||
47 | */ |
||
48 | private function getFilterProperties(ApiFilter $filterAnnotation, \ReflectionClass $reflectionClass, \ReflectionProperty $reflectionProperty = null): array |
||
49 | { |
||
50 | $properties = []; |
||
51 | |||
52 | if ($filterAnnotation->properties) { |
||
0 ignored issues
–
show
|
|||
53 | foreach ($filterAnnotation->properties as $property => $strategy) { |
||
54 | if (\is_int($property)) { |
||
55 | $properties[$strategy] = null; |
||
56 | } else { |
||
57 | $properties[$property] = $strategy; |
||
58 | } |
||
59 | } |
||
60 | |||
61 | return $properties; |
||
62 | } |
||
63 | |||
64 | if (null !== $reflectionProperty) { |
||
65 | $properties[$reflectionProperty->getName()] = $filterAnnotation->strategy ?: null; |
||
66 | |||
67 | return $properties; |
||
68 | } |
||
69 | |||
70 | if ($filterAnnotation->strategy) { |
||
71 | foreach ($reflectionClass->getProperties() as $reflectionProperty) { |
||
72 | $properties[$reflectionProperty->getName()] = $filterAnnotation->strategy; |
||
73 | } |
||
74 | } |
||
75 | |||
76 | return $properties; |
||
77 | } |
||
78 | |||
79 | /** |
||
80 | * Reads filter annotations from a ReflectionClass. |
||
81 | * |
||
82 | * @return array Key is the filter id. It has two values, properties and the ApiFilter instance |
||
83 | */ |
||
84 | private function readFilterAnnotations(\ReflectionClass $reflectionClass, Reader $reader): array |
||
85 | { |
||
86 | $filters = []; |
||
87 | |||
88 | foreach ($this->getFilterAnnotations($reader->getClassAnnotations($reflectionClass)) as $filterAnnotation) { |
||
89 | $filterClass = $filterAnnotation->filterClass; |
||
90 | $id = $this->generateFilterId($reflectionClass, $filterClass, $filterAnnotation->id); |
||
91 | |||
92 | if (!isset($filters[$id])) { |
||
93 | $filters[$id] = [$filterAnnotation->arguments, $filterClass]; |
||
94 | } |
||
95 | |||
96 | if ($properties = $this->getFilterProperties($filterAnnotation, $reflectionClass)) { |
||
97 | $filters[$id][0]['properties'] = $properties; |
||
98 | } |
||
99 | } |
||
100 | |||
101 | foreach ($reflectionClass->getProperties() as $reflectionProperty) { |
||
102 | foreach ($this->getFilterAnnotations($reader->getPropertyAnnotations($reflectionProperty)) as $filterAnnotation) { |
||
103 | $filterClass = $filterAnnotation->filterClass; |
||
104 | $id = $this->generateFilterId($reflectionClass, $filterClass, $filterAnnotation->id); |
||
105 | |||
106 | if (!isset($filters[$id])) { |
||
107 | $filters[$id] = [$filterAnnotation->arguments, $filterClass]; |
||
108 | } |
||
109 | |||
110 | if ($properties = $this->getFilterProperties($filterAnnotation, $reflectionClass, $reflectionProperty)) { |
||
111 | if (isset($filters[$id][0]['properties'])) { |
||
112 | $filters[$id][0]['properties'] = array_merge($filters[$id][0]['properties'], $properties); |
||
113 | } else { |
||
114 | $filters[$id][0]['properties'] = $properties; |
||
115 | } |
||
116 | } |
||
117 | } |
||
118 | } |
||
119 | |||
120 | $parent = $reflectionClass->getParentClass(); |
||
121 | |||
122 | if (false !== $parent) { |
||
123 | return array_merge($filters, $this->readFilterAnnotations($parent, $reader)); |
||
124 | } |
||
125 | |||
126 | return $filters; |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Generates a unique, per-class and per-filter identifier prefixed by `annotated_`. |
||
131 | * |
||
132 | * @param \ReflectionClass $reflectionClass the reflection class of a Resource |
||
133 | * @param string $filterClass the filter class |
||
134 | * @param string $filterId the filter id |
||
135 | */ |
||
136 | private function generateFilterId(\ReflectionClass $reflectionClass, string $filterClass, string $filterId = null): string |
||
137 | { |
||
138 | $suffix = null !== $filterId ? '_'.$filterId : $filterId; |
||
139 | |||
140 | return 'annotated_'.Inflector::tableize(str_replace('\\', '', $reflectionClass->getName().(new \ReflectionClass($filterClass))->getName().$suffix)); |
||
141 | } |
||
142 | } |
||
143 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.