Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions src/Doctrine/Odm/Filter/FreeTextQueryFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,51 @@ final class FreeTextQueryFilter implements FilterInterface, ManagerRegistryAware
use ManagerRegistryAwareTrait;

/**
* @param list<string> $properties an array of properties, defaults to `parameter->getProperties()`
* @param FilterInterface|array<string, FilterInterface> $filter a filter applied to every property,
* or a map of `property => filter` to use a
* dedicated filter per property
* @param list<string>|null $properties an array of properties, defaults to
* the map keys when `$filter` is a map,
* otherwise to `parameter->getProperties()`
*/
public function __construct(private readonly FilterInterface $filter, private readonly ?array $properties = null)
public function __construct(private readonly FilterInterface|array $filter, private readonly ?array $properties = null)
{
}

public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
{
if ($this->filter instanceof ManagerRegistryAwareInterface) {
$this->filter->setManagerRegistry($this->getManagerRegistry());
}
$filterMap = \is_array($this->filter) ? $this->filter : null;

if (null === $filterMap) {
if ($this->filter instanceof ManagerRegistryAwareInterface) {
$this->filter->setManagerRegistry($this->getManagerRegistry());
}

if ($this->filter instanceof LoggerAwareInterface) {
$this->filter->setLogger($this->getLogger());
if ($this->filter instanceof LoggerAwareInterface) {
$this->filter->setLogger($this->getLogger());
}
}

$parameter = $context['parameter'];
foreach ($this->properties ?? $parameter->getProperties() ?? [] as $property) {
$properties = $this->properties ?? (null !== $filterMap ? array_keys($filterMap) : $parameter->getProperties()) ?? [];

foreach ($properties as $property) {
$filter = null !== $filterMap ? ($filterMap[$property] ?? null) : $this->filter;

if (null === $filter) {
continue;
}

if (null !== $filterMap) {
if ($filter instanceof ManagerRegistryAwareInterface) {
$filter->setManagerRegistry($this->getManagerRegistry());
}

if ($filter instanceof LoggerAwareInterface) {
$filter->setLogger($this->getLogger());
}
}

$subParameter = $parameter->withProperty($property);

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

$newContext = ['parameter' => $subParameter, 'match' => $context['match'] ?? $aggregationBuilder->match()->expr()] + $context;
$this->filter->apply(
$filter->apply(
$aggregationBuilder,
$resourceClass,
$operation,
Expand Down
45 changes: 36 additions & 9 deletions src/Doctrine/Orm/Filter/FreeTextQueryFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,54 @@ final class FreeTextQueryFilter implements FilterInterface, ManagerRegistryAware
use ManagerRegistryAwareTrait;

/**
* @param list<string> $properties an array of properties, defaults to `parameter->getProperties()`
* @param FilterInterface|array<string, FilterInterface> $filter a filter applied to every property,
* or a map of `property => filter` to use a
* dedicated filter per property
* @param list<string>|null $properties an array of properties, defaults to
* the map keys when `$filter` is a map,
* otherwise to `parameter->getProperties()`
*/
public function __construct(private readonly FilterInterface $filter, private readonly ?array $properties = null)
public function __construct(private readonly FilterInterface|array $filter, private readonly ?array $properties = null)
{
}

public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
if ($this->filter instanceof ManagerRegistryAwareInterface) {
$this->filter->setManagerRegistry($this->getManagerRegistry());
}
$filterMap = \is_array($this->filter) ? $this->filter : null;

if (null === $filterMap) {
if ($this->filter instanceof ManagerRegistryAwareInterface) {
$this->filter->setManagerRegistry($this->getManagerRegistry());
}

if ($this->filter instanceof LoggerAwareInterface) {
$this->filter->setLogger($this->getLogger());
if ($this->filter instanceof LoggerAwareInterface) {
$this->filter->setLogger($this->getLogger());
}
}

$parameter = $context['parameter'];
$qb = clone $queryBuilder;
$qb->resetDQLPart('where');
$qb->setParameters(new ArrayCollection());
foreach ($this->properties ?? $parameter->getProperties() ?? [] as $property) {
$properties = $this->properties ?? (null !== $filterMap ? array_keys($filterMap) : $parameter->getProperties()) ?? [];

foreach ($properties as $property) {
$filter = null !== $filterMap ? ($filterMap[$property] ?? null) : $this->filter;

if (null === $filter) {
continue;
}

if (null !== $filterMap) {
if ($filter instanceof ManagerRegistryAwareInterface) {
$filter->setManagerRegistry($this->getManagerRegistry());
}

if ($filter instanceof LoggerAwareInterface) {
$filter->setLogger($this->getLogger());
}
}

$subParameter = $parameter->withProperty($property);

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

$this->filter->apply(
$filter->apply(
$qb,
$queryNameGenerator,
$resourceClass,
Expand Down
4 changes: 4 additions & 0 deletions tests/Fixtures/TestBundle/Document/Chicken.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
),
'autocomplete' => new QueryParameter(filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())), properties: ['name', 'ean']),
'q' => new QueryParameter(filter: new FreeTextQueryFilter(new PartialSearchFilter()), properties: ['name', 'ean']),
'qmixed' => new QueryParameter(filter: new FreeTextQueryFilter([
'name' => new OrFilter(new PartialSearchFilter()),
'ean' => new OrFilter(new ExactFilter()),
]), description: 'Partial name match or exact ean match'),
'ownerNamePartial' => new QueryParameter(
filter: new PartialSearchFilter(),
property: 'owner.name',
Expand Down
4 changes: 4 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Chicken.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
),
'autocomplete' => new QueryParameter(filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())), properties: ['name', 'ean']),
'q' => new QueryParameter(filter: new FreeTextQueryFilter(new PartialSearchFilter()), properties: ['name', 'ean']),
'qmixed' => new QueryParameter(filter: new FreeTextQueryFilter([
'name' => new OrFilter(new PartialSearchFilter()),
'ean' => new OrFilter(new ExactFilter()),
]), description: 'Partial name match or exact ean match'),
'ownerNamePartial' => new QueryParameter(
filter: new PartialSearchFilter(),
property: 'owner.name',
Expand Down
17 changes: 17 additions & 0 deletions tests/Functional/Parameters/FreeTextQueryFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@ public function testFreeTextQueryFilterWithTwoLevelTraversalPartial(): void
$this->assertCount(2, $response['member']);
}

public function testFreeTextQueryFilterWithPerPropertyFilterMap(): void
{
$client = $this->createClient();

$response = $client->request('GET', '/chickens?qmixed=Henri')->toArray();
$this->assertJsonContains(['totalItems' => 1]);
$this->assertSame('Henriette', $response['member'][0]['name']);

$response = $client->request('GET', '/chickens?qmixed=978020137963')->toArray();
$this->assertJsonContains(['totalItems' => 1]);
$this->assertSame('978020137963', $response['member'][0]['ean']);

$response = $client->request('GET', '/chickens?qmixed=97802')->toArray();
$this->assertJsonContains(['totalItems' => 1]);
$this->assertSame('978020137962', $response['member'][0]['name']);
}

public function testFreeTextQueryFilterWithTwoLevelTraversalPartialWithPropertyPlaceholder(): void
{
$client = $this->createClient();
Expand Down
Loading