Skip to content

Commit 13c15dd

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

File tree

18 files changed

+493
-53
lines changed

18 files changed

+493
-53
lines changed

src/Doctrine/Common/Filter/BooleanFilterTrait.php

Lines changed: 1 addition & 1 deletion
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

src/Doctrine/Odm/Extension/ParameterExtension.php

Lines changed: 37 additions & 11 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\ManagerRegistryAwareInterface;
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,14 +56,29 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
4556
continue;
4657
}
4758

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-
}
59+
$filter = match (true) {
60+
$filterId instanceof AbstractFilter => $filterId,
61+
\is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId),
62+
default => null,
63+
};
64+
65+
if (!($filter instanceof FilterInterface)) {
66+
return;
67+
}
68+
69+
if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
70+
$filter->setManagerRegistry($this->managerRegistry);
71+
}
72+
73+
if (!$filter->getProperties()) {
74+
$filter->setProperties([$parameter->getProperty() ?? $parameter->getKey() => []]);
75+
}
76+
77+
$filterContext = ['filters' => $values, 'parameter' => $parameter];
78+
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
79+
// update by reference
80+
if (isset($filterContext['mongodb_odm_sort_fields'])) {
81+
$context['mongodb_odm_sort_fields'] = $filterContext['mongodb_odm_sort_fields'];
5682
}
5783
}
5884
}

src/Doctrine/Odm/Filter/AbstractFilter.php

Lines changed: 28 additions & 7 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, ManagerRegistryAwareInterface
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

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

59-
protected function getManagerRegistry(): ManagerRegistry
63+
public function hasManagerRegistry(): bool
64+
{
65+
return $this->managerRegistry instanceof ManagerRegistry;
66+
}
67+
68+
public function getManagerRegistry(): ManagerRegistry
6069
{
70+
if (!$this->hasManagerRegistry()) {
71+
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
72+
}
73+
6174
return $this->managerRegistry;
6275
}
6376

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

6990
/**
70-
* @param string[] $properties
91+
* @param array<string, mixed> $properties
7192
*/
7293
public function setProperties(array $properties): void
7394
{

src/Doctrine/Odm/Filter/BooleanFilter.php

Lines changed: 11 additions & 1 deletion
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
}
Lines changed: 25 additions & 0 deletions
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\Odm\Filter;
15+
16+
use Doctrine\Bundle\MongoDBBundle\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/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: 33 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\ManagerRegistryAwareInterface;
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,27 @@ 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 instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
73+
$filter->setManagerRegistry($this->managerRegistry);
5574
}
75+
76+
if (!$filter->getProperties()) {
77+
$filter->setProperties([$parameter->getProperty() ?? $parameter->getKey() => []]);
78+
}
79+
80+
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,
81+
['filters' => $values, 'parameter' => $parameter] + $context
82+
);
5683
}
5784
}
5885

src/Doctrine/Orm/Filter/AbstractFilter.php

Lines changed: 27 additions & 9 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, ManagerRegistryAwareInterface
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

56-
protected function getManagerRegistry(): ManagerRegistry
60+
public function hasManagerRegistry(): bool
61+
{
62+
return $this->managerRegistry instanceof ManagerRegistry;
63+
}
64+
65+
public 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): void
6275
{
63-
return $this->properties;
76+
$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)