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 Nov 4, 2024
1 parent 1ac0c3d commit 1130a1a
Show file tree
Hide file tree
Showing 17 changed files with 484 additions and 53 deletions.
2 changes: 1 addition & 1 deletion src/Doctrine/Common/Filter/BooleanFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function getDescription(string $resourceClass): array
return $description;
}

abstract protected function getProperties(): ?array;
abstract public function getProperties(): ?array;

abstract protected function getLogger(): LoggerInterface;

Expand Down
48 changes: 37 additions & 11 deletions src/Doctrine/Odm/Extension/ParameterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
namespace ApiPlatform\Doctrine\Odm\Extension;

use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait;
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
use ApiPlatform\Doctrine\Odm\Filter\ManagerRegistryAwareInterface;
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,14 +34,20 @@ 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,
) {
}

/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass = null, ?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 @@ -45,14 +56,29 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
continue;
}

$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
if ($filter instanceof FilterInterface) {
$filterContext = ['filters' => $values, 'parameter' => $parameter];
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
// update by reference
if (isset($filterContext['mongodb_odm_sort_fields'])) {
$context['mongodb_odm_sort_fields'] = $filterContext['mongodb_odm_sort_fields'];
}
$filter = match (true) {
$filterId instanceof AbstractFilter => $filterId,
\is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId),
default => null,
};

if (!($filter instanceof FilterInterface)) {
return;
}

if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
$filter->setManagerRegistry($this->managerRegistry);
}

if (!$filter->getProperties()) {
$filter->setProperties([$parameter->getProperty() ?? $parameter->getKey() => []]);
}

$filterContext = ['filters' => $values, 'parameter' => $parameter];
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
// update by reference
if (isset($filterContext['mongodb_odm_sort_fields'])) {
$context['mongodb_odm_sort_fields'] = $filterContext['mongodb_odm_sort_fields'];
}
}
}
Expand Down
35 changes: 28 additions & 7 deletions src/Doctrine/Odm/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
use ApiPlatform\Doctrine\Odm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait;
use ApiPlatform\Metadata\Operation;
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
Expand All @@ -30,14 +30,18 @@
*
* @author Alan Poulain <[email protected]>
*/
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryAwareInterface
{
use MongoDbOdmPropertyHelperTrait;
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,18 +60,35 @@ 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
public function hasManagerRegistry(): bool
{
return $this->managerRegistry instanceof ManagerRegistry;
}

public function getManagerRegistry(): ManagerRegistry
{
if (!$this->hasManagerRegistry()) {
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
}

return $this->managerRegistry;
}

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

/**
* @return array<string, mixed>|null
*/
public function getProperties(): ?array
{
return $this->properties;
}

/**
* @param string[] $properties
* @param array<string, mixed> $properties
*/
public function setProperties(array $properties): void
{
Expand Down
12 changes: 11 additions & 1 deletion src/Doctrine/Odm/Filter/BooleanFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
namespace ApiPlatform\Doctrine\Odm\Filter;

use ApiPlatform\Doctrine\Common\Filter\BooleanFilterTrait;
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;

Expand Down Expand Up @@ -104,7 +106,7 @@
* @author Teoh Han Hui <[email protected]>
* @author Alan Poulain <[email protected]>
*/
final class BooleanFilter extends AbstractFilter
final class BooleanFilter extends AbstractFilter implements JsonSchemaFilterInterface
{
use BooleanFilterTrait;

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

$aggregationBuilder->match()->field($matchField)->equals($value);
}

/**
* @return array<string, string>
*/
public function getSchema(Parameter $parameter): array
{
return $parameter->getSchema() ?? ['type' => 'boolean'];
}
}
25 changes: 25 additions & 0 deletions src/Doctrine/Odm/Filter/ManagerRegistryAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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\Doctrine\Odm\Filter;

use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;

interface ManagerRegistryAwareInterface
{
public function hasManagerRegistry(): bool;

public function getManagerRegistry(): ManagerRegistry;

public function setManagerRegistry(?ManagerRegistry $managerRegistry): void;
}
8 changes: 4 additions & 4 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 @@ -39,9 +39,9 @@ abstract protected function splitPropertyParts(string $property, string $resourc
*/
protected function getClassMetadata(string $resourceClass): ClassMetadata
{
$manager = $this
->getManagerRegistry()
->getManagerForClass($resourceClass);
/** @var ?ManagerRegistry $managerRegistry */
$managerRegistry = $this->getManagerRegistry();
$manager = $managerRegistry?->getManagerForClass($resourceClass);

if ($manager) {
return $manager->getClassMetadata($resourceClass);
Expand Down
39 changes: 33 additions & 6 deletions src/Doctrine/Orm/Extension/ParameterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@
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\Filter\ManagerRegistryAwareInterface;
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 +35,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,10 +59,27 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter
continue;
}

$filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null;
if ($filter instanceof FilterInterface) {
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context);
$filter = match (true) {
$filterId instanceof AbstractFilter => $filterId,
\is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId),
default => null,
};

if (!($filter instanceof FilterInterface)) {
return;
}

if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
$filter->setManagerRegistry($this->managerRegistry);
}

if (!$filter->getProperties()) {
$filter->setProperties([$parameter->getProperty() ?? $parameter->getKey() => []]);
}

$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation,
['filters' => $values, 'parameter' => $parameter] + $context
);
}
}

Expand Down
36 changes: 27 additions & 9 deletions src/Doctrine/Orm/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@
use Psr\Log\NullLogger;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;

abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryAwareInterface
{
use OrmPropertyHelperTrait;
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,29 +57,43 @@ 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
public function hasManagerRegistry(): bool
{
return $this->managerRegistry instanceof ManagerRegistry;
}

public function getManagerRegistry(): ManagerRegistry
{
if (!$this->hasManagerRegistry()) {
throw new \RuntimeException('ManagerRegistry must be initialized before accessing it.');
}

return $this->managerRegistry;
}

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

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

/**
* @param string[] $properties
* @param array<string, mixed> $properties
*/
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
Loading

0 comments on commit 1130a1a

Please sign in to comment.