Skip to content
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

Feature/aliased property filters #6581

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a2a404a
feat(doctrine): add possibility to alias query parameters for filters…
Zzareb Sep 3, 2024
85ee84c
feat(doctrine): add doc to php doc of backed enum filter
Zzareb Sep 3, 2024
cbf3886
feat(doctrine): revert some copy pasted code
Zzareb Sep 4, 2024
63ec4ed
test: add verbose to see which test is skipped
Zzareb Sep 4, 2024
95327fa
test: add verbose to see which test is skipped
Zzareb Sep 4, 2024
d5ffd44
test: add verbose to see which test is skipped
Zzareb Sep 4, 2024
6ee099e
test: test with testdox
Zzareb Sep 4, 2024
6740680
test: test if passing
Zzareb Sep 4, 2024
e88450c
test: test if passing
Zzareb Sep 4, 2024
06325fa
test: test if passing
Zzareb Sep 4, 2024
a6915ec
fix: add missing exception class
Zzareb Sep 4, 2024
c40e080
fix: phpcs
Zzareb Sep 4, 2024
c551b15
fix: bad interface for exception
Zzareb Sep 4, 2024
24af1cd
fix: more phpcs
Zzareb Sep 4, 2024
4d8b9cc
fix: more phpcs
Zzareb Sep 4, 2024
06ab1ba
fix: no more drop schema because we just use querying
Zzareb Sep 4, 2024
ead64b6
fix: phpunit group as attribute
Zzareb Sep 4, 2024
d652593
fix: test no drop or create schema
Zzareb Sep 4, 2024
4eab1d3
fix: add missing setter
Zzareb Sep 4, 2024
7b51aae
fix: fixture creation error
Zzareb Sep 4, 2024
cadb32a
fix: fixture creation error
Zzareb Sep 4, 2024
17207b8
fix: fixture creation error
Zzareb Sep 4, 2024
3ec364f
fix: fixture creation error
Zzareb Sep 4, 2024
f51a53c
fix: fixture creation error
Zzareb Sep 4, 2024
a9f699a
fix: fixture creation error
Zzareb Sep 4, 2024
93e104a
fix: fixture creation error
Zzareb Sep 4, 2024
5f869d1
fix: fixture creation error
Zzareb Sep 4, 2024
10fbc89
fix: fixture creation error
Zzareb Sep 4, 2024
846cd81
fix: fixture creation error
Zzareb Sep 4, 2024
3ed3608
fix: fixture creation error
Zzareb Sep 4, 2024
1300133
fix: fixture creation error
Zzareb Sep 4, 2024
4a148c9
fix: fixture creation error and phpcs
Zzareb Sep 4, 2024
45a8e1f
fix: rewrite the tests to test only abstract filters. remove old tests.
Zzareb Sep 5, 2024
2543099
fix: phpcs
Zzareb Sep 5, 2024
0491cbd
fix: phpcs
Zzareb Sep 5, 2024
b753edc
fix: phpcs + extend TestCase
Zzareb Sep 5, 2024
2d114a7
fix: phpcs + try fix phpstan
Zzareb Sep 5, 2024
40c4a0b
fix: phpcs + try fix phpstan
Zzareb Sep 5, 2024
822e9fe
fix: phpstan
Zzareb Sep 5, 2024
0cdad91
fix: phpcs
Zzareb Sep 5, 2024
ef6b2bf
fix: test behat error
Zzareb Sep 5, 2024
0b08b81
fix: test behat error
Zzareb Sep 5, 2024
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ jobs:
run: |
mkdir -p build/logs/behat
vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --no-interaction ${{ matrix.coverage && '--profile=default-coverage' || '--profile=default' }}
grep -iF "exception" var/log/*.log
grep -iF "exception" build/logs/behat/*.*
grep -iF "exception" build/logs/behat/junit/*.*
- name: Merge code coverage reports
if: matrix.coverage
run: |
Expand Down
46 changes: 46 additions & 0 deletions src/Doctrine/Common/Filter/PropertyAliasesFilterTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Exception\UnMappedPropertyAliasException;

/**
* Interface for filtering the collection by given properties.
*
* @author Christophe Zarebski <christophe.zarebski@gmail.com>
*/
trait PropertyAliasesFilterTrait
{
protected array $propertyAliases = [];

protected function getPropertyAliases(): array
{
return $this->propertyAliases;
}

protected function isAlias(string $alias): bool
{
return !empty($this->getPropertyAliases()) && \in_array($alias, $this->getPropertyAliases(), true);
}

protected function getAliasForPropertyOrProperty(string $property): string
{
return $this->propertyAliases[$property] ?? $property;
}

protected function getPropertyFromAlias(string $alias): string
{
return array_flip($this->propertyAliases)[$alias] ?? throw new UnMappedPropertyAliasException($alias);
}
}
12 changes: 11 additions & 1 deletion src/Doctrine/Odm/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Doctrine\Odm\Filter;

use ApiPlatform\Doctrine\Common\Filter\PropertyAliasesFilterTrait;
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
use ApiPlatform\Doctrine\Odm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait;
Expand All @@ -33,12 +34,15 @@
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
{
use MongoDbOdmPropertyHelperTrait;
use PropertyAliasesFilterTrait;
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, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null, array $propertyAliases = [])
{
$this->logger = $logger ?? new NullLogger();
$this->propertyAliases = $propertyAliases;
}

/**
Expand Down Expand Up @@ -94,6 +98,10 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b

protected function denormalizePropertyName(string|int $property): string
{
if ($this->isAlias($property)) {
$property = $this->getPropertyFromAlias($property);
}

if (!$this->nameConverter instanceof NameConverterInterface) {
return (string) $property;
}
Expand All @@ -103,6 +111,8 @@ protected function denormalizePropertyName(string|int $property): string

protected function normalizePropertyName(string $property): string
{
$property = $this->getAliasForPropertyOrProperty($property);

if (!$this->nameConverter instanceof NameConverterInterface) {
return $property;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Doctrine/Odm/Filter/BooleanFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
* book.boolean_filter:
* parent: 'api_platform.doctrine.odm.boolean_filter'
* arguments: [ { published: ~ } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { published: ~ }
* # $propertyAliases: { published: 'issuedOn' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down
4 changes: 4 additions & 0 deletions src/Doctrine/Odm/Filter/DateFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
* book.date_filter:
* parent: 'api_platform.doctrine.odm.date_filter'
* arguments: [ { createdAt: ~ } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { createdAt: ~ }
* # $propertyAliases: { createdAt: 'created' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down
8 changes: 6 additions & 2 deletions src/Doctrine/Odm/Filter/ExistsFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
* book.exist_filter:
* parent: 'api_platform.doctrine.odm.exist_filter'
* arguments: [ { comment: ~ } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { comment: ~ }
* # $propertyAliases: { comment: 'opinion' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down Expand Up @@ -111,9 +115,9 @@ final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface
{
use ExistsFilterTrait;

public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null)
public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null, array $propertyAliases = [])
{
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
parent::__construct($managerRegistry, $logger, $properties, $nameConverter, $propertyAliases);

$this->existsParameterName = $existsParameterName;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Doctrine/Odm/Filter/NumericFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
* book.numeric_filter:
* parent: 'api_platform.doctrine.odm.numeric_filter'
* arguments: [ { price: ~ } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { price: ~ }
* # $propertyAliases: { price: 'priceInclVat' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down
13 changes: 11 additions & 2 deletions src/Doctrine/Odm/Filter/OrderFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
* book.order_filter:
* parent: 'api_platform.doctrine.odm.order_filter'
* arguments: [ $properties: { id: ~, title: ~ }, $orderParameterName: order ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { id: ASC, title: DESC }
* # $orderParameterName: order
* # $propertyAliases: { id: 'identifier', title: 'aliasedFieldName' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down Expand Up @@ -131,6 +136,10 @@
* book.order_filter:
* parent: 'api_platform.doctrine.odm.order_filter'
* arguments: [ { id: ASC, title: DESC } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { id: ASC, title: DESC }
* # $propertyAliases: { id: 'identifier', title: 'aliasedFieldName' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down Expand Up @@ -200,7 +209,7 @@ final class OrderFilter extends AbstractFilter implements OrderFilterInterface
{
use OrderFilterTrait;

public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null)
public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null, array $propertyAliases = [])
{
if (null !== $properties) {
$properties = array_map(static function ($propertyOptions) {
Expand All @@ -215,7 +224,7 @@ public function __construct(ManagerRegistry $managerRegistry, string $orderParam
}, $properties);
}

parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
parent::__construct($managerRegistry, $logger, $properties, $nameConverter, $propertyAliases);

$this->orderParameterName = $orderParameterName;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Doctrine/Odm/Filter/RangeFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
* book.range_filter:
* parent: 'api_platform.doctrine.odm.range_filter'
* arguments: [ { price: ~ } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { price: ~ }
* # $propertyAliases: { price: 'priceInclVat' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down
8 changes: 6 additions & 2 deletions src/Doctrine/Odm/Filter/SearchFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
* book.search_filter:
* parent: 'api_platform.doctrine.odm.search_filter'
* arguments: [ { isbn: 'exact', description: 'partial' } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { isbn: 'exact', description: 'partial' }
* # $propertyAliases: { isbn: 'identifier', description: 'aliasedDescription' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down Expand Up @@ -140,9 +144,9 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface

public const DOCTRINE_INTEGER_TYPE = [MongoDbType::INTEGER, MongoDbType::INT];

public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, ?IdentifiersExtractorInterface $identifiersExtractor, ?PropertyAccessorInterface $propertyAccessor = null, ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null)
public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, ?IdentifiersExtractorInterface $identifiersExtractor, ?PropertyAccessorInterface $propertyAccessor = null, ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null, array $propertyAliases = [])
{
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
parent::__construct($managerRegistry, $logger, $properties, $nameConverter, $propertyAliases);

$this->iriConverter = $iriConverter;
$this->identifiersExtractor = $identifiersExtractor;
Expand Down
107 changes: 107 additions & 0 deletions src/Doctrine/Odm/Tests/Filter/AliasedFieldFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Filter;

use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter as OdmAbstractFilter;
use ApiPlatform\Metadata\FilterInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

class AliasedFieldFilterTest extends TestCase
{
private function getFakeFilter(): object
{
return new class(managerRegistry: $this->createMock(ManagerRegistry::class), logger: $this->createMock(LoggerInterface::class), properties: ['name' => 'exact', 'some.relation.field' => 'partial'], propertyAliases: ['some.relation.field' => 'aliasedField']) extends OdmAbstractFilter {
protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
{
}

public function getDescription(string $resourceClass): array
{
return [];
}
};
}

#[Group('filter-test')]
public function testOdmFilterWithAliasedFieldsDenormalizes(): void
{
$fakeFilter = $this->getFakeFilter();

$denormalizePropertyNameClosure = function () {
$that = $this;

/* @var FilterInterface $that */
/* @phpstan-ignore method.notFound */
return $that->denormalizePropertyName('aliasedField');
};

$this->assertEquals('some.relation.field', $denormalizePropertyNameClosure->call($fakeFilter));

$normalizePropertyNameClosure = function () {
$that = $this;

/* @var FilterInterface $that */
/* @phpstan-ignore method.notFound */
return $that->normalizePropertyName('some.relation.field');
};

$this->assertEquals('aliasedField', $normalizePropertyNameClosure->call($fakeFilter));
}

#[Group('filter-test')]
public function testOdmFilterWithAliasedFieldsNormalizes(): void
{
$fakeFilter = $this->getFakeFilter();

$normalizePropertyNameClosure = function () {
$that = $this;

/* @var FilterInterface $that */
/* @phpstan-ignore method.notFound */
return $that->normalizePropertyName('some.relation.field');
};

$this->assertEquals('aliasedField', $normalizePropertyNameClosure->call($fakeFilter));
}

#[Group('filter-test')]
public function testOdmFilterWithAliasedFieldsDefaultsOnUnaliasedProperty(): void
{
$fakeFilter = $this->getFakeFilter();

$denormalizePropertyNameClosure = function () {
$that = $this;

/* @var FilterInterface $that */
/* @phpstan-ignore method.notFound */
return $that->denormalizePropertyName('name');
};

$normalizePropertyNameClosure = function () {
$that = $this;

/* @var FilterInterface $that */
/* @phpstan-ignore method.notFound */
return $that->normalizePropertyName('name');
};

$this->assertEquals('name', $denormalizePropertyNameClosure->call($fakeFilter));
$this->assertEquals('name', $normalizePropertyNameClosure->call($fakeFilter));
}
}
12 changes: 11 additions & 1 deletion src/Doctrine/Orm/Filter/AbstractFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Doctrine\Orm\Filter;

use ApiPlatform\Doctrine\Common\Filter\PropertyAliasesFilterTrait;
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
use ApiPlatform\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait;
Expand All @@ -27,12 +28,15 @@
abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface
{
use OrmPropertyHelperTrait;
use PropertyAliasesFilterTrait;
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, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null, array $propertyAliases = [])
{
$this->logger = $logger ?? new NullLogger();
$this->propertyAliases = $propertyAliases;
}

/**
Expand Down Expand Up @@ -91,6 +95,10 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b

protected function denormalizePropertyName(string|int $property): string
{
if ($this->isAlias($property)) {
$property = $this->getPropertyFromAlias($property);
}

if (!$this->nameConverter instanceof NameConverterInterface) {
return (string) $property;
}
Expand All @@ -100,6 +108,8 @@ protected function denormalizePropertyName(string|int $property): string

protected function normalizePropertyName(string $property): string
{
$property = $this->getAliasForPropertyOrProperty($property);

if (!$this->nameConverter instanceof NameConverterInterface) {
return $property;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Doctrine/Orm/Filter/BackedEnumFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
* book.backed_enum_filter:
* parent: 'api_platform.doctrine.orm.backed_enum_filter'
* arguments: [ { status: ~ } ]
* # you can also alias the properties you are filtering on to expose search under different names
* # arguments:
* # $properties: { status: ~ }
* # $propertyAliases: { status: 'statusLabel' }
* tags: [ 'api_platform.filter' ]
* # The following are mandatory only if a _defaults section is defined with inverted values.
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
Expand Down
Loading
Loading