Passed
Push — master ( 8bd912...d93388 )
by Alan
06:58 queued 02:20
created

src/Util/AnnotationFilterExtractorTrait.php (1 issue)

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
Bug Best Practice introduced by
The expression $filterAnnotation->properties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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