Skip to content

Commit 9179b36

Browse files
authored
feat(doctrine): per-property filter map in FreeTextQueryFilter (#8257)
1 parent 3c658d1 commit 9179b36

5 files changed

Lines changed: 97 additions & 18 deletions

File tree

src/Doctrine/Odm/Filter/FreeTextQueryFilter.php

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,51 @@ final class FreeTextQueryFilter implements FilterInterface, ManagerRegistryAware
2828
use ManagerRegistryAwareTrait;
2929

3030
/**
31-
* @param list<string> $properties an array of properties, defaults to `parameter->getProperties()`
31+
* @param FilterInterface|array<string, FilterInterface> $filter a filter applied to every property,
32+
* or a map of `property => filter` to use a
33+
* dedicated filter per property
34+
* @param list<string>|null $properties an array of properties, defaults to
35+
* the map keys when `$filter` is a map,
36+
* otherwise to `parameter->getProperties()`
3237
*/
33-
public function __construct(private readonly FilterInterface $filter, private readonly ?array $properties = null)
38+
public function __construct(private readonly FilterInterface|array $filter, private readonly ?array $properties = null)
3439
{
3540
}
3641

3742
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
3843
{
39-
if ($this->filter instanceof ManagerRegistryAwareInterface) {
40-
$this->filter->setManagerRegistry($this->getManagerRegistry());
41-
}
44+
$filterMap = \is_array($this->filter) ? $this->filter : null;
45+
46+
if (null === $filterMap) {
47+
if ($this->filter instanceof ManagerRegistryAwareInterface) {
48+
$this->filter->setManagerRegistry($this->getManagerRegistry());
49+
}
4250

43-
if ($this->filter instanceof LoggerAwareInterface) {
44-
$this->filter->setLogger($this->getLogger());
51+
if ($this->filter instanceof LoggerAwareInterface) {
52+
$this->filter->setLogger($this->getLogger());
53+
}
4554
}
4655

4756
$parameter = $context['parameter'];
48-
foreach ($this->properties ?? $parameter->getProperties() ?? [] as $property) {
57+
$properties = $this->properties ?? (null !== $filterMap ? array_keys($filterMap) : $parameter->getProperties()) ?? [];
58+
59+
foreach ($properties as $property) {
60+
$filter = null !== $filterMap ? ($filterMap[$property] ?? null) : $this->filter;
61+
62+
if (null === $filter) {
63+
continue;
64+
}
65+
66+
if (null !== $filterMap) {
67+
if ($filter instanceof ManagerRegistryAwareInterface) {
68+
$filter->setManagerRegistry($this->getManagerRegistry());
69+
}
70+
71+
if ($filter instanceof LoggerAwareInterface) {
72+
$filter->setLogger($this->getLogger());
73+
}
74+
}
75+
4976
$subParameter = $parameter->withProperty($property);
5077

5178
$nestedPropertiesInfo = $parameter->getExtraProperties()['nested_properties_info'] ?? [];
@@ -57,7 +84,7 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, ?Opera
5784
]);
5885

5986
$newContext = ['parameter' => $subParameter, 'match' => $context['match'] ?? $aggregationBuilder->match()->expr()] + $context;
60-
$this->filter->apply(
87+
$filter->apply(
6188
$aggregationBuilder,
6289
$resourceClass,
6390
$operation,

src/Doctrine/Orm/Filter/FreeTextQueryFilter.php

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,54 @@ final class FreeTextQueryFilter implements FilterInterface, ManagerRegistryAware
3131
use ManagerRegistryAwareTrait;
3232

3333
/**
34-
* @param list<string> $properties an array of properties, defaults to `parameter->getProperties()`
34+
* @param FilterInterface|array<string, FilterInterface> $filter a filter applied to every property,
35+
* or a map of `property => filter` to use a
36+
* dedicated filter per property
37+
* @param list<string>|null $properties an array of properties, defaults to
38+
* the map keys when `$filter` is a map,
39+
* otherwise to `parameter->getProperties()`
3540
*/
36-
public function __construct(private readonly FilterInterface $filter, private readonly ?array $properties = null)
41+
public function __construct(private readonly FilterInterface|array $filter, private readonly ?array $properties = null)
3742
{
3843
}
3944

4045
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
4146
{
42-
if ($this->filter instanceof ManagerRegistryAwareInterface) {
43-
$this->filter->setManagerRegistry($this->getManagerRegistry());
44-
}
47+
$filterMap = \is_array($this->filter) ? $this->filter : null;
48+
49+
if (null === $filterMap) {
50+
if ($this->filter instanceof ManagerRegistryAwareInterface) {
51+
$this->filter->setManagerRegistry($this->getManagerRegistry());
52+
}
4553

46-
if ($this->filter instanceof LoggerAwareInterface) {
47-
$this->filter->setLogger($this->getLogger());
54+
if ($this->filter instanceof LoggerAwareInterface) {
55+
$this->filter->setLogger($this->getLogger());
56+
}
4857
}
4958

5059
$parameter = $context['parameter'];
5160
$qb = clone $queryBuilder;
5261
$qb->resetDQLPart('where');
5362
$qb->setParameters(new ArrayCollection());
54-
foreach ($this->properties ?? $parameter->getProperties() ?? [] as $property) {
63+
$properties = $this->properties ?? (null !== $filterMap ? array_keys($filterMap) : $parameter->getProperties()) ?? [];
64+
65+
foreach ($properties as $property) {
66+
$filter = null !== $filterMap ? ($filterMap[$property] ?? null) : $this->filter;
67+
68+
if (null === $filter) {
69+
continue;
70+
}
71+
72+
if (null !== $filterMap) {
73+
if ($filter instanceof ManagerRegistryAwareInterface) {
74+
$filter->setManagerRegistry($this->getManagerRegistry());
75+
}
76+
77+
if ($filter instanceof LoggerAwareInterface) {
78+
$filter->setLogger($this->getLogger());
79+
}
80+
}
81+
5582
$subParameter = $parameter->withProperty($property);
5683

5784
$nestedPropertiesInfo = $parameter->getExtraProperties()['nested_properties_info'] ?? [];
@@ -62,7 +89,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
6289
: [],
6390
]);
6491

65-
$this->filter->apply(
92+
$filter->apply(
6693
$qb,
6794
$queryNameGenerator,
6895
$resourceClass,

tests/Fixtures/TestBundle/Document/Chicken.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
),
4848
'autocomplete' => new QueryParameter(filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())), properties: ['name', 'ean']),
4949
'q' => new QueryParameter(filter: new FreeTextQueryFilter(new PartialSearchFilter()), properties: ['name', 'ean']),
50+
'qmixed' => new QueryParameter(filter: new FreeTextQueryFilter([
51+
'name' => new OrFilter(new PartialSearchFilter()),
52+
'ean' => new OrFilter(new ExactFilter()),
53+
]), description: 'Partial name match or exact ean match'),
5054
'ownerNamePartial' => new QueryParameter(
5155
filter: new PartialSearchFilter(),
5256
property: 'owner.name',

tests/Fixtures/TestBundle/Entity/Chicken.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
),
4848
'autocomplete' => new QueryParameter(filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())), properties: ['name', 'ean']),
4949
'q' => new QueryParameter(filter: new FreeTextQueryFilter(new PartialSearchFilter()), properties: ['name', 'ean']),
50+
'qmixed' => new QueryParameter(filter: new FreeTextQueryFilter([
51+
'name' => new OrFilter(new PartialSearchFilter()),
52+
'ean' => new OrFilter(new ExactFilter()),
53+
]), description: 'Partial name match or exact ean match'),
5054
'ownerNamePartial' => new QueryParameter(
5155
filter: new PartialSearchFilter(),
5256
property: 'owner.name',

tests/Functional/Parameters/FreeTextQueryFilterTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,23 @@ public function testFreeTextQueryFilterWithTwoLevelTraversalPartial(): void
100100
$this->assertCount(2, $response['member']);
101101
}
102102

103+
public function testFreeTextQueryFilterWithPerPropertyFilterMap(): void
104+
{
105+
$client = $this->createClient();
106+
107+
$response = $client->request('GET', '/chickens?qmixed=Henri')->toArray();
108+
$this->assertJsonContains(['totalItems' => 1]);
109+
$this->assertSame('Henriette', $response['member'][0]['name']);
110+
111+
$response = $client->request('GET', '/chickens?qmixed=978020137963')->toArray();
112+
$this->assertJsonContains(['totalItems' => 1]);
113+
$this->assertSame('978020137963', $response['member'][0]['ean']);
114+
115+
$response = $client->request('GET', '/chickens?qmixed=97802')->toArray();
116+
$this->assertJsonContains(['totalItems' => 1]);
117+
$this->assertSame('978020137962', $response['member'][0]['name']);
118+
}
119+
103120
public function testFreeTextQueryFilterWithTwoLevelTraversalPartialWithPropertyPlaceholder(): void
104121
{
105122
$client = $this->createClient();

0 commit comments

Comments
 (0)