Skip to content

Commit 572a083

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

File tree

8 files changed

+70
-41
lines changed

8 files changed

+70
-41
lines changed

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

Lines changed: 43 additions & 15 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,27 @@ 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+
* @return bool
137+
*/
138+
protected function isPropertyNested(string $property, string $resourceClass): bool
139+
{
140+
return false !== strpos($property, '.') && false === $this->isPropertyEmbedded($property, $resourceClass);
141+
}
142+
143+
/**
144+
* Determines whether the given property is embedded.
145+
*
146+
* @param string $property
147+
* @param string $resourceClass
134148
*
135149
* @return bool
136150
*/
137-
protected function isPropertyNested(string $property): bool
151+
protected function isPropertyEmbedded(string $property, string $resourceClass): bool
138152
{
139-
return false !== strpos($property, '.');
153+
$metadata = $this->getClassMetadata($resourceClass);
154+
155+
return $metadata->hasField($property);
140156
}
141157

142158
/**
@@ -173,33 +189,44 @@ protected function getNestedMetadata(string $resourceClass, array $associations)
173189
* - field: string holding the actual field (leaf node)
174190
*
175191
* @param string $property
176-
*
192+
* @param string $resourceClass
177193
* @return array
178194
*/
179-
protected function splitPropertyParts(string $property): array
195+
protected function splitPropertyParts(string $property, string $resourceClass): array
180196
{
181197
$parts = explode('.', $property);
198+
$metadata = $this->getClassMetadata($resourceClass);
199+
$slice = 0;
200+
201+
foreach ($parts as $part) {
202+
if ($metadata->hasAssociation($part)) {
203+
$metadata = $this->getClassMetadata($metadata->getAssociationTargetClass($part));
204+
} else {
205+
$slice -= 1;
206+
}
207+
}
182208

183209
return [
184-
'associations' => array_slice($parts, 0, -1),
185-
'field' => end($parts),
210+
'associations' => array_slice($parts, 0, $slice),
211+
'field' => implode('.', array_slice($parts, $slice)),
186212
];
187213
}
188214

189215
/**
190216
* Extracts properties to filter from the request.
191217
*
192218
* @param Request $request
219+
* @param string $resourceClass
193220
*
194221
* @return array
195222
*/
196-
protected function extractProperties(Request $request): array
223+
protected function extractProperties(Request $request, string $resourceClass): array
197224
{
198225
$needsFixing = false;
199226

200227
if (null !== $this->properties) {
201228
foreach ($this->properties as $property => $value) {
202-
if ($this->isPropertyNested($property) && $request->query->has(str_replace('.', '_', $property))) {
229+
if (($this->isPropertyNested($property, $resourceClass) || $this->isPropertyEmbedded($property, $resourceClass)) && $request->query->has(str_replace('.', '_', $property))) {
203230
$needsFixing = true;
204231
}
205232
}
@@ -219,16 +246,17 @@ protected function extractProperties(Request $request): array
219246
* @param string $rootAlias
220247
* @param QueryBuilder $queryBuilder
221248
* @param QueryNameGeneratorInterface $queryNameGenerator
249+
* @param string $resourceClass
222250
*
223251
* @throws InvalidArgumentException If property is not nested
224252
*
225253
* @return array An array where the first element is the join $alias of the leaf entity,
226254
* the second element is the $field name
227255
* the third element is the $associations array
228256
*/
229-
protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator): array
257+
protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass): array
230258
{
231-
$propertyParts = $this->splitPropertyParts($property);
259+
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
232260
$parentAlias = $rootAlias;
233261

234262
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)