Skip to content

Commit e3b707b

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

File tree

16 files changed

+506
-31
lines changed

16 files changed

+506
-31
lines changed

src/Doctrine/Common/Filter/BooleanFilterTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function getDescription(string $resourceClass): array
4242
$description = [];
4343

4444
$properties = $this->getProperties();
45-
if (null === $properties) {
45+
if (null === $properties && $this->hasManagerRegistry()) {
4646
$properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null);
4747
}
4848

src/Doctrine/Odm/Extension/ParameterExtension.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@
1414
namespace ApiPlatform\Doctrine\Odm\Extension;
1515

1616
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
17+
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter;
1718
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
19+
use ApiPlatform\Doctrine\Odm\Filter\ManagerRegistryConfigurableInterface;
1820
use ApiPlatform\Metadata\Operation;
1921
use ApiPlatform\State\ParameterNotFound;
22+
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
2023
use Doctrine\ODM\MongoDB\Aggregation\Builder;
24+
use Psr\Container\ContainerExceptionInterface;
2125
use Psr\Container\ContainerInterface;
26+
use Psr\Container\NotFoundExceptionInterface;
2227

2328
/**
2429
* Reads operation parameters and execute its filter.
@@ -29,14 +34,20 @@ final class ParameterExtension implements AggregationCollectionExtensionInterfac
2934
{
3035
use ParameterValueExtractorTrait;
3136

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

43+
/**
44+
* @throws ContainerExceptionInterface
45+
* @throws NotFoundExceptionInterface
46+
*/
3647
private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass = null, ?Operation $operation = null, array &$context = []): void
3748
{
3849
foreach ($operation->getParameters() ?? [] as $parameter) {
39-
if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
50+
if (null === ($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
4051
continue;
4152
}
4253

@@ -45,7 +56,19 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
4556
continue;
4657
}
4758

48-
$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
59+
$filter = null;
60+
61+
if ($filterId instanceof AbstractFilter) {
62+
$filterId->setProperties($values);
63+
if ($filterId instanceof ManagerRegistryConfigurableInterface) {
64+
$filterId->setManagerRegistry($this->managerRegistry);
65+
}
66+
67+
$filter = $filterId;
68+
} elseif (\is_string($filterId) && $this->filterLocator->has($filterId)) {
69+
$filter = $this->filterLocator->get($filterId);
70+
}
71+
4972
if ($filter instanceof FilterInterface) {
5073
$filterContext = ['filters' => $values, 'parameter' => $parameter];
5174
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);

src/Doctrine/Odm/Filter/AbstractFilter.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
1818
use ApiPlatform\Doctrine\Odm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait;
1919
use ApiPlatform\Metadata\Operation;
20+
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
2021
use Doctrine\ODM\MongoDB\Aggregation\Builder;
21-
use Doctrine\Persistence\ManagerRegistry;
2222
use Psr\Log\LoggerInterface;
2323
use Psr\Log\NullLogger;
2424
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
@@ -30,14 +30,18 @@
3030
*
3131
* @author Alan Poulain <[email protected]>
3232
*/
33-
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
33+
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryConfigurableInterface
3434
{
3535
use MongoDbOdmPropertyHelperTrait;
3636
use PropertyHelperTrait;
3737
protected LoggerInterface $logger;
3838

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

@@ -58,9 +62,18 @@ abstract protected function filterProperty(string $property, $value, Builder $ag
5862

5963
protected function getManagerRegistry(): ManagerRegistry
6064
{
65+
if (null === $this->managerRegistry) {
66+
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
67+
}
68+
6169
return $this->managerRegistry;
6270
}
6371

72+
public function setManagerRegistry(?ManagerRegistry $managerRegistry): ?ManagerRegistry
73+
{
74+
return $this->managerRegistry = $managerRegistry;
75+
}
76+
6477
protected function getProperties(): ?array
6578
{
6679
return $this->properties;

src/Doctrine/Odm/Filter/BooleanFilter.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
namespace ApiPlatform\Doctrine\Odm\Filter;
1515

1616
use ApiPlatform\Doctrine\Common\Filter\BooleanFilterTrait;
17+
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
18+
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
1719
use ApiPlatform\Metadata\Operation;
20+
use ApiPlatform\Metadata\Parameter;
21+
use ApiPlatform\Metadata\QueryParameter;
22+
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
1823
use Doctrine\ODM\MongoDB\Aggregation\Builder;
1924
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
2025

@@ -104,7 +109,7 @@
104109
* @author Teoh Han Hui <[email protected]>
105110
* @author Alan Poulain <[email protected]>
106111
*/
107-
final class BooleanFilter extends AbstractFilter
112+
final class BooleanFilter extends AbstractFilter implements OpenApiParameterFilterInterface, JsonSchemaFilterInterface
108113
{
109114
use BooleanFilterTrait;
110115

@@ -139,4 +144,27 @@ protected function filterProperty(string $property, $value, Builder $aggregation
139144

140145
$aggregationBuilder->match()->field($matchField)->equals($value);
141146
}
147+
148+
/**
149+
* @return array<string, string>
150+
*/
151+
public function getSchema(Parameter $parameter): array
152+
{
153+
return $parameter->getSchema() ?? ['type' => 'boolean'];
154+
}
155+
156+
public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null
157+
{
158+
$in = $parameter instanceof QueryParameter ? 'query' : 'header';
159+
$key = $parameter->getKey();
160+
161+
return [
162+
new OpenApiParameter(
163+
name: $key,
164+
in: $in,
165+
required: false,
166+
schema: ['type' => 'boolean'],
167+
),
168+
];
169+
}
142170
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Odm\Filter;
15+
16+
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
17+
18+
interface ManagerRegistryConfigurableInterface
19+
{
20+
public function setManagerRegistry(?ManagerRegistry $managerRegistry): ?ManagerRegistry;
21+
}

src/Doctrine/Odm/PropertyHelperTrait.php

Lines changed: 4 additions & 4 deletions
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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@
1414
namespace ApiPlatform\Doctrine\Orm\Extension;
1515

1616
use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
17+
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
1718
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
19+
use ApiPlatform\Doctrine\Orm\Filter\ManagerRegistryConfigurableInterface;
1820
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
1921
use ApiPlatform\Metadata\Operation;
2022
use ApiPlatform\State\ParameterNotFound;
2123
use Doctrine\ORM\QueryBuilder;
24+
use Psr\Container\ContainerExceptionInterface;
2225
use Psr\Container\ContainerInterface;
26+
use Psr\Container\NotFoundExceptionInterface;
27+
use Symfony\Bridge\Doctrine\ManagerRegistry;
2328

2429
/**
2530
* Reads operation parameters and execute its filter.
@@ -30,17 +35,22 @@ final class ParameterExtension implements QueryCollectionExtensionInterface, Que
3035
{
3136
use ParameterValueExtractorTrait;
3237

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

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

@@ -49,10 +59,28 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter
4959
continue;
5060
}
5161

52-
$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
53-
if ($filter instanceof FilterInterface) {
54-
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context);
62+
$filter = match (true) {
63+
$filterId instanceof AbstractFilter => $filterId,
64+
\is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId),
65+
default => null,
66+
};
67+
68+
if (!($filter instanceof FilterInterface)) {
69+
return;
70+
}
71+
72+
if (!$filter->hasManagerRegistry() && $filter instanceof ManagerRegistryConfigurableInterface) {
73+
$filter->setManagerRegistry($this->managerRegistry);
5574
}
75+
76+
if ([] === $filter->getProperties() || null === $filter->getProperties()) {
77+
$key = $parameter->getProperty() ?? $parameter->getKey();
78+
$filter->setProperties([$key => []]);
79+
}
80+
81+
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,
82+
['filters' => $values, 'parameter' => $parameter] + $context
83+
);
5684
}
5785
}
5886

src/Doctrine/Orm/Filter/AbstractFilter.php

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@
2424
use Psr\Log\NullLogger;
2525
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2626

27-
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
27+
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryConfigurableInterface
2828
{
2929
use OrmPropertyHelperTrait;
3030
use PropertyHelperTrait;
3131
protected LoggerInterface $logger;
3232

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

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

60+
public function hasManagerRegistry(): bool
61+
{
62+
return $this->managerRegistry instanceof ManagerRegistry;
63+
}
64+
5665
protected function getManagerRegistry(): ManagerRegistry
5766
{
67+
if (!$this->hasManagerRegistry()) {
68+
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
69+
}
70+
5871
return $this->managerRegistry;
5972
}
6073

61-
protected function getProperties(): ?array
74+
public function setManagerRegistry(?ManagerRegistry $managerRegistry): ?ManagerRegistry
6275
{
63-
return $this->properties;
76+
return $this->managerRegistry = $managerRegistry;
6477
}
6578

66-
protected function getLogger(): LoggerInterface
79+
public function getProperties(): ?array
6780
{
68-
return $this->logger;
81+
return $this->properties;
6982
}
7083

7184
/**
72-
* @param string[] $properties
85+
* @param array<string, mixed> $properties
7386
*/
7487
public function setProperties(array $properties): void
7588
{
7689
$this->properties = $properties;
7790
}
7891

92+
protected function getLogger(): LoggerInterface
93+
{
94+
return $this->logger;
95+
}
96+
7997
/**
8098
* Determines whether the given property is enabled.
8199
*/

0 commit comments

Comments
 (0)