From 37bd61cbe0f7e91b49a661974f69c8190dae4fe4 Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Thu, 20 Jul 2023 14:30:19 +0200 Subject: [PATCH] feat(serializer): support for getSupportedTypes (symfony 6.3) --- features/doctrine/search_filter.feature | 9 ++++ .../Extension/FilterEagerLoadingExtension.php | 7 +-- .../ApiResource/Issue5648/CustomFilter.php | 44 +++++++++++++++++++ .../ApiResource/Issue5648/DummyResource.php | 40 +++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/Fixtures/TestBundle/ApiResource/Issue5648/CustomFilter.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/Issue5648/DummyResource.php diff --git a/features/doctrine/search_filter.feature b/features/doctrine/search_filter.feature index 2b8502fa493..4037ac1cbe9 100644 --- a/features/doctrine/search_filter.feature +++ b/features/doctrine/search_filter.feature @@ -1033,3 +1033,12 @@ Feature: Search filter on collections Then the response status code should be 200 And the response should be in JSON And the JSON node "hydra:totalItems" should be equal to 1 + + @!mongodb + @createSchema + Scenario: Custom search filters can use Doctrine Expressions as join conditions + Given there is a dummy object with 3 relatedDummies and their thirdLevel + When I send a "GET" request to "/dummy_ressource_with_custom_filter?custom=3" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "hydra:totalItems" should be equal to 1 diff --git a/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php index dc840135b4f..3f2548712fc 100644 --- a/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ b/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php @@ -167,12 +167,13 @@ private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, Query /** @var Join $joinPart */ $joinString = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinPart->getJoin()); $pos = strpos($joinString, '.'); + $joinCondition = (string) $joinPart->getCondition(); if (false === $pos) { - if (null !== $joinPart->getCondition() && null !== $this->resourceClassResolver && $this->resourceClassResolver->isResourceClass($joinString)) { + if ($joinCondition && $this->resourceClassResolver?->isResourceClass($joinString)) { $newAlias = $queryNameGenerator->generateJoinAlias($joinPart->getAlias()); $aliases[] = "{$joinPart->getAlias()}."; $replacements[] = "$newAlias."; - $condition = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinPart->getCondition()); + $condition = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinCondition); $join = new Join($joinPart->getJoinType(), $joinPart->getJoin(), $newAlias, $joinPart->getConditionType(), $condition); $queryBuilderClone->add('join', [$replacement => $join], true); // @phpstan-ignore-line } @@ -184,7 +185,7 @@ private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, Query $newAlias = $queryNameGenerator->generateJoinAlias($association); $aliases[] = "{$joinPart->getAlias()}."; $replacements[] = "$newAlias."; - $condition = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinPart->getCondition() ?? ''); + $condition = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinCondition); QueryBuilderHelper::addJoinOnce($queryBuilderClone, $queryNameGenerator, $alias, $association, $joinPart->getJoinType(), $joinPart->getConditionType(), $condition, $originAlias, $newAlias); } diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5648/CustomFilter.php b/tests/Fixtures/TestBundle/ApiResource/Issue5648/CustomFilter.php new file mode 100644 index 00000000000..e6c8d62ed3a --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5648/CustomFilter.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5648; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\Query\Expr\Join; +use Doctrine\ORM\QueryBuilder; + +class CustomFilter extends AbstractFilter +{ + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + { + if ('custom' !== $property) { + return; + } + + $alias = $queryBuilder->getRootAliases()[0]; + $secondAlias = $queryNameGenerator->generateJoinAlias('relatedDummies'); + + $joinCondition = $queryBuilder->expr()->like(sprintf('%s.name', $secondAlias), ':param'); + + $queryBuilder->join(sprintf('%s.relatedDummies', $alias), $secondAlias, Join::WITH, $joinCondition) + ->setParameter('param', '%'.$value.'%') + ->andWhere('1=1'); // problem only gets triggered when there is a where part. + } + + public function getDescription(string $resourceClass): array + { + return []; + } +} diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue5648/DummyResource.php b/tests/Fixtures/TestBundle/ApiResource/Issue5648/DummyResource.php new file mode 100644 index 00000000000..5f9c74e71f9 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue5648/DummyResource.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5648; + +use ApiPlatform\Doctrine\Orm\State\Options; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; + +#[ApiResource( + operations: [ + new GetCollection(uriTemplate: '/dummy_ressource_with_custom_filter', itemUriTemplate: '/dummy_ressource_with_custom_filter/{id}'), + new Get(uriTemplate: '/dummy_ressource_with_custom_filter/{id}', uriVariables: ['id']), + ], + stateOptions: new Options(entityClass: Dummy::class) +)] +#[ApiFilter(CustomFilter::class)] +class DummyResource +{ + #[ApiProperty(identifier: true)] + public ?int $id = null; + + public string $name; + + public array $relatedDummies; +}