Skip to content

feat(doctrine): new iri search filters #7121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Doctrine/Common/Filter/OpenApiFilterTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?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\Common\Filter;

use ApiPlatform\Metadata\Parameter;
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;

/**
* @author Vincent Amstoutz <[email protected]>
*/
trait OpenApiFilterTrait
{
public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null
{
return new OpenApiParameter(name: $parameter->getKey().'[]', in: 'query', style: 'deepObject', explode: true);
}

public function getDescription(string $resourceClass): array
{
return [];
}
}
44 changes: 44 additions & 0 deletions src/Doctrine/Odm/Filter/ExactFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?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 ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ODM\MongoDB\Aggregation\Builder;

/**
* @author Vincent Amstoutz <[email protected]>
*/
final class ExactFilter implements FilterInterface, OpenApiParameterFilterInterface
{
use OpenApiFilterTrait;

public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void

Check warning on line 28 in src/Doctrine/Odm/Filter/ExactFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/ExactFilter.php#L28

Added line #L28 was not covered by tests
{
if (!$parameter = $context['parameter'] ?? null) {
return;

Check warning on line 31 in src/Doctrine/Odm/Filter/ExactFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/ExactFilter.php#L30-L31

Added lines #L30 - L31 were not covered by tests
}

$values = (array) $parameter->getValue();

Check warning on line 34 in src/Doctrine/Odm/Filter/ExactFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/ExactFilter.php#L34

Added line #L34 was not covered by tests

// TODO: handle nested properties
$property = $parameter->getProperty();

Check warning on line 37 in src/Doctrine/Odm/Filter/ExactFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/ExactFilter.php#L37

Added line #L37 was not covered by tests

$aggregationBuilder
->match()
->field($property)
->in($values);

Check warning on line 42 in src/Doctrine/Odm/Filter/ExactFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/ExactFilter.php#L39-L42

Added lines #L39 - L42 were not covered by tests
}
}
3 changes: 3 additions & 0 deletions src/Doctrine/Odm/Filter/FilterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use ApiPlatform\Metadata\FilterInterface as BaseFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use Doctrine\ODM\MongoDB\Aggregation\Builder;

/**
Expand All @@ -26,6 +27,8 @@ interface FilterInterface extends BaseFilterInterface
{
/**
* Applies the filter.
*
* @param array|array{filters?: array<string, mixed>|array, parameter?: Parameter, mongodb_odm_sort_fields?: array, ...} $context
*/
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void;
}
54 changes: 54 additions & 0 deletions src/Doctrine/Odm/Filter/IriFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?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 ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\ParameterProviderFilterInterface;
use ApiPlatform\State\ParameterProvider\IriConverterParameterProvider;
use Doctrine\ODM\MongoDB\Aggregation\Builder;

/**
* @author Vincent Amstoutz <[email protected]>
*/
final class IriFilter implements FilterInterface, OpenApiParameterFilterInterface, ParameterProviderFilterInterface
{
use OpenApiFilterTrait;

public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void

Check warning on line 30 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L30

Added line #L30 was not covered by tests
{
if (!$parameter = $context['parameter'] ?? null) {
return;

Check warning on line 33 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L32-L33

Added lines #L32 - L33 were not covered by tests
}

$value = $parameter->getValue();
if (!\is_array($value)) {
$value = [$value];

Check warning on line 38 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L36-L38

Added lines #L36 - L38 were not covered by tests
}

// TODO: handle nested properties
$property = $parameter->getProperty();

Check warning on line 42 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L42

Added line #L42 was not covered by tests

$aggregationBuilder
->match()
->field($property)
->in($value);

Check warning on line 47 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L44-L47

Added lines #L44 - L47 were not covered by tests
}

public static function getParameterProvider(): string

Check warning on line 50 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L50

Added line #L50 was not covered by tests
{
return IriConverterParameterProvider::class;

Check warning on line 52 in src/Doctrine/Odm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/IriFilter.php#L52

Added line #L52 was not covered by tests
}
}
43 changes: 43 additions & 0 deletions src/Doctrine/Odm/Filter/OrFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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 ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ODM\MongoDB\Aggregation\Builder;

/**
* @author Vincent Amstoutz <[email protected]>
*/
final class OrFilter implements FilterInterface, OpenApiParameterFilterInterface
{
use OpenApiFilterTrait;
/**
* @var array<FilterInterface>
*/
private readonly array $filters;

public function __construct(FilterInterface ...$filters)

Check warning on line 32 in src/Doctrine/Odm/Filter/OrFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/OrFilter.php#L32

Added line #L32 was not covered by tests
{
$this->filters = $filters;

Check warning on line 34 in src/Doctrine/Odm/Filter/OrFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/OrFilter.php#L34

Added line #L34 was not covered by tests
}

public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void

Check warning on line 37 in src/Doctrine/Odm/Filter/OrFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/OrFilter.php#L37

Added line #L37 was not covered by tests
{
foreach ($this->filters as $filter) {
$filter->apply($aggregationBuilder, $resourceClass, $operation, $context);

Check warning on line 40 in src/Doctrine/Odm/Filter/OrFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/OrFilter.php#L39-L40

Added lines #L39 - L40 were not covered by tests
}
}
}
49 changes: 49 additions & 0 deletions src/Doctrine/Odm/Filter/PartialSearchFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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 ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use MongoDB\BSON\Regex;

/**
* @author Vincent Amstoutz <[email protected]>
*/
final class PartialSearchFilter implements FilterInterface, OpenApiParameterFilterInterface
{
use OpenApiFilterTrait;

public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void

Check warning on line 29 in src/Doctrine/Odm/Filter/PartialSearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/PartialSearchFilter.php#L29

Added line #L29 was not covered by tests
{
if (!$parameter = $context['parameter'] ?? null) {
return;

Check warning on line 32 in src/Doctrine/Odm/Filter/PartialSearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/PartialSearchFilter.php#L31-L32

Added lines #L31 - L32 were not covered by tests
}

$value = $parameter->getValue();
if (!\is_string($value) || '' === $value) {
return;

Check warning on line 37 in src/Doctrine/Odm/Filter/PartialSearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/PartialSearchFilter.php#L35-L37

Added lines #L35 - L37 were not covered by tests
}

// TODO: handle nested properties
$property = $parameter->getProperty();
$escapedValue = preg_quote($value, '/');

Check warning on line 42 in src/Doctrine/Odm/Filter/PartialSearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/PartialSearchFilter.php#L41-L42

Added lines #L41 - L42 were not covered by tests

$aggregationBuilder
->match()
->field($property)
->equals(new Regex($escapedValue, 'i'));

Check warning on line 47 in src/Doctrine/Odm/Filter/PartialSearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Odm/Filter/PartialSearchFilter.php#L44-L47

Added lines #L44 - L47 were not covered by tests
}
}
48 changes: 48 additions & 0 deletions src/Doctrine/Orm/Filter/ExactFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?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\Orm\Filter;

use ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;

/**
* @author Vincent Amstoutz <[email protected]>
*/
final class ExactFilter implements FilterInterface, OpenApiParameterFilterInterface
{
use OpenApiFilterTrait;

public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
if (!$parameter = $context['parameter'] ?? null) {
return;

Check warning on line 32 in src/Doctrine/Orm/Filter/ExactFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Orm/Filter/ExactFilter.php#L32

Added line #L32 was not covered by tests
}

$value = $parameter->getValue();
if (!\is_array($value)) {
$value = [$value];
}

$property = $parameter->getProperty();
$alias = $queryBuilder->getRootAliases()[0];
$parameterName = $queryNameGenerator->generateParameterName($property);

$queryBuilder
->andWhere(\sprintf('%s.%s = :%s', $alias, $property, $parameterName))
->setParameter($parameterName, $value);
}
}
7 changes: 5 additions & 2 deletions src/Doctrine/Orm/Filter/ExistsFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,11 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
return;
}

foreach ($context['filters'][$this->existsParameterName] ?? [] as $property => $value) {
$this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
$properties = $context['filters'][$this->existsParameterName];
if ([] !== $properties) {
foreach ($properties as $property => $value) {
$this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/Doctrine/Orm/Filter/FilterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\FilterInterface as BaseFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use Doctrine\ORM\QueryBuilder;

/**
Expand All @@ -27,6 +28,8 @@ interface FilterInterface extends BaseFilterInterface
{
/**
* Applies the filter.
*
* @param array{filters?: array<string, mixed>|array, parameter?: Parameter, ...} $context
*/
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void;
}
56 changes: 56 additions & 0 deletions src/Doctrine/Orm/Filter/IriFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?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\Orm\Filter;

use ApiPlatform\Doctrine\Common\Filter\OpenApiFilterTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\ParameterProviderFilterInterface;
use ApiPlatform\State\ParameterProvider\IriConverterParameterProvider;
use Doctrine\ORM\QueryBuilder;

/**
* @author Vincent Amstoutz <[email protected]>
*/
final class IriFilter implements FilterInterface, OpenApiParameterFilterInterface, ParameterProviderFilterInterface
{
use OpenApiFilterTrait;

public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
if (!$parameter = $context['parameter'] ?? null) {
return;

Check warning on line 34 in src/Doctrine/Orm/Filter/IriFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Orm/Filter/IriFilter.php#L34

Added line #L34 was not covered by tests
}

$value = $parameter->getValue();
if (!\is_array($value)) {
$value = [$value];
}

$property = $parameter->getProperty();
$alias = $queryBuilder->getRootAliases()[0];
$parameterName = $queryNameGenerator->generateParameterName($property);

$queryBuilder
->join(\sprintf('%s.%s', $alias, $property), $parameterName)
->andWhere(\sprintf('%s IN(:%s)', $parameterName, $parameterName))
->setParameter($parameterName, $value);
}

public static function getParameterProvider(): string
{
return IriConverterParameterProvider::class;
}
}
Loading
Loading