Skip to content

Commit 5a3a193

Browse files
committed
SearchFilter: Allow strategy on associations
1 parent cfc05d1 commit 5a3a193

File tree

5 files changed

+84
-65
lines changed

5 files changed

+84
-65
lines changed

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait;
2020
use ApiPlatform\Core\Exception\InvalidArgumentException;
2121
use Doctrine\Common\Persistence\ManagerRegistry;
22-
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
2322
use Doctrine\ODM\MongoDB\Aggregation\Builder;
2423
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MongoDBClassMetadata;
2524
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
@@ -94,6 +93,14 @@ protected function filterProperty(string $property, $value, Builder $aggregation
9493

9594
$caseSensitive = true;
9695

96+
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;
97+
98+
// prefixing the strategy with i makes it case insensitive
99+
if (0 === strpos($strategy, 'i')) {
100+
$strategy = substr($strategy, 1);
101+
$caseSensitive = false;
102+
}
103+
97104
if ($metadata->hasField($field) && !$metadata->hasAssociation($field)) {
98105
if ('id' === $field) {
99106
$values = array_map([$this, 'getIdFromValue'], $values);
@@ -107,17 +114,10 @@ protected function filterProperty(string $property, $value, Builder $aggregation
107114
return;
108115
}
109116

110-
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;
111-
112-
// prefixing the strategy with i makes it case insensitive
113-
if (0 === strpos($strategy, 'i')) {
114-
$strategy = substr($strategy, 1);
115-
$caseSensitive = false;
116-
}
117-
118117
$inValues = [];
118+
$type = $metadata->getTypeOfField($field);
119119
foreach ($values as $inValue) {
120-
$inValues[] = $this->addEqualityMatchStrategy($strategy, $field, $inValue, $caseSensitive, $metadata);
120+
$inValues[] = $this->addEqualityMatchStrategy($strategy, $inValue, $caseSensitive, $type);
121121
}
122122

123123
$aggregationBuilder
@@ -149,10 +149,15 @@ protected function filterProperty(string $property, $value, Builder $aggregation
149149
return;
150150
}
151151

152+
$inValues = [];
153+
foreach ($values as $inValue) {
154+
$inValues[] = $this->addEqualityMatchStrategy($strategy, $inValue, $caseSensitive, $doctrineTypeField);
155+
}
156+
152157
$aggregationBuilder
153158
->match()
154159
->field($matchField)
155-
->in($values);
160+
->in($inValues);
156161
}
157162

158163
/**
@@ -162,10 +167,8 @@ protected function filterProperty(string $property, $value, Builder $aggregation
162167
*
163168
* @return Regex|string
164169
*/
165-
private function addEqualityMatchStrategy(string $strategy, string $field, $value, bool $caseSensitive, ClassMetadata $metadata)
170+
private function addEqualityMatchStrategy(string $strategy, $value, bool $caseSensitive, string $type)
166171
{
167-
$type = $metadata->getTypeOfField($field);
168-
169172
switch ($strategy) {
170173
case MongoDbType::STRING !== $type:
171174
return MongoDbType::getType($type)->convertToDatabaseValue($value);

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

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
9595
$doctrineTypeField = $this->getDoctrineFieldType($property, $resourceClass);
9696
$values = array_map([$this, 'getIdFromValue'], $values);
9797

98+
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;
99+
100+
// prefixing the strategy with i makes it case insensitive
101+
if (0 === strpos($strategy, 'i')) {
102+
$strategy = substr($strategy, 1);
103+
$caseSensitive = false;
104+
}
105+
98106
if ($metadata->hasField($field)) {
99107
if (!$this->hasValidValues($values, $doctrineTypeField)) {
100108
$this->logger->notice('Invalid filter ignored', [
@@ -104,34 +112,8 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
104112
return;
105113
}
106114

107-
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;
108-
109-
// prefixing the strategy with i makes it case insensitive
110-
if (0 === strpos($strategy, 'i')) {
111-
$strategy = substr($strategy, 1);
112-
$caseSensitive = false;
113-
}
114-
115-
if (1 === \count($values)) {
116-
$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $doctrineTypeField, $values[0], $caseSensitive);
117-
118-
return;
119-
}
120-
121-
if (self::STRATEGY_EXACT !== $strategy) {
122-
$this->logger->notice('Invalid filter ignored', [
123-
'exception' => new InvalidArgumentException(sprintf('"%s" strategy selected for "%s" property, but only "%s" strategy supports multiple values', $strategy, $property, self::STRATEGY_EXACT)),
124-
]);
125-
126-
return;
127-
}
128-
129-
$wrapCase = $this->createWrapCase($caseSensitive);
130115
$valueParameter = $queryNameGenerator->generateParameterName($field);
131-
132-
$queryBuilder
133-
->andWhere(sprintf($wrapCase('%s.%s').' IN (:%s)', $alias, $field, $valueParameter))
134-
->setParameter($valueParameter, $caseSensitive ? $values : array_map('strtolower', $values));
116+
$this->addWhereByStrategy($strategy, $queryBuilder, $alias, $field, $doctrineTypeField, $values, $caseSensitive, $valueParameter);
135117
}
136118

137119
// metadata doesn't have the field, nor an association on the field
@@ -155,63 +137,67 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
155137
return;
156138
}
157139

158-
$association = $field;
159-
$valueParameter = $queryNameGenerator->generateParameterName($association);
160-
if ($metadata->isCollectionValuedAssociation($association)) {
161-
$associationAlias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $association);
140+
$valueParameter = $queryNameGenerator->generateParameterName($field);
141+
if ($metadata->isCollectionValuedAssociation($field)) {
142+
$associationAlias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $field);
162143
$associationField = $associationFieldIdentifier;
163144
} else {
164145
$associationAlias = $alias;
165146
$associationField = $field;
166147
}
167148

168-
if (1 === \count($values)) {
169-
$queryBuilder
170-
->andWhere(sprintf('%s.%s = :%s', $associationAlias, $associationField, $valueParameter))
171-
->setParameter($valueParameter, $values[0], $doctrineTypeField);
172-
} else {
173-
$queryBuilder
174-
->andWhere(sprintf('%s.%s IN (:%s)', $associationAlias, $associationField, $valueParameter))
175-
->setParameter($valueParameter, $values, $doctrineTypeField);
176-
}
149+
$this->addWhereByStrategy($strategy, $queryBuilder, $associationAlias, $associationField, $doctrineTypeField, $values, $caseSensitive, $valueParameter);
177150
}
178151

179152
/**
180153
* Adds where clause according to the strategy.
181154
*
182155
* @throws InvalidArgumentException If strategy does not exist
183156
*/
184-
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $fieldType, $value, bool $caseSensitive)
157+
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, string $alias, string $field, $fieldType, array $values, bool $caseSensitive, string $valueParameter)
185158
{
186159
$wrapCase = $this->createWrapCase($caseSensitive);
187-
$valueParameter = $queryNameGenerator->generateParameterName($field);
160+
161+
if (self::STRATEGY_EXACT !== $strategy && 1 !== \count($values)) {
162+
$this->logger->notice('Invalid filter ignored', [
163+
'exception' => new InvalidArgumentException(sprintf('"%s" strategy selected for "%s" property, but only "%s" strategy supports multiple values', $strategy, $field, self::STRATEGY_EXACT)),
164+
]);
165+
166+
return;
167+
}
188168

189169
switch ($strategy) {
190170
case null:
191171
case self::STRATEGY_EXACT:
192-
$queryBuilder
193-
->andWhere(sprintf($wrapCase('%s.%s').' = '.$wrapCase(':%s'), $alias, $field, $valueParameter))
194-
->setParameter($valueParameter, $value, $fieldType);
172+
if (1 === \count($values)) {
173+
$queryBuilder
174+
->andWhere(sprintf($wrapCase('%s.%s').' = '.$wrapCase(':%s'), $alias, $field, $valueParameter))
175+
->setParameter($valueParameter, $values[0], $fieldType);
176+
} else {
177+
$queryBuilder
178+
->andWhere(sprintf($wrapCase('%s.%s').' IN (:%s)', $alias, $field, $valueParameter))
179+
->setParameter($valueParameter, $caseSensitive ? $values : array_map('strtolower', $values));
180+
}
195181
break;
196182
case self::STRATEGY_PARTIAL:
197183
$queryBuilder
198184
->andWhere(sprintf($wrapCase('%s.%s').' LIKE '.$wrapCase('CONCAT(\'%%\', :%s, \'%%\')'), $alias, $field, $valueParameter))
199-
->setParameter($valueParameter, $value, $fieldType);
185+
->setParameter($valueParameter, $values[0], $fieldType);
200186
break;
201187
case self::STRATEGY_START:
202188
$queryBuilder
203189
->andWhere(sprintf($wrapCase('%s.%s').' LIKE '.$wrapCase('CONCAT(:%s, \'%%\')'), $alias, $field, $valueParameter))
204-
->setParameter($valueParameter, $value, $fieldType);
190+
->setParameter($valueParameter, $values[0], $fieldType);
205191
break;
206192
case self::STRATEGY_END:
207193
$queryBuilder
208194
->andWhere(sprintf($wrapCase('%s.%s').' LIKE '.$wrapCase('CONCAT(\'%%\', :%s)'), $alias, $field, $valueParameter))
209-
->setParameter($valueParameter, $value, $fieldType);
195+
->setParameter($valueParameter, $values[0], $fieldType);
210196
break;
211197
case self::STRATEGY_WORD_START:
212198
$queryBuilder
213199
->andWhere(sprintf($wrapCase('%1$s.%2$s').' LIKE '.$wrapCase('CONCAT(:%3$s, \'%%\')').' OR '.$wrapCase('%1$s.%2$s').' LIKE '.$wrapCase('CONCAT(\'%% \', :%3$s, \'%%\')'), $alias, $field, $valueParameter))
214-
->setParameter($valueParameter, $value, $fieldType);
200+
->setParameter($valueParameter, $values[0], $fieldType);
215201
break;
216202
default:
217203
throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));

tests/Bridge/Doctrine/Common/Filter/SearchFilterTestTrait.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,15 @@ private function provideApplyTestArguments(): array
361361
'relatedDummy.symfony' => 'exact',
362362
],
363363
],
364+
'partial nested property' => [
365+
[
366+
'id' => null,
367+
'relatedDummy.symfony' => 'partial',
368+
],
369+
[
370+
'relatedDummy.symfony' => 'partial',
371+
],
372+
],
364373
];
365374
}
366375
}

tests/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilterTest.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,15 +518,31 @@ public function provideApplyTestData(): array
518518
],
519519
'nested property' => [
520520
[
521+
[
522+
'$lookup' => [
523+
'from' => 'RelatedDummy',
524+
'localField' => 'relatedDummy',
525+
'foreignField' => '_id',
526+
'as' => 'relatedDummy_lkup',
527+
],
528+
],
529+
[
530+
'$unwind' => '$relatedDummy_lkup',
531+
],
521532
[
522533
'$match' => [
523-
'name' => [
534+
'relatedDummy_lkup.symfony' => [
524535
'$in' => [
525536
'exact',
526537
],
527538
],
528539
],
529540
],
541+
],
542+
$filterFactory,
543+
],
544+
'partial nested property' => [
545+
[
530546
[
531547
'$lookup' => [
532548
'from' => 'RelatedDummy',
@@ -542,7 +558,7 @@ public function provideApplyTestData(): array
542558
'$match' => [
543559
'relatedDummy_lkup.symfony' => [
544560
'$in' => [
545-
'exact',
561+
new Regex('partial'),
546562
],
547563
],
548564
],

tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,11 @@ public function provideApplyTestData(): array
491491
],
492492
$filterFactory,
493493
],
494+
'partial nested property' => [
495+
sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.symfony LIKE CONCAT(\'%%\', :symfony_p1, \'%%\')', $this->alias, Dummy::class),
496+
['symfony_p1' => 'partial'],
497+
$filterFactory,
498+
],
494499
]
495500
);
496501
}

0 commit comments

Comments
 (0)