Skip to content

Commit

Permalink
feat(doctrine): boolean filter like laravel filters
Browse files Browse the repository at this point in the history
  • Loading branch information
vinceAmstoutz committed Oct 26, 2024
1 parent 1ac0c3d commit cc689c3
Show file tree
Hide file tree
Showing 12 changed files with 443 additions and 24 deletions.
35 changes: 32 additions & 3 deletions src/Doctrine/Odm/Extension/ParameterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
namespace ApiPlatform\Doctrine\Odm\Extension;

use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ParameterNotFound;
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;

/**
* Reads operation parameters and execute its filter.
Expand All @@ -29,8 +33,10 @@ final class ParameterExtension implements AggregationCollectionExtensionInterfac
{
use ParameterValueExtractorTrait;

public function __construct(private readonly ContainerInterface $filterLocator)
{
public function __construct(
private readonly ContainerInterface $filterLocator,
private readonly ?ManagerRegistry $managerRegistry = null,
) {
}

private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass = null, ?Operation $operation = null, array &$context = []): void
Expand All @@ -45,7 +51,8 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
continue;
}

$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
$filter = $this->getFilter($filterId);

if ($filter instanceof FilterInterface) {
$filterContext = ['filters' => $values, 'parameter' => $parameter];
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
Expand All @@ -72,4 +79,26 @@ public function applyToItem(Builder $aggregationBuilder, string $resourceClass,
{
$this->applyFilter($aggregationBuilder, $resourceClass, $operation, $context);
}

/**
* @param $values array<string, string>
*
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function getFilter(AbstractFilter|string|null $filterId, array $values = []): ?FilterInterface
{
if ($filterId instanceof AbstractFilter) {
$filterId->setManagerRegistry($this->managerRegistry);
$filterId->setProperties($values);

return $filterId;
}

if (\is_string($filterId) && $this->filterLocator->has($filterId)) {
return $this->filterLocator->get($filterId);
}

return null;
}
}
15 changes: 12 additions & 3 deletions src/Doctrine/Odm/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInt
use PropertyHelperTrait;
protected LoggerInterface $logger;

public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null)
{
public function __construct(
protected ?ManagerRegistry $managerRegistry = null,
?LoggerInterface $logger = null,
protected ?array $properties = null,
protected ?NameConverterInterface $nameConverter = null,
) {
$this->logger = $logger ?? new NullLogger();
}

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

protected function getManagerRegistry(): ManagerRegistry
protected function getManagerRegistry(): ?ManagerRegistry
{
return $this->managerRegistry;
}

public function setManagerRegistry(?ManagerRegistry $managerRegistry): ?ManagerRegistry
{
return $this->managerRegistry = $managerRegistry;
}

protected function getProperties(): ?array
{
return $this->properties;
Expand Down
4 changes: 2 additions & 2 deletions src/Doctrine/Odm/PropertyHelperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*/
trait PropertyHelperTrait
{
abstract protected function getManagerRegistry(): ManagerRegistry;
abstract protected function getManagerRegistry(): ?ManagerRegistry;

/**
* Splits the given property into parts.
Expand All @@ -41,7 +41,7 @@ protected function getClassMetadata(string $resourceClass): ClassMetadata
{
$manager = $this
->getManagerRegistry()
->getManagerForClass($resourceClass);
?->getManagerForClass($resourceClass);

if ($manager) {
return $manager->getClassMetadata($resourceClass);
Expand Down
44 changes: 39 additions & 5 deletions src/Doctrine/Orm/Extension/ParameterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@
namespace ApiPlatform\Doctrine\Orm\Extension;

use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ParameterNotFound;
use Doctrine\ORM\QueryBuilder;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Bridge\Doctrine\ManagerRegistry;

/**
* Reads operation parameters and execute its filter.
Expand All @@ -30,17 +34,22 @@ final class ParameterExtension implements QueryCollectionExtensionInterface, Que
{
use ParameterValueExtractorTrait;

public function __construct(private readonly ContainerInterface $filterLocator)
{
public function __construct(
private readonly ContainerInterface $filterLocator,
private readonly ?ManagerRegistry $managerRegistry = null,
) {
}

/**
* @param array<string, mixed> $context
*
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
foreach ($operation?->getParameters() ?? [] as $parameter) {
if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
if (null === ($v = $parameter->getValue()) || $v instanceof ParameterNotFound) {
continue;
}

Expand All @@ -49,9 +58,12 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter
continue;
}

$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
$filter = $this->getFilter($filterId, $values);

if ($filter instanceof FilterInterface) {
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context);
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,
['filters' => $values, 'parameter' => $parameter] + $context
);
}
}
}
Expand All @@ -71,4 +83,26 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
{
$this->applyFilter($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
}

/**
* @param $values array<string, string>
*
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function getFilter(AbstractFilter|string|null $filterId, array $values = []): ?FilterInterface
{
if ($filterId instanceof AbstractFilter) {
$filterId->setManagerRegistry($this->managerRegistry);
$filterId->setProperties($values);

return $filterId;
}

if (\is_string($filterId) && $this->filterLocator->has($filterId)) {
return $this->filterLocator->get($filterId);
}

return null;
}
}
23 changes: 16 additions & 7 deletions src/Doctrine/Orm/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInt
use PropertyHelperTrait;
protected LoggerInterface $logger;

public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null)
{
public function __construct(
protected ?ManagerRegistry $managerRegistry = null,
?LoggerInterface $logger = null,
protected ?array $properties = null,
protected ?NameConverterInterface $nameConverter = null,
) {
$this->logger = $logger ?? new NullLogger();
}

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

protected function getManagerRegistry(): ManagerRegistry
protected function getManagerRegistry(): ?ManagerRegistry
{
return $this->managerRegistry;
}

protected function getProperties(): ?array
public function setManagerRegistry(?ManagerRegistry $managerRegistry): ?ManagerRegistry
{
return $this->properties;
return $this->managerRegistry = $managerRegistry;
}

protected function getLogger(): LoggerInterface
public function getProperties(): ?array
{
return $this->logger;
return $this->properties;
}

/**
Expand All @@ -76,6 +80,11 @@ public function setProperties(array $properties): void
$this->properties = $properties;
}

protected function getLogger(): LoggerInterface
{
return $this->logger;
}

/**
* Determines whether the given property is enabled.
*/
Expand Down
30 changes: 29 additions & 1 deletion src/Doctrine/Orm/Filter/BooleanFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@

use ApiPlatform\Doctrine\Common\Filter\BooleanFilterTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
Expand Down Expand Up @@ -106,7 +111,7 @@
* @author Amrouche Hamza <[email protected]>
* @author Teoh Han Hui <[email protected]>
*/
final class BooleanFilter extends AbstractFilter
final class BooleanFilter extends AbstractFilter implements OpenApiParameterFilterInterface, JsonSchemaFilterInterface
{
use BooleanFilterTrait;

Expand Down Expand Up @@ -145,4 +150,27 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
}

/**
* @return array<string, mixed>
*/
public function getSchema(Parameter $parameter): array
{
return ['type' => 'boolean'];
}

public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null
{
$in = $parameter instanceof QueryParameter ? 'query' : 'header';
$key = $parameter->getKey();

return [
new OpenApiParameter(
name: $key,
in: $in,
required: false,
schema: ['type' => 'boolean'],
),
];
}
}
4 changes: 2 additions & 2 deletions src/Doctrine/Orm/PropertyHelperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*/
trait PropertyHelperTrait
{
abstract protected function getManagerRegistry(): ManagerRegistry;
abstract protected function getManagerRegistry(): ?ManagerRegistry;

/**
* Splits the given property into parts.
Expand All @@ -43,7 +43,7 @@ protected function getClassMetadata(string $resourceClass): ClassMetadata
{
$manager = $this
->getManagerRegistry()
->getManagerForClass($resourceClass);
?->getManagerForClass($resourceClass);

if ($manager) {
return $manager->getClassMetadata($resourceClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@

<service id="api_platform.doctrine_mongodb.odm.extension.parameter_extension" class="ApiPlatform\Doctrine\Odm\Extension\ParameterExtension" public="false">
<argument type="service" id="api_platform.filter_locator" />

<argument type="service" id="doctrine_mongodb" on-invalid="null"/>
<tag name="api_platform.doctrine_mongodb.odm.aggregation_extension.collection" priority="32" />
<tag name="api_platform.doctrine_mongodb.odm.aggregation_extension.item"/>
</service>
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bundle/Resources/config/doctrine_orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@

<service id="api_platform.doctrine.orm.extension.parameter_extension" class="ApiPlatform\Doctrine\Orm\Extension\ParameterExtension" public="false">
<argument type="service" id="api_platform.filter_locator" />
<argument type="service" id="doctrine" on-invalid="null"/>
<tag name="api_platform.doctrine.orm.query_extension.collection" priority="-16" />
<tag name="api_platform.doctrine.orm.query_extension.item" priority="-9" />
</service>
Expand Down
60 changes: 60 additions & 0 deletions tests/Fixtures/TestBundle/Document/FilteredBooleanParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Doctrine\Odm\Filter\BooleanFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

#[ApiResource]
#[GetCollection(
parameters: [
'active' => new QueryParameter(
filter: new BooleanFilter(),
),
'enabled' => new QueryParameter(
filter: new BooleanFilter(),
property: 'active',
),
],
)]
#[ODM\Document]
class FilteredBooleanParameter
{
public function __construct(
#[ODM\Id(type: 'int', strategy: 'INCREMENT')]
public ?int $id = null,

#[ODM\Field(type: 'bool', nullable: true)]
public ?bool $active = null,
) {
}

public function getId(): ?int
{
return $this->id;
}

public function isActive(): bool
{
return $this->active;
}

public function setActive(?bool $active): void
{
$this->active = $active;
}
}
Loading

0 comments on commit cc689c3

Please sign in to comment.