Skip to content

Commit aabd7e6

Browse files
committed
refactor(doctrine): make DateFilter standalone, drop AbstractFilter
DateFilter (ORM + ODM) stops extending the deprecated AbstractFilter, the last of the three 5.0 survivors (after ExistsFilter). It stays a genuine survivor rather than a ComparisonFilter overlay: its date semantics — isDateField, \DateTime/\DateTimeImmutable coercion by column immutability, typed setParameter, and the INCLUDE_NULL_* null-management — are not expressible through ComparisonFilter. The decouple is structural: the AbstractFilter::apply() loop is reintroduced as a local apply() (driving filterProperty over $context['filters'], the universal value source for both legacy and QueryParameter declarations), and the inherited helpers (ctor, properties accessors, isPropertyEnabled, name-converter handling, logger) are inlined. ManagerRegistryAwareInterface is kept for BC injection and the deprecated getDescription(); the ORM filter reads class metadata from $queryBuilder->getEntityManager() so the active path no longer depends on the registry, while ODM keeps it (its aggregation Builder exposes no DocumentManager).
1 parent 5b59c6c commit aabd7e6

8 files changed

Lines changed: 275 additions & 100 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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\Doctrine\Common\Filter;
15+
16+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
17+
18+
/**
19+
* Holds an optional name converter and (de)normalizes property names through it.
20+
*
21+
* @author Antoine Bluchet <soyuka@gmail.com>
22+
*/
23+
trait NameConverterAwareTrait
24+
{
25+
private ?NameConverterInterface $nameConverter = null;
26+
27+
public function hasNameConverter(): bool
28+
{
29+
return $this->nameConverter instanceof NameConverterInterface;
30+
}
31+
32+
public function getNameConverter(): ?NameConverterInterface
33+
{
34+
return $this->nameConverter;
35+
}
36+
37+
public function setNameConverter(NameConverterInterface $nameConverter): void
38+
{
39+
$this->nameConverter = $nameConverter;
40+
}
41+
42+
protected function denormalizePropertyName(string|int $property): string
43+
{
44+
if (!$this->nameConverter instanceof NameConverterInterface) {
45+
return (string) $property;
46+
}
47+
48+
return implode('.', array_map($this->nameConverter->denormalize(...), explode('.', (string) $property)));
49+
}
50+
51+
protected function normalizePropertyName(string $property): string
52+
{
53+
if (!$this->nameConverter instanceof NameConverterInterface) {
54+
return $property;
55+
}
56+
57+
return implode('.', array_map($this->nameConverter->normalize(...), explode('.', $property)));
58+
}
59+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Doctrine\Common\Filter;
15+
16+
/**
17+
* @author Antoine Bluchet <soyuka@gmail.com>
18+
*/
19+
trait PropertyAwareFilterTrait
20+
{
21+
/**
22+
* @var array<string, mixed>|null
23+
*/
24+
private ?array $properties = null;
25+
26+
public function getProperties(): ?array
27+
{
28+
return $this->properties;
29+
}
30+
31+
/**
32+
* @param array<string, mixed> $properties
33+
*/
34+
public function setProperties(array $properties): void
35+
{
36+
$this->properties = $properties;
37+
}
38+
}

src/Doctrine/Odm/Filter/DateFilter.php

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515

1616
use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface;
1717
use ApiPlatform\Doctrine\Common\Filter\DateFilterTrait;
18+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
19+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareTrait;
20+
use ApiPlatform\Doctrine\Common\Filter\NameConverterAwareInterface;
21+
use ApiPlatform\Doctrine\Common\Filter\NameConverterAwareTrait;
22+
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
23+
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterTrait;
24+
use ApiPlatform\Doctrine\Odm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait;
1825
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
1926
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
2027
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
@@ -23,6 +30,10 @@
2330
use ApiPlatform\Metadata\QueryParameter;
2431
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
2532
use Doctrine\ODM\MongoDB\Aggregation\Builder;
33+
use Doctrine\Persistence\ManagerRegistry;
34+
use Psr\Log\LoggerInterface;
35+
use Psr\Log\NullLogger;
36+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2637

2738
/**
2839
* The date filter allows to filter a collection by date intervals.
@@ -120,20 +131,59 @@
120131
* @author Kévin Dunglas <dunglas@gmail.com>
121132
* @author Théo FIDRY <theo.fidry@gmail.com>
122133
* @author Alan Poulain <contact@alanpoulain.eu>
123-
*
124-
* @deprecated since API Platform 4.4: extending {@see AbstractFilter} is deprecated. In 5.0 this filter is rewritten as a standalone overlay over {@see ComparisonFilter} (translating the `[before]`/`[strictly_before]`/`[after]`/`[strictly_after]` syntax) — same class name, same URL syntax, drop-in. Declare it through a QueryParameter to migrate.
125134
*/
126-
final class DateFilter extends AbstractFilter implements DateFilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface
135+
final class DateFilter implements DateFilterInterface, FilterInterface, JsonSchemaFilterInterface, ManagerRegistryAwareInterface, NameConverterAwareInterface, OpenApiParameterFilterInterface, PropertyAwareFilterInterface
127136
{
128137
use DateFilterTrait;
138+
use ManagerRegistryAwareTrait;
139+
use MongoDbOdmPropertyHelperTrait;
140+
use NameConverterAwareTrait;
141+
use PropertyAwareFilterTrait;
129142

130143
public const DOCTRINE_DATE_TYPES = [
131144
'date' => true,
132145
'date_immutable' => true,
133146
];
134147

148+
private LoggerInterface $logger;
149+
150+
/**
151+
* @param array<string, mixed>|null $properties
152+
*/
153+
public function __construct(?ManagerRegistry $managerRegistry = null, ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null)
154+
{
155+
$this->managerRegistry = $managerRegistry;
156+
$this->logger = $logger ?? new NullLogger();
157+
$this->properties = $properties;
158+
$this->nameConverter = $nameConverter;
159+
}
160+
161+
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
162+
{
163+
foreach ($context['filters'] ?? [] as $property => $value) {
164+
$this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operation, $context);
165+
}
166+
}
167+
168+
protected function getLogger(): LoggerInterface
169+
{
170+
return $this->logger;
171+
}
172+
173+
protected function isPropertyEnabled(string $property, string $resourceClass): bool
174+
{
175+
if (null === $this->properties) {
176+
// to ensure sanity, nested properties must still be explicitly enabled
177+
return !$this->isPropertyNested($property, $resourceClass);
178+
}
179+
180+
return \array_key_exists($property, $this->properties);
181+
}
182+
135183
/**
136-
* {@inheritdoc}
184+
* @param array<string, mixed> $context
185+
*
186+
* @param-out array<string, mixed> $context
137187
*/
138188
protected function filterProperty(string $property, mixed $value, Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
139189
{

src/Doctrine/Odm/Filter/ExistsFilter.php

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
1919
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareTrait;
2020
use ApiPlatform\Doctrine\Common\Filter\NameConverterAwareInterface;
21+
use ApiPlatform\Doctrine\Common\Filter\NameConverterAwareTrait;
2122
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
23+
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterTrait;
2224
use ApiPlatform\Doctrine\Common\Filter\PropertyPlaceholderOpenApiParameterTrait;
2325
use ApiPlatform\Doctrine\Odm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait;
2426
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
@@ -122,45 +124,21 @@ final class ExistsFilter implements ExistsFilterInterface, FilterInterface, Json
122124
use ExistsFilterTrait;
123125
use ManagerRegistryAwareTrait;
124126
use MongoDbOdmPropertyHelperTrait;
127+
use NameConverterAwareTrait;
128+
use PropertyAwareFilterTrait;
125129
use PropertyPlaceholderOpenApiParameterTrait;
126130

127131
private LoggerInterface $logger;
128132

129133
/**
130134
* @param array<string, mixed>|null $properties
131135
*/
132-
public function __construct(?ManagerRegistry $managerRegistry = null, ?LoggerInterface $logger = null, private ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, private ?NameConverterInterface $nameConverter = null)
136+
public function __construct(?ManagerRegistry $managerRegistry = null, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null)
133137
{
134138
$this->managerRegistry = $managerRegistry;
135139
$this->logger = $logger ?? new NullLogger();
136140
$this->existsParameterName = $existsParameterName;
137-
}
138-
139-
public function getProperties(): ?array
140-
{
141-
return $this->properties;
142-
}
143-
144-
/**
145-
* @param array<string, mixed> $properties
146-
*/
147-
public function setProperties(array $properties): void
148-
{
149141
$this->properties = $properties;
150-
}
151-
152-
public function hasNameConverter(): bool
153-
{
154-
return $this->nameConverter instanceof NameConverterInterface;
155-
}
156-
157-
public function getNameConverter(): ?NameConverterInterface
158-
{
159-
return $this->nameConverter;
160-
}
161-
162-
public function setNameConverter(NameConverterInterface $nameConverter): void
163-
{
164142
$this->nameConverter = $nameConverter;
165143
}
166144

@@ -179,24 +157,6 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b
179157
return \array_key_exists($property, $this->properties);
180158
}
181159

182-
protected function denormalizePropertyName(string|int $property): string
183-
{
184-
if (!$this->nameConverter instanceof NameConverterInterface) {
185-
return (string) $property;
186-
}
187-
188-
return implode('.', array_map($this->nameConverter->denormalize(...), explode('.', (string) $property)));
189-
}
190-
191-
protected function normalizePropertyName(string $property): string
192-
{
193-
if (!$this->nameConverter instanceof NameConverterInterface) {
194-
return $property;
195-
}
196-
197-
return implode('.', array_map($this->nameConverter->normalize(...), explode('.', $property)));
198-
}
199-
200160
/**
201161
* {@inheritdoc}
202162
*/
@@ -215,7 +175,9 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, ?Opera
215175
}
216176

217177
/**
218-
* {@inheritdoc}
178+
* @param array<string, mixed> $context
179+
*
180+
* @param-out array<string, mixed> $context
219181
*/
220182
protected function filterProperty(string $property, mixed $value, Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
221183
{

0 commit comments

Comments
 (0)