Skip to content

Commit 08f0acb

Browse files
committed
Manage embedded fields for API Platform Filter #2
1 parent 8c27b49 commit 08f0acb

File tree

8 files changed

+71
-40
lines changed

8 files changed

+71
-40
lines changed

src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
5656
return;
5757
}
5858

59-
foreach ($this->extractProperties($request) as $property => $value) {
59+
foreach ($this->extractProperties($request, $resourceClass) as $property => $value) {
6060
$this->filterProperty($property, $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName);
6161
}
6262
}
@@ -92,14 +92,15 @@ protected function getClassMetadata(string $resourceClass): ClassMetadata
9292
* Determines whether the given property is enabled.
9393
*
9494
* @param string $property
95+
* @param string $resourceClass
9596
*
9697
* @return bool
9798
*/
98-
protected function isPropertyEnabled(string $property): bool
99+
protected function isPropertyEnabled(string $property, string $resourceClass): bool
99100
{
100101
if (null === $this->properties) {
101102
// to ensure sanity, nested properties must still be explicitly enabled
102-
return !$this->isPropertyNested($property);
103+
return !$this->isPropertyNested($property, $resourceClass);
103104
}
104105

105106
return array_key_exists($property, $this->properties);
@@ -116,8 +117,8 @@ protected function isPropertyEnabled(string $property): bool
116117
*/
117118
protected function isPropertyMapped(string $property, string $resourceClass, bool $allowAssociation = false): bool
118119
{
119-
if ($this->isPropertyNested($property)) {
120-
$propertyParts = $this->splitPropertyParts($property);
120+
if ($this->isPropertyNested($property, $resourceClass)) {
121+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
121122
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
122123
$property = $propertyParts['field'];
123124
} else {
@@ -131,12 +132,28 @@ protected function isPropertyMapped(string $property, string $resourceClass, boo
131132
* Determines whether the given property is nested.
132133
*
133134
* @param string $property
135+
* @param string $resourceClass
136+
*
137+
* @return bool
138+
*/
139+
protected function isPropertyNested(string $property, string $resourceClass): bool
140+
{
141+
return false !== strpos($property, '.') && false === $this->isPropertyEmbedded($property, $resourceClass);
142+
}
143+
144+
/**
145+
* Determines whether the given property is embedded.
146+
*
147+
* @param string $property
148+
* @param string $resourceClass
134149
*
135150
* @return bool
136151
*/
137-
protected function isPropertyNested(string $property): bool
152+
protected function isPropertyEmbedded(string $property, string $resourceClass): bool
138153
{
139-
return false !== strpos($property, '.');
154+
$metadata = $this->getClassMetadata($resourceClass);
155+
156+
return $metadata->hasField($property);
140157
}
141158

142159
/**
@@ -173,33 +190,45 @@ protected function getNestedMetadata(string $resourceClass, array $associations)
173190
* - field: string holding the actual field (leaf node)
174191
*
175192
* @param string $property
193+
* @param string $resourceClass
176194
*
177195
* @return array
178196
*/
179-
protected function splitPropertyParts(string $property): array
197+
protected function splitPropertyParts(string $property, string $resourceClass): array
180198
{
181199
$parts = explode('.', $property);
200+
$metadata = $this->getClassMetadata($resourceClass);
201+
$slice = 0;
202+
203+
foreach ($parts as $part) {
204+
if ($metadata->hasAssociation($part)) {
205+
$metadata = $this->getClassMetadata($metadata->getAssociationTargetClass($part));
206+
} else {
207+
$slice -= 1;
208+
}
209+
}
182210

183211
return [
184-
'associations' => array_slice($parts, 0, -1),
185-
'field' => end($parts),
212+
'associations' => array_slice($parts, 0, $slice),
213+
'field' => implode('.', array_slice($parts, $slice)),
186214
];
187215
}
188216

189217
/**
190218
* Extracts properties to filter from the request.
191219
*
192220
* @param Request $request
221+
* @param string $resourceClass
193222
*
194223
* @return array
195224
*/
196-
protected function extractProperties(Request $request): array
225+
protected function extractProperties(Request $request, string $resourceClass): array
197226
{
198227
$needsFixing = false;
199228

200229
if (null !== $this->properties) {
201230
foreach ($this->properties as $property => $value) {
202-
if ($this->isPropertyNested($property) && $request->query->has(str_replace('.', '_', $property))) {
231+
if (($this->isPropertyNested($property, $resourceClass) || $this->isPropertyEmbedded($property, $resourceClass)) && $request->query->has(str_replace('.', '_', $property))) {
203232
$needsFixing = true;
204233
}
205234
}
@@ -219,16 +248,17 @@ protected function extractProperties(Request $request): array
219248
* @param string $rootAlias
220249
* @param QueryBuilder $queryBuilder
221250
* @param QueryNameGeneratorInterface $queryNameGenerator
251+
* @param string $resourceClass
222252
*
223253
* @throws InvalidArgumentException If property is not nested
224254
*
225255
* @return array An array where the first element is the join $alias of the leaf entity,
226256
* the second element is the $field name
227257
* the third element is the $associations array
228258
*/
229-
protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator): array
259+
protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass): array
230260
{
231-
$propertyParts = $this->splitPropertyParts($property);
261+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
232262
$parentAlias = $rootAlias;
233263

234264
foreach ($propertyParts['associations'] as $association) {

src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function getDescription(string $resourceClass): array
6363
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
6464
{
6565
if (
66-
!$this->isPropertyEnabled($property) ||
66+
!$this->isPropertyEnabled($property, $resourceClass) ||
6767
!$this->isPropertyMapped($property, $resourceClass) ||
6868
!$this->isBooleanField($property, $resourceClass)
6969
) {
@@ -90,9 +90,10 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
9090
$alias = 'o';
9191
$field = $property;
9292

93-
if ($this->isPropertyNested($property)) {
94-
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator);
93+
if ($this->isPropertyNested($property, $resourceClass)) {
94+
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
9595
}
96+
9697
$valueParameter = $queryNameGenerator->generateParameterName($field);
9798

9899
$queryBuilder
@@ -110,7 +111,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
110111
*/
111112
protected function isBooleanField(string $property, string $resourceClass): bool
112113
{
113-
$propertyParts = $this->splitPropertyParts($property);
114+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
114115
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
115116

116117
return DBALType::BOOLEAN === $metadata->getTypeOfField($propertyParts['field']);

src/Bridge/Doctrine/Orm/Filter/DateFilter.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
6666
// Expect $values to be an array having the period as keys and the date value as values
6767
if (
6868
!is_array($values) ||
69-
!$this->isPropertyEnabled($property) ||
69+
!$this->isPropertyEnabled($property, $resourceClass) ||
7070
!$this->isPropertyMapped($property, $resourceClass) ||
7171
!$this->isDateField($property, $resourceClass)
7272
) {
@@ -76,8 +76,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
7676
$alias = 'o';
7777
$field = $property;
7878

79-
if ($this->isPropertyNested($property)) {
80-
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator);
79+
if ($this->isPropertyNested($property, $resourceClass)) {
80+
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
8181
}
8282

8383
$nullManagement = $this->properties[$property] ?? null;
@@ -157,7 +157,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
157157
*/
158158
protected function isDateField(string $property, string $resourceClass): bool
159159
{
160-
$propertyParts = $this->splitPropertyParts($property);
160+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
161161
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
162162

163163
return isset(self::DOCTRINE_DATE_TYPES[$metadata->getTypeOfField($propertyParts['field'])]);

src/Bridge/Doctrine/Orm/Filter/NumericFilter.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function getDescription(string $resourceClass): array
5858
if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isNumericField($property, $resourceClass)) {
5959
continue;
6060
}
61-
$propertyParts = $this->splitPropertyParts($property);
61+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
6262
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
6363

6464
$description[$property] = [
@@ -97,7 +97,7 @@ private function getType(string $doctrineType): string
9797
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
9898
{
9999
if (
100-
!$this->isPropertyEnabled($property) ||
100+
!$this->isPropertyEnabled($property, $resourceClass) ||
101101
!$this->isPropertyMapped($property, $resourceClass) ||
102102
!$this->isNumericField($property, $resourceClass)
103103
) {
@@ -115,8 +115,8 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
115115
$alias = 'o';
116116
$field = $property;
117117

118-
if ($this->isPropertyNested($property)) {
119-
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator);
118+
if ($this->isPropertyNested($property, $resourceClass)) {
119+
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
120120
}
121121
$valueParameter = $queryNameGenerator->generateParameterName($field);
122122

@@ -135,7 +135,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
135135
*/
136136
protected function isNumericField(string $property, string $resourceClass): bool
137137
{
138-
$propertyParts = $this->splitPropertyParts($property);
138+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
139139
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
140140

141141
return isset(self::DOCTRINE_NUMERIC_TYPES[$metadata->getTypeOfField($propertyParts['field'])]);

src/Bridge/Doctrine/Orm/Filter/OrderFilter.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function getDescription(string $resourceClass): array
7777
*/
7878
protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
7979
{
80-
if (!$this->isPropertyEnabled($property) || !$this->isPropertyMapped($property, $resourceClass)) {
80+
if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) {
8181
return;
8282
}
8383

@@ -94,8 +94,8 @@ protected function filterProperty(string $property, $direction, QueryBuilder $qu
9494
$alias = 'o';
9595
$field = $property;
9696

97-
if ($this->isPropertyNested($property)) {
98-
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator);
97+
if ($this->isPropertyNested($property, $resourceClass)) {
98+
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
9999
}
100100

101101
$queryBuilder->addOrderBy(sprintf('%s.%s', $alias, $field), $direction);
@@ -104,7 +104,7 @@ protected function filterProperty(string $property, $direction, QueryBuilder $qu
104104
/**
105105
* {@inheritdoc}
106106
*/
107-
protected function extractProperties(Request $request): array
107+
protected function extractProperties(Request $request, string $resourceClass): array
108108
{
109109
return $request->query->get($this->orderParameterName, []);
110110
}

src/Bridge/Doctrine/Orm/Filter/RangeFilter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
6262
{
6363
if (
6464
!is_array($values) ||
65-
!$this->isPropertyEnabled($property) ||
65+
!$this->isPropertyEnabled($property, $resourceClass) ||
6666
!$this->isPropertyMapped($property, $resourceClass)
6767
) {
6868
return;
@@ -71,8 +71,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
7171
$alias = 'o';
7272
$field = $property;
7373

74-
if ($this->isPropertyNested($property)) {
75-
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator);
74+
if ($this->isPropertyNested($property, $resourceClass)) {
75+
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
7676
}
7777

7878
foreach ($values as $operator => $value) {

src/Bridge/Doctrine/Orm/Filter/SearchFilter.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ public function getDescription(string $resourceClass): array
8282
continue;
8383
}
8484

85-
if ($this->isPropertyNested($property)) {
86-
$propertyParts = $this->splitPropertyParts($property);
85+
if ($this->isPropertyNested($property, $resourceClass)) {
86+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
8787
$field = $propertyParts['field'];
8888
$metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
8989
} else {
@@ -169,7 +169,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
169169
{
170170
if (
171171
null === $value ||
172-
!$this->isPropertyEnabled($property) ||
172+
!$this->isPropertyEnabled($property, $resourceClass) ||
173173
!$this->isPropertyMapped($property, $resourceClass, true)
174174
) {
175175
return;
@@ -178,8 +178,8 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
178178
$alias = 'o';
179179
$field = $property;
180180

181-
if ($this->isPropertyNested($property)) {
182-
list($alias, $field, $associations) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator);
181+
if ($this->isPropertyNested($property, $resourceClass)) {
182+
list($alias, $field, $associations) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
183183
$metadata = $this->getNestedMetadata($resourceClass, $associations);
184184
} else {
185185
$metadata = $this->getClassMetadata($resourceClass);

src/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ public function generateJoinAlias(string $association): string
3636
*/
3737
public function generateParameterName(string $name): string
3838
{
39-
return sprintf('%s_p%d', $name, $this->incrementedName++);
39+
return sprintf('%s_p%d', str_replace('.', '_', $name), $this->incrementedName++);
4040
}
4141
}

0 commit comments

Comments
 (0)