Skip to content

Commit fd22d9c

Browse files
committed
feat(doctrine): boolean filter like laravel filters
1 parent b458b55 commit fd22d9c

19 files changed

+482
-48
lines changed

src/Doctrine/Common/Filter/BooleanFilterTrait.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function getDescription(string $resourceClass): array
6161
return $description;
6262
}
6363

64-
abstract protected function getProperties(): ?array;
64+
abstract public function getProperties(): ?array;
6565

6666
abstract protected function getLogger(): LoggerInterface;
6767

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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 Doctrine\Persistence\ManagerRegistry;
17+
18+
interface ManagerRegistryAwareInterface
19+
{
20+
public function hasManagerRegistry(): bool;
21+
22+
public function getManagerRegistry(): ManagerRegistry;
23+
24+
public function setManagerRegistry(?ManagerRegistry $managerRegistry): void;
25+
}

src/Doctrine/Odm/Extension/ParameterExtension.php

+38-11
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@
1313

1414
namespace ApiPlatform\Doctrine\Odm\Extension;
1515

16+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
1617
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
18+
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter;
1719
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
20+
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
1821
use ApiPlatform\Metadata\Operation;
1922
use ApiPlatform\State\ParameterNotFound;
23+
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
2024
use Doctrine\ODM\MongoDB\Aggregation\Builder;
25+
use Psr\Container\ContainerExceptionInterface;
2126
use Psr\Container\ContainerInterface;
27+
use Psr\Container\NotFoundExceptionInterface;
2228

2329
/**
2430
* Reads operation parameters and execute its filter.
@@ -29,14 +35,20 @@ final class ParameterExtension implements AggregationCollectionExtensionInterfac
2935
{
3036
use ParameterValueExtractorTrait;
3137

32-
public function __construct(private readonly ContainerInterface $filterLocator)
33-
{
38+
public function __construct(
39+
private readonly ContainerInterface $filterLocator,
40+
private readonly ?ManagerRegistry $managerRegistry = null,
41+
) {
3442
}
3543

44+
/**
45+
* @throws ContainerExceptionInterface
46+
* @throws NotFoundExceptionInterface
47+
*/
3648
private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass = null, ?Operation $operation = null, array &$context = []): void
3749
{
3850
foreach ($operation->getParameters() ?? [] as $parameter) {
39-
if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
51+
if (null === ($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
4052
continue;
4153
}
4254

@@ -45,14 +57,29 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
4557
continue;
4658
}
4759

48-
$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
49-
if ($filter instanceof FilterInterface) {
50-
$filterContext = ['filters' => $values, 'parameter' => $parameter];
51-
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
52-
// update by reference
53-
if (isset($filterContext['mongodb_odm_sort_fields'])) {
54-
$context['mongodb_odm_sort_fields'] = $filterContext['mongodb_odm_sort_fields'];
55-
}
60+
$filter = match (true) {
61+
$filterId instanceof FilterInterface => $filterId,
62+
\is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId),
63+
default => null,
64+
};
65+
66+
if (!($filter instanceof FilterInterface)) {
67+
throw new InvalidArgumentException(\sprintf('Could not find filter "%s" for parameter "%s" in operation "%s" for resource "%s".', $filterId, $parameter->getKey(), $operation?->getShortName(), $resourceClass));
68+
}
69+
70+
if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
71+
$filter->setManagerRegistry($this->managerRegistry);
72+
}
73+
74+
if ($filter instanceof AbstractFilter && !$filter->getProperties()) {
75+
$filter->setProperties([$parameter->getProperty() ?? $parameter->getKey() => []]);
76+
}
77+
78+
$filterContext = ['filters' => $values, 'parameter' => $parameter];
79+
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
80+
// update by reference
81+
if (isset($filterContext['mongodb_odm_sort_fields'])) {
82+
$context['mongodb_odm_sort_fields'] = $filterContext['mongodb_odm_sort_fields'];
5683
}
5784
}
5885
}

src/Doctrine/Odm/Filter/AbstractFilter.php

+28-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Odm\Filter;
1515

16+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
1617
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
1718
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
1819
use ApiPlatform\Doctrine\Odm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait;
@@ -30,14 +31,18 @@
3031
*
3132
* @author Alan Poulain <[email protected]>
3233
*/
33-
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
34+
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryAwareInterface
3435
{
3536
use MongoDbOdmPropertyHelperTrait;
3637
use PropertyHelperTrait;
3738
protected LoggerInterface $logger;
3839

39-
public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null)
40-
{
40+
public function __construct(
41+
protected ?ManagerRegistry $managerRegistry = null,
42+
?LoggerInterface $logger = null,
43+
protected ?array $properties = null,
44+
protected ?NameConverterInterface $nameConverter = null,
45+
) {
4146
$this->logger = $logger ?? new NullLogger();
4247
}
4348

@@ -56,18 +61,35 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, ?Opera
5661
*/
5762
abstract protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void;
5863

59-
protected function getManagerRegistry(): ManagerRegistry
64+
public function hasManagerRegistry(): bool
65+
{
66+
return $this->managerRegistry instanceof ManagerRegistry;
67+
}
68+
69+
public function getManagerRegistry(): ManagerRegistry
6070
{
71+
if (!$this->hasManagerRegistry()) {
72+
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
73+
}
74+
6175
return $this->managerRegistry;
6276
}
6377

64-
protected function getProperties(): ?array
78+
public function setManagerRegistry(?ManagerRegistry $managerRegistry): void
79+
{
80+
$this->managerRegistry = $managerRegistry;
81+
}
82+
83+
/**
84+
* @return array<string, mixed>|null
85+
*/
86+
public function getProperties(): ?array
6587
{
6688
return $this->properties;
6789
}
6890

6991
/**
70-
* @param string[] $properties
92+
* @param array<string, mixed> $properties
7193
*/
7294
public function setProperties(array $properties): void
7395
{

src/Doctrine/Odm/Filter/BooleanFilter.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
namespace ApiPlatform\Doctrine\Odm\Filter;
1515

1616
use ApiPlatform\Doctrine\Common\Filter\BooleanFilterTrait;
17+
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
1718
use ApiPlatform\Metadata\Operation;
19+
use ApiPlatform\Metadata\Parameter;
1820
use Doctrine\ODM\MongoDB\Aggregation\Builder;
1921
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
2022

@@ -104,7 +106,7 @@
104106
* @author Teoh Han Hui <[email protected]>
105107
* @author Alan Poulain <[email protected]>
106108
*/
107-
final class BooleanFilter extends AbstractFilter
109+
final class BooleanFilter extends AbstractFilter implements JsonSchemaFilterInterface
108110
{
109111
use BooleanFilterTrait;
110112

@@ -139,4 +141,12 @@ protected function filterProperty(string $property, $value, Builder $aggregation
139141

140142
$aggregationBuilder->match()->field($matchField)->equals($value);
141143
}
144+
145+
/**
146+
* @return array<string, string>
147+
*/
148+
public function getSchema(Parameter $parameter): array
149+
{
150+
return $parameter->getSchema() ?? ['type' => 'boolean'];
151+
}
142152
}

src/Doctrine/Odm/PropertyHelperTrait.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*/
2828
trait PropertyHelperTrait
2929
{
30-
abstract protected function getManagerRegistry(): ManagerRegistry;
30+
abstract protected function getManagerRegistry(): ?ManagerRegistry;
3131

3232
/**
3333
* Splits the given property into parts.
@@ -39,9 +39,9 @@ abstract protected function splitPropertyParts(string $property, string $resourc
3939
*/
4040
protected function getClassMetadata(string $resourceClass): ClassMetadata
4141
{
42-
$manager = $this
43-
->getManagerRegistry()
44-
->getManagerForClass($resourceClass);
42+
/** @var ?ManagerRegistry $managerRegistry */
43+
$managerRegistry = $this->getManagerRegistry();
44+
$manager = $managerRegistry?->getManagerForClass($resourceClass);
4545

4646
if ($manager) {
4747
return $manager->getClassMetadata($resourceClass);

src/Doctrine/Orm/Extension/ParameterExtension.php

+31-6
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@
1313

1414
namespace ApiPlatform\Doctrine\Orm\Extension;
1515

16+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
1617
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
18+
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
1719
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
1820
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
1921
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
2022
use ApiPlatform\Metadata\Operation;
2123
use ApiPlatform\State\ParameterNotFound;
2224
use Doctrine\ORM\QueryBuilder;
25+
use Psr\Container\ContainerExceptionInterface;
2326
use Psr\Container\ContainerInterface;
27+
use Psr\Container\NotFoundExceptionInterface;
28+
use Symfony\Bridge\Doctrine\ManagerRegistry;
2429

2530
/**
2631
* Reads operation parameters and execute its filter.
@@ -31,17 +36,22 @@ final class ParameterExtension implements QueryCollectionExtensionInterface, Que
3136
{
3237
use ParameterValueExtractorTrait;
3338

34-
public function __construct(private readonly ContainerInterface $filterLocator)
35-
{
39+
public function __construct(
40+
private readonly ContainerInterface $filterLocator,
41+
private readonly ?ManagerRegistry $managerRegistry = null,
42+
) {
3643
}
3744

3845
/**
3946
* @param array<string, mixed> $context
47+
*
48+
* @throws ContainerExceptionInterface
49+
* @throws NotFoundExceptionInterface
4050
*/
4151
private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
4252
{
4353
foreach ($operation?->getParameters() ?? [] as $parameter) {
44-
if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
54+
if (null === ($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
4555
continue;
4656
}
4757

@@ -50,12 +60,27 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter
5060
continue;
5161
}
5262

53-
$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
54-
if (!$filter instanceof FilterInterface) {
63+
$filter = match (true) {
64+
$filterId instanceof FilterInterface => $filterId,
65+
\is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId),
66+
default => null,
67+
};
68+
69+
if (!($filter instanceof FilterInterface)) {
5570
throw new InvalidArgumentException(\sprintf('Could not find filter "%s" for parameter "%s" in operation "%s" for resource "%s".', $filterId, $parameter->getKey(), $operation?->getShortName(), $resourceClass));
5671
}
5772

58-
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context);
73+
if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
74+
$filter->setManagerRegistry($this->managerRegistry);
75+
}
76+
77+
if ($filter instanceof AbstractFilter && !$filter->getProperties()) {
78+
$filter->setProperties([$parameter->getProperty() ?? $parameter->getKey() => []]);
79+
}
80+
81+
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,
82+
['filters' => $values, 'parameter' => $parameter] + $context
83+
);
5984
}
6085
}
6186

src/Doctrine/Orm/Filter/AbstractFilter.php

+28-9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Doctrine\Orm\Filter;
1515

16+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
1617
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
1718
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
1819
use ApiPlatform\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait;
@@ -24,14 +25,18 @@
2425
use Psr\Log\NullLogger;
2526
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2627

27-
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
28+
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryAwareInterface
2829
{
2930
use OrmPropertyHelperTrait;
3031
use PropertyHelperTrait;
3132
protected LoggerInterface $logger;
3233

33-
public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null)
34-
{
34+
public function __construct(
35+
protected ?ManagerRegistry $managerRegistry = null,
36+
?LoggerInterface $logger = null,
37+
protected ?array $properties = null,
38+
protected ?NameConverterInterface $nameConverter = null,
39+
) {
3540
$this->logger = $logger ?? new NullLogger();
3641
}
3742

@@ -53,29 +58,43 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
5358
*/
5459
abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void;
5560

56-
protected function getManagerRegistry(): ManagerRegistry
61+
public function hasManagerRegistry(): bool
62+
{
63+
return $this->managerRegistry instanceof ManagerRegistry;
64+
}
65+
66+
public function getManagerRegistry(): ManagerRegistry
5767
{
68+
if (!$this->hasManagerRegistry()) {
69+
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
70+
}
71+
5872
return $this->managerRegistry;
5973
}
6074

61-
protected function getProperties(): ?array
75+
public function setManagerRegistry(?ManagerRegistry $managerRegistry): void
6276
{
63-
return $this->properties;
77+
$this->managerRegistry = $managerRegistry;
6478
}
6579

66-
protected function getLogger(): LoggerInterface
80+
public function getProperties(): ?array
6781
{
68-
return $this->logger;
82+
return $this->properties;
6983
}
7084

7185
/**
72-
* @param string[] $properties
86+
* @param array<string, mixed> $properties
7387
*/
7488
public function setProperties(array $properties): void
7589
{
7690
$this->properties = $properties;
7791
}
7892

93+
protected function getLogger(): LoggerInterface
94+
{
95+
return $this->logger;
96+
}
97+
7998
/**
8099
* Determines whether the given property is enabled.
81100
*/

0 commit comments

Comments
 (0)