14
14
use Doctrine \Common \Persistence \ManagerRegistry ;
15
15
use Doctrine \Common \Persistence \Mapping \ClassMetadata ;
16
16
use Dunglas \ApiBundle \Api \ResourceInterface ;
17
+ use Dunglas \ApiBundle \Util \RequestParser ;
17
18
use Symfony \Component \HttpFoundation \Request ;
18
19
19
20
/**
23
24
*
24
25
* @author Kévin Dunglas <[email protected] >
25
26
* @author Théo FIDRY <[email protected] >
27
+ * @author Vincent Chalamon <[email protected] >
26
28
*/
27
29
abstract class AbstractFilter implements FilterInterface
28
30
{
@@ -61,15 +63,102 @@ protected function getClassMetadata(ResourceInterface $resource)
61
63
}
62
64
63
65
/**
64
- * Is the given property enabled?
66
+ * Determines whether the given property is enabled.
65
67
*
66
68
* @param string $property
67
69
*
68
70
* @return bool
69
71
*/
70
72
protected function isPropertyEnabled ($ property )
71
73
{
72
- return null === $ this ->properties || array_key_exists ($ property , $ this ->properties );
74
+ if (null === $ this ->properties ) {
75
+ // to ensure sanity, nested properties must still be explicitly enabled
76
+ return !$ this ->isPropertyNested ($ property );
77
+ }
78
+
79
+ return array_key_exists ($ property , $ this ->properties );
80
+ }
81
+
82
+ /**
83
+ * Determines whether the given property is mapped.
84
+ *
85
+ * @param string $property
86
+ * @param ResourceInterface $resource
87
+ * @param bool $allowAssociation
88
+ *
89
+ * @return bool
90
+ */
91
+ protected function isPropertyMapped ($ property , ResourceInterface $ resource , $ allowAssociation = false )
92
+ {
93
+ if ($ this ->isPropertyNested ($ property )) {
94
+ $ propertyParts = $ this ->splitPropertyParts ($ property );
95
+ $ metadata = $ this ->getNestedMetadata ($ resource , $ propertyParts ['associations ' ]);
96
+ $ property = $ propertyParts ['field ' ];
97
+ } else {
98
+ $ metadata = $ this ->getClassMetadata ($ resource );
99
+ }
100
+
101
+ return $ metadata ->hasField ($ property ) || ($ allowAssociation && $ metadata ->hasAssociation ($ property ));
102
+ }
103
+
104
+ /**
105
+ * Determines whether the given property is nested.
106
+ *
107
+ * @param string $property
108
+ *
109
+ * @return bool
110
+ */
111
+ protected function isPropertyNested ($ property )
112
+ {
113
+ return false !== strpos ($ property , '. ' );
114
+ }
115
+
116
+ /**
117
+ * Gets nested class metadata for the given resource.
118
+ *
119
+ * @param ResourceInterface $resource
120
+ * @param string[] $associations
121
+ *
122
+ * @return ClassMetadata
123
+ */
124
+ protected function getNestedMetadata (ResourceInterface $ resource , array $ associations )
125
+ {
126
+ $ metadata = $ this ->getClassMetadata ($ resource );
127
+
128
+ foreach ($ associations as $ association ) {
129
+ if ($ metadata ->hasAssociation ($ association )) {
130
+ $ associationClass = $ metadata ->getAssociationTargetClass ($ association );
131
+
132
+ $ metadata = $ this
133
+ ->managerRegistry
134
+ ->getManagerForClass ($ associationClass )
135
+ ->getClassMetadata ($ associationClass )
136
+ ;
137
+ }
138
+ }
139
+
140
+ return $ metadata ;
141
+ }
142
+
143
+ /**
144
+ * Splits the given property into parts.
145
+ *
146
+ * Returns an array with the following keys:
147
+ * - associations: array of associations according to nesting order
148
+ * - field: string holding the actual field (leaf node)
149
+ *
150
+ * @param string $property
151
+ *
152
+ * @return array
153
+ */
154
+ protected function splitPropertyParts ($ property )
155
+ {
156
+ $ parts = explode ('. ' , $ property );
157
+
158
+ return [
159
+ 'associations ' => array_slice ($ parts , 0 , -1 ),
160
+ 'field ' => end ($ parts ),
161
+ ];
73
162
}
74
163
75
164
/**
@@ -81,6 +170,20 @@ protected function isPropertyEnabled($property)
81
170
*/
82
171
protected function extractProperties (Request $ request )
83
172
{
173
+ $ needsFixing = false ;
174
+
175
+ if (null !== $ this ->properties ) {
176
+ foreach ($ this ->properties as $ property => $ value ) {
177
+ if ($ this ->isPropertyNested ($ property ) && $ request ->query ->has (str_replace ('. ' , '_ ' , $ property ))) {
178
+ $ needsFixing = true ;
179
+ }
180
+ }
181
+ }
182
+
183
+ if ($ needsFixing ) {
184
+ $ request = RequestParser::parseAndDuplicateRequest ($ request );
185
+ }
186
+
84
187
return $ request ->query ->all ();
85
188
}
86
189
}
0 commit comments