Skip to content

Commit 7b5cd92

Browse files
committed
fix: fix PartialSearchFilter
Fixes #7487
1 parent fa6a5e4 commit 7b5cd92

File tree

3 files changed

+67
-30
lines changed

3 files changed

+67
-30
lines changed

src/Doctrine/Orm/Filter/PartialSearchFilter.php

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,46 +20,64 @@
2020
use ApiPlatform\Metadata\Operation;
2121
use Doctrine\ORM\QueryBuilder;
2222

23+
use function is_iterable;
24+
use function strtolower;
25+
2326
/**
2427
* @author Vincent Amstoutz <[email protected]>
28+
* @author Ré Schopmeijer <[email protected]>
2529
*/
2630
final class PartialSearchFilter implements FilterInterface, OpenApiParameterFilterInterface
2731
{
2832
use BackwardCompatibleFilterDescriptionTrait;
2933
use OpenApiFilterTrait;
3034

31-
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
32-
{
33-
$parameter = $context['parameter'];
34-
$property = $parameter->getProperty();
35-
$alias = $queryBuilder->getRootAliases()[0];
36-
$field = $alias.'.'.$property;
37-
$parameterName = $queryNameGenerator->generateParameterName($property);
38-
$values = $parameter->getValue();
39-
40-
if (!is_iterable($values)) {
41-
$queryBuilder->setParameter($parameterName, '%'.strtolower($values).'%');
35+
public function apply(
36+
QueryBuilder $queryBuilder,
37+
QueryNameGeneratorInterface $queryNameGenerator,
38+
string $resourceClass,
39+
?Operation $operation = null,
40+
array $context = [],
41+
): void {
42+
$parameter = $context['parameter'];
43+
$alias = $queryBuilder->getRootAliases()[0];
44+
$properties = $parameter->getProperties() ?? [$parameter->getProperty()];
45+
foreach ($properties as $property) {
46+
$field = $alias . '.' . $property;
47+
$parameterName = $queryNameGenerator->generateParameterName($property);
48+
$values = $parameter->getValue();
4249

43-
$queryBuilder->{$context['whereClause'] ?? 'andWhere'}($queryBuilder->expr()->like(
44-
'LOWER('.$field.')',
45-
':'.$parameterName
46-
));
50+
if (!is_iterable($values)) {
51+
$queryBuilder->setParameter($parameterName, '%' . strtolower($values) . '%');
4752

48-
return;
49-
}
53+
$queryBuilder->{$context['whereClause'] ?? 'andWhere'}(
54+
$queryBuilder
55+
->expr()
56+
->like(
57+
'LOWER(' . $field . ')',
58+
':' . $parameterName,
59+
),
60+
);
61+
} else {
62+
$likeExpressions = [];
63+
foreach ($values as $val) {
64+
$parameterName = $queryNameGenerator->generateParameterName($property);
65+
$likeExpressions[] = $queryBuilder
66+
->expr()
67+
->like(
68+
'LOWER(' . $field . ')',
69+
':' . $parameterName,
70+
)
71+
;
72+
$queryBuilder->setParameter($parameterName, '%' . strtolower($val) . '%');
73+
}
5074

51-
$likeExpressions = [];
52-
foreach ($values as $val) {
53-
$parameterName = $queryNameGenerator->generateParameterName($property);
54-
$likeExpressions[] = $queryBuilder->expr()->like(
55-
'LOWER('.$field.')',
56-
':'.$parameterName
57-
);
58-
$queryBuilder->setParameter($parameterName, '%'.strtolower($val).'%');
75+
$queryBuilder->{$context['whereClause'] ?? 'andWhere'}(
76+
$queryBuilder
77+
->expr()
78+
->orX(...$likeExpressions),
79+
);
80+
}
5981
}
60-
61-
$queryBuilder->{$context['whereClause'] ?? 'andWhere'}(
62-
$queryBuilder->expr()->orX(...$likeExpressions)
63-
);
6482
}
6583
}

tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
1515

16+
use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
1617
use ApiPlatform\Metadata\ApiFilter;
1718
use ApiPlatform\Metadata\ApiResource;
1819
use ApiPlatform\Metadata\GetCollection;
@@ -34,6 +35,10 @@
3435
'searchPartial[:property]' => new QueryParameter(filter: 'app_search_filter_partial'),
3536
'searchExact[:property]' => new QueryParameter(filter: 'app_search_filter_with_exact'),
3637
'searchOnTextAndDate[:property]' => new QueryParameter(filter: 'app_filter_date_and_search'),
38+
'searchParameter[:property]' => new QueryParameter(
39+
filter : new PartialSearchFilter(),
40+
properties: ['foo']
41+
),
3742
'q' => new QueryParameter(property: 'hydra:freetextQuery'),
3843
]
3944
)]

tests/Functional/Parameters/DoctrineTest.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function testDoctrineEntitySearchFilter(): void
4949
$this->assertEquals('bar', $a['hydra:member'][1]['foo']);
5050

5151
$this->assertArraySubset(['hydra:search' => [
52-
'hydra:template' => \sprintf('/%s{?foo,fooAlias,order[order[id]],order[order[foo]],searchPartial[foo],searchExact[foo],searchOnTextAndDate[foo],searchOnTextAndDate[createdAt][before],searchOnTextAndDate[createdAt][strictly_before],searchOnTextAndDate[createdAt][after],searchOnTextAndDate[createdAt][strictly_after],q,id,createdAt}', $route),
52+
'hydra:template' => \sprintf('/%s{?foo,fooAlias,order[order[id]],order[order[foo]],searchPartial[foo],searchExact[foo],searchOnTextAndDate[foo],searchOnTextAndDate[createdAt][before],searchOnTextAndDate[createdAt][strictly_before],searchOnTextAndDate[createdAt][after],searchOnTextAndDate[createdAt][strictly_after],searchParameter[foo],q,id,createdAt}', $route),
5353
]], $a);
5454

5555
$this->assertArraySubset(['@type' => 'IriTemplateMapping', 'variable' => 'fooAlias', 'property' => 'foo'], $a['hydra:search']['hydra:mapping'][1]);
@@ -118,6 +118,20 @@ public function testPropertyPlaceholderFilter(): void
118118
$this->assertEquals($a['hydra:member'][0]['foo'], 'baz');
119119
}
120120

121+
public function testPartialSearchFilter(): void
122+
{
123+
static::bootKernel();
124+
$resource = $this->isMongoDB() ? SearchFilterParameterDocument::class : SearchFilterParameter::class;
125+
$this->recreateSchema([$resource]);
126+
$this->loadFixtures($resource);
127+
$route = 'search_filter_parameter';
128+
$response = self::createClient()
129+
->request('GET', $route . '?searchParameter[foo]=baz')
130+
;
131+
$a = $response->toArray();
132+
$this->assertEquals($a['hydra:member'][0]['foo'], 'baz');
133+
}
134+
121135
public function testStateOptions(): void
122136
{
123137
if ($this->isMongoDB()) {

0 commit comments

Comments
 (0)