diff --git a/src/Doctrine/Orm/Filter/ExactFilter.php b/src/Doctrine/Orm/Filter/ExactFilter.php index 37956151713..afe3c063183 100644 --- a/src/Doctrine/Orm/Filter/ExactFilter.php +++ b/src/Doctrine/Orm/Filter/ExactFilter.php @@ -31,19 +31,25 @@ final class ExactFilter implements FilterInterface, OpenApiParameterFilterInterf public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { $parameter = $context['parameter']; - $value = $parameter->getValue(); - $property = $parameter->getProperty(); $alias = $queryBuilder->getRootAliases()[0]; - $parameterName = $queryNameGenerator->generateParameterName($property); - - if (\is_array($value)) { - $queryBuilder - ->{$context['whereClause'] ?? 'andWhere'}(\sprintf('%s.%s IN (:%s)', $alias, $property, $parameterName)); - } else { - $queryBuilder - ->{$context['whereClause'] ?? 'andWhere'}(\sprintf('%s.%s = :%s', $alias, $property, $parameterName)); + $properties = $parameter->getProperties() ?? [$parameter->getProperty()]; + foreach ($properties as $property) { + $parameterName = $queryNameGenerator->generateParameterName($property); + $values = $parameter->getValue(); + + if (is_iterable($values)) { + $queryBuilder + ->{$context['whereClause'] ?? 'andWhere'}( + \sprintf('%s.%s IN (:%s)', $alias, $property, $parameterName) + ); + } else { + $queryBuilder + ->{$context['whereClause'] ?? 'andWhere'}( + \sprintf('%s.%s = :%s', $alias, $property, $parameterName) + ); + } + + $queryBuilder->setParameter($parameterName, $values); } - - $queryBuilder->setParameter($parameterName, $value); } } diff --git a/src/Doctrine/Orm/Filter/PartialSearchFilter.php b/src/Doctrine/Orm/Filter/PartialSearchFilter.php index 90cde75c3fa..f13d436f87f 100644 --- a/src/Doctrine/Orm/Filter/PartialSearchFilter.php +++ b/src/Doctrine/Orm/Filter/PartialSearchFilter.php @@ -22,6 +22,7 @@ /** * @author Vincent Amstoutz + * @author RĂ© Schopmeijer */ final class PartialSearchFilter implements FilterInterface, OpenApiParameterFilterInterface { @@ -31,35 +32,37 @@ final class PartialSearchFilter implements FilterInterface, OpenApiParameterFilt public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { $parameter = $context['parameter']; - $property = $parameter->getProperty(); $alias = $queryBuilder->getRootAliases()[0]; - $field = $alias.'.'.$property; - $parameterName = $queryNameGenerator->generateParameterName($property); - $values = $parameter->getValue(); + $properties = $parameter->getProperties() ?? [$parameter->getProperty()]; + foreach ($properties as $property) { + $field = $alias.'.'.$property; + $parameterName = $queryNameGenerator->generateParameterName($property); + $values = $parameter->getValue(); - if (!is_iterable($values)) { - $queryBuilder->setParameter($parameterName, '%'.strtolower($values).'%'); + if (!is_iterable($values)) { + $queryBuilder->setParameter($parameterName, '%'.strtolower($values).'%'); - $queryBuilder->{$context['whereClause'] ?? 'andWhere'}($queryBuilder->expr()->like( - 'LOWER('.$field.')', - ':'.$parameterName - )); + $queryBuilder->{$context['whereClause'] ?? 'andWhere'}($queryBuilder->expr()->like( + 'LOWER('.$field.')', + ':'.$parameterName, + )); - return; - } + continue; + } - $likeExpressions = []; - foreach ($values as $val) { - $parameterName = $queryNameGenerator->generateParameterName($property); - $likeExpressions[] = $queryBuilder->expr()->like( - 'LOWER('.$field.')', - ':'.$parameterName + $likeExpressions = []; + foreach ($values as $val) { + $parameterName = $queryNameGenerator->generateParameterName($property); + $likeExpressions[] = $queryBuilder->expr()->like( + 'LOWER('.$field.')', + ':'.$parameterName, + ); + $queryBuilder->setParameter($parameterName, '%'.strtolower($val).'%'); + } + + $queryBuilder->{$context['whereClause'] ?? 'andWhere'}( + $queryBuilder->expr()->orX(...$likeExpressions), ); - $queryBuilder->setParameter($parameterName, '%'.strtolower($val).'%'); } - - $queryBuilder->{$context['whereClause'] ?? 'andWhere'}( - $queryBuilder->expr()->orX(...$likeExpressions) - ); } } diff --git a/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php b/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php index 533bb7a14b7..cbfedb892d1 100644 --- a/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php +++ b/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php @@ -13,6 +13,8 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; +use ApiPlatform\Doctrine\Orm\Filter\ExactFilter; +use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; @@ -34,6 +36,14 @@ 'searchPartial[:property]' => new QueryParameter(filter: 'app_search_filter_partial'), 'searchExact[:property]' => new QueryParameter(filter: 'app_search_filter_with_exact'), 'searchOnTextAndDate[:property]' => new QueryParameter(filter: 'app_filter_date_and_search'), + 'searchPartialProperties[:property]' => new QueryParameter( + filter : new PartialSearchFilter(), + properties: ['foo'], + ), + 'searchExactProperties[:property]' => new QueryParameter( + filter: new ExactFilter(), + properties: ['foo'], + ), 'q' => new QueryParameter(property: 'hydra:freetextQuery'), ] )] diff --git a/tests/Functional/Parameters/DoctrineTest.php b/tests/Functional/Parameters/DoctrineTest.php index cf825a61676..90f33789329 100644 --- a/tests/Functional/Parameters/DoctrineTest.php +++ b/tests/Functional/Parameters/DoctrineTest.php @@ -49,7 +49,7 @@ public function testDoctrineEntitySearchFilter(): void $this->assertEquals('bar', $a['hydra:member'][1]['foo']); $this->assertArraySubset(['hydra:search' => [ - '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), + '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],searchPartialProperties[foo],searchExactProperties[foo],q,id,createdAt}', $route), ]], $a); $this->assertArraySubset(['@type' => 'IriTemplateMapping', 'variable' => 'fooAlias', 'property' => 'foo'], $a['hydra:search']['hydra:mapping'][1]); @@ -118,6 +118,30 @@ public function testPropertyPlaceholderFilter(): void $this->assertEquals($a['hydra:member'][0]['foo'], 'baz'); } + public function testPartialSearchFilterWithProperties(): void + { + static::bootKernel(); + $resource = $this->isMongoDB() ? SearchFilterParameterDocument::class : SearchFilterParameter::class; + $this->recreateSchema([$resource]); + $this->loadFixtures($resource); + $route = 'search_filter_parameter'; + $response = self::createClient()->request('GET', $route.'?searchPartialProperties[foo]=baz'); + $a = $response->toArray(); + $this->assertEquals($a['hydra:member'][0]['foo'], 'baz'); + } + + public function testExactFilterWithProperties(): void + { + static::bootKernel(); + $resource = $this->isMongoDB() ? SearchFilterParameterDocument::class : SearchFilterParameter::class; + $this->recreateSchema([$resource]); + $this->loadFixtures($resource); + $route = 'search_filter_parameter'; + $response = self::createClient()->request('GET', $route.'?searchExactProperties[foo]=baz'); + $a = $response->toArray(); + $this->assertEquals($a['hydra:member'][0]['foo'], 'baz'); + } + public function testStateOptions(): void { if ($this->isMongoDB()) {