Skip to content

Commit 2143660

Browse files
committed
fix(doctrine): inject nameConverter into AbstractFilter via QueryParameter (#7866)
| Q | A | ------------- | --- | Branch? | main | Tickets | Fixes #7866 | License | MIT | Doc PR | ∅ AbstractFilter subclasses (DateFilter, OrderFilter, etc.) silently failed when used with QueryParameter and a nameConverter configured, because the nameConverter was never injected into inline filter instances.
1 parent 7c83f85 commit 2143660

10 files changed

Lines changed: 163 additions & 869 deletions

File tree

filters.md

Lines changed: 0 additions & 869 deletions
This file was deleted.

src/Doctrine/Common/ParameterExtensionTrait.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Doctrine\Persistence\ManagerRegistry;
2121
use Psr\Container\ContainerInterface;
2222
use Psr\Log\LoggerInterface;
23+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2324

2425
trait ParameterExtensionTrait
2526
{
@@ -28,6 +29,7 @@ trait ParameterExtensionTrait
2829
protected ContainerInterface $filterLocator;
2930
protected ?ManagerRegistry $managerRegistry = null;
3031
protected ?LoggerInterface $logger = null;
32+
protected ?NameConverterInterface $nameConverter = null;
3133

3234
/**
3335
* @param object $filter the filter instance to configure
@@ -43,6 +45,10 @@ private function configureFilter(object $filter, Parameter $parameter): void
4345
$filter->setLogger($this->logger);
4446
}
4547

48+
if ($this->nameConverter && ($filter instanceof \ApiPlatform\Doctrine\Orm\Filter\AbstractFilter || $filter instanceof \ApiPlatform\Doctrine\Odm\Filter\AbstractFilter) && null === $filter->getNameConverter()) {
49+
$filter->setNameConverter($this->nameConverter);
50+
}
51+
4652
if ($filter instanceof PropertyAwareFilterInterface) {
4753
$properties = [];
4854
// Check if the filter has getProperties method (e.g., if it's an AbstractFilter)

src/Doctrine/Odm/Extension/ParameterExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Doctrine\ODM\MongoDB\Aggregation\Builder;
2323
use Psr\Container\ContainerInterface;
2424
use Psr\Log\LoggerInterface;
25+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2526

2627
/**
2728
* Reads operation parameters and execute its filter.
@@ -36,10 +37,12 @@ public function __construct(
3637
ContainerInterface $filterLocator,
3738
?ManagerRegistry $managerRegistry = null,
3839
?LoggerInterface $logger = null,
40+
?NameConverterInterface $nameConverter = null,
3941
) {
4042
$this->filterLocator = $filterLocator;
4143
$this->managerRegistry = $managerRegistry;
4244
$this->logger = $logger;
45+
$this->nameConverter = $nameConverter;
4346
}
4447

4548
/**

src/Doctrine/Odm/Filter/AbstractFilter.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ public function setManagerRegistry(ManagerRegistry $managerRegistry): void
8585
$this->managerRegistry = $managerRegistry;
8686
}
8787

88+
public function getNameConverter(): ?NameConverterInterface
89+
{
90+
return $this->nameConverter;
91+
}
92+
93+
public function setNameConverter(NameConverterInterface $nameConverter): void
94+
{
95+
$this->nameConverter = $nameConverter;
96+
}
97+
8898
/**
8999
* @return array<string, mixed>|null
90100
*/

src/Doctrine/Orm/Extension/ParameterExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Doctrine\Persistence\ManagerRegistry;
2323
use Psr\Container\ContainerInterface;
2424
use Psr\Log\LoggerInterface;
25+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2526

2627
/**
2728
* Reads operation parameters and execute its filter.
@@ -36,10 +37,12 @@ public function __construct(
3637
ContainerInterface $filterLocator,
3738
?ManagerRegistry $managerRegistry = null,
3839
?LoggerInterface $logger = null,
40+
?NameConverterInterface $nameConverter = null,
3941
) {
4042
$this->filterLocator = $filterLocator;
4143
$this->managerRegistry = $managerRegistry;
4244
$this->logger = $logger;
45+
$this->nameConverter = $nameConverter;
4346
}
4447

4548
/**

src/Doctrine/Orm/Filter/AbstractFilter.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ public function setManagerRegistry(ManagerRegistry $managerRegistry): void
7878
$this->managerRegistry = $managerRegistry;
7979
}
8080

81+
public function getNameConverter(): ?NameConverterInterface
82+
{
83+
return $this->nameConverter;
84+
}
85+
86+
public function setNameConverter(NameConverterInterface $nameConverter): void
87+
{
88+
$this->nameConverter = $nameConverter;
89+
}
90+
8191
public function getProperties(): ?array
8292
{
8393
return $this->properties;

src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
service('api_platform.filter_locator'),
182182
service('doctrine_mongodb')->nullOnInvalid(),
183183
service('logger')->nullOnInvalid(),
184+
service('api_platform.name_converter')->nullOnInvalid(),
184185
])
185186
->tag('api_platform.doctrine_mongodb.odm.aggregation_extension.collection', ['priority' => 32])
186187
->tag('api_platform.doctrine_mongodb.odm.aggregation_extension.item');

src/Symfony/Bundle/Resources/config/doctrine_orm.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
service('api_platform.filter_locator'),
200200
service('doctrine')->nullOnInvalid(),
201201
service('logger')->nullOnInvalid(),
202+
service('api_platform.name_converter')->nullOnInvalid(),
202203
])
203204
->tag('api_platform.doctrine.orm.query_extension.collection', ['priority' => -16])
204205
->tag('api_platform.doctrine.orm.query_extension.item', ['priority' => -9]);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use ApiPlatform\Metadata\QueryParameter;
20+
use Doctrine\ORM\Mapping as ORM;
21+
22+
/**
23+
* Tests that legacy AbstractFilter subclasses work with QueryParameter
24+
* when a nameConverter is configured (issue #7866).
25+
*/
26+
#[ApiResource(
27+
operations: [
28+
new GetCollection(
29+
parameters: [
30+
'nameConverted' => new QueryParameter(
31+
filter: new DateFilter(),
32+
properties: ['nameConverted'],
33+
),
34+
],
35+
),
36+
],
37+
)]
38+
#[ORM\Entity]
39+
class ConvertedDateParameter
40+
{
41+
#[ORM\Column(type: 'integer', nullable: true)]
42+
#[ORM\Id]
43+
#[ORM\GeneratedValue(strategy: 'AUTO')]
44+
private ?int $id = null;
45+
46+
#[ORM\Column(type: 'date')]
47+
public \DateTime $nameConverted;
48+
49+
public function getId(): ?int
50+
{
51+
return $this->id;
52+
}
53+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Functional\Parameters;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConvertedDateParameter;
18+
use ApiPlatform\Tests\RecreateSchemaTrait;
19+
use ApiPlatform\Tests\SetupClassResourcesTrait;
20+
21+
/**
22+
* Tests that legacy AbstractFilter subclasses (DateFilter, etc.) work correctly
23+
* with QueryParameter when a nameConverter is configured.
24+
*
25+
* @see https://github.com/api-platform/core/issues/7866
26+
*/
27+
final class NameConverterFilterTest extends ApiTestCase
28+
{
29+
use RecreateSchemaTrait;
30+
use SetupClassResourcesTrait;
31+
32+
protected static ?bool $alwaysBootKernel = false;
33+
34+
/**
35+
* @return class-string[]
36+
*/
37+
public static function getResources(): array
38+
{
39+
return [ConvertedDateParameter::class];
40+
}
41+
42+
protected function setUp(): void
43+
{
44+
$this->recreateSchema([ConvertedDateParameter::class]);
45+
$this->loadFixtures();
46+
}
47+
48+
public function testDateFilterWithNameConverter(): void
49+
{
50+
$response = self::createClient()->request('GET', '/converted_date_parameters?nameConverted[after]=2025-01-15');
51+
$this->assertResponseIsSuccessful();
52+
$members = $response->toArray()['hydra:member'];
53+
$this->assertCount(2, $members);
54+
}
55+
56+
public function testDateFilterBeforeWithNameConverter(): void
57+
{
58+
$response = self::createClient()->request('GET', '/converted_date_parameters?nameConverted[before]=2025-01-15');
59+
$this->assertResponseIsSuccessful();
60+
$members = $response->toArray()['hydra:member'];
61+
$this->assertCount(1, $members);
62+
}
63+
64+
private function loadFixtures(): void
65+
{
66+
$manager = $this->getManager();
67+
68+
foreach (['2025-01-01', '2025-02-01', '2025-03-01'] as $date) {
69+
$entity = new ConvertedDateParameter();
70+
$entity->nameConverted = new \DateTime($date);
71+
$manager->persist($entity);
72+
}
73+
74+
$manager->flush();
75+
}
76+
}

0 commit comments

Comments
 (0)