This repository was archived by the owner on Oct 8, 2025. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 91
feat (filter): add filter system. #256
Open
GregoireHebert
wants to merge
6
commits into
Sylius:1.5
Choose a base branch
from
GregoireHebert:master
base: 1.5
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
815a24d
feat (filter): add filter system.
GregoireHebert 7897ddb
feat (filters): code style
GregoireHebert e1009c3
feat (filters): change non support of https://wiki.php.net/rfc/short_…
GregoireHebert 7d76b10
feat (filters): fix tests
GregoireHebert 14140f1
feat (filters): update copyrights terms
GregoireHebert 111121b
feat (filters): add search filter
GregoireHebert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace spec\Sylius\ShopApiPlugin\FilterExtension; | ||
|
|
||
| use Doctrine\ORM\QueryBuilder; | ||
| use PhpSpec\ObjectBehavior; | ||
| use Sylius\Component\Core\Model\Product; | ||
| use Sylius\ShopApiPlugin\FilterExtension\FilterExtension; | ||
| use Sylius\ShopApiPlugin\FilterExtension\Filters\FilterInterface; | ||
|
|
||
| final class FilterExtensionSpec extends ObjectBehavior | ||
| { | ||
| function it_is_initializable() | ||
| { | ||
| $this->shouldHaveType(FilterExtension::class); | ||
| } | ||
|
|
||
| function it_should_apply_filters(FilterInterface $filter, QueryBuilder $queryBuilder) | ||
| { | ||
| $ressourceClass = Product::class; | ||
| $filterConditions = ['boolean'=>['attribute'=>true]]; | ||
|
|
||
| $filter->applyFilter($filterConditions, $ressourceClass, $queryBuilder)->shouldBeCalled(); | ||
| $this->addFilter($filter); | ||
| $this->applyFilters($queryBuilder, $ressourceClass, $filterConditions); | ||
| } | ||
|
|
||
| function it_should_not_apply_anything(FilterInterface $filter, QueryBuilder $queryBuilder) | ||
| { | ||
| $ressourceClass = Product::class; | ||
| $filterConditions = ['boolean'=>['attribute'=>true]]; | ||
|
|
||
| $filter->applyFilter($filterConditions, $ressourceClass, $queryBuilder)->shouldNotBeCalled(); | ||
| $this->applyFilters($queryBuilder, $ressourceClass, $filterConditions); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace spec\Sylius\ShopApiPlugin\FilterExtension\Filters; | ||
|
|
||
| use Doctrine\Common\Persistence\ManagerRegistry; | ||
| use Doctrine\Common\Persistence\ObjectManager; | ||
| use Doctrine\ORM\QueryBuilder; | ||
| use Doctrine\Common\Persistence\Mapping\ClassMetadata; | ||
| use PhpSpec\ObjectBehavior; | ||
| use Psr\Log\LoggerInterface; | ||
| use Sylius\Component\Core\Model\Product; | ||
| use Sylius\ShopApiPlugin\FilterExtension\Filters\BooleanFilter; | ||
|
|
||
| final class BooleanFilterSpec extends ObjectBehavior | ||
| { | ||
| function let(ManagerRegistry $managerRegistry, LoggerInterface $logger) | ||
| { | ||
| $this->beConstructedWith($managerRegistry, $logger); | ||
| } | ||
|
|
||
| function it_is_initializable() | ||
| { | ||
| $this->shouldHaveType(BooleanFilter::class); | ||
| } | ||
|
|
||
| function it_should_ignore_a_non_boolean_condition(QueryBuilder $queryBuilder) | ||
| { | ||
| $conditions = ['search'=>['enabled'=>true]]; | ||
| $resourceClass = Product::class; | ||
|
|
||
| $queryBuilder->getRootAliases()->shouldNotBeCalled(); | ||
|
|
||
| $this->applyFilter($conditions, $resourceClass, $queryBuilder); | ||
| } | ||
|
|
||
| function it_should_apply_a_boolean_condition(ManagerRegistry $managerRegistry, ObjectManager $om, ClassMetadata $classMetadata, LoggerInterface $logger, QueryBuilder $queryBuilder) | ||
| { | ||
| $conditions = ['boolean'=>['enabled'=>true]]; | ||
| $resourceClass = Product::class; | ||
|
|
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. redundant blank line |
||
| // is property an association false | ||
| $classMetadata->hasAssociation('enabled')->willReturn(false); | ||
| // yes, this is a boolean | ||
| $classMetadata->getTypeOfField('enabled')->willReturn('boolean'); | ||
| // is property mapped true | ||
| $classMetadata->hasField('enabled')->willReturn(true); | ||
|
|
||
| $om->getClassMetadata($resourceClass)->willReturn($classMetadata); | ||
| $managerRegistry->getManagerForClass($resourceClass)->willReturn($om); | ||
|
|
||
| $this->beConstructedWith($managerRegistry, $logger); | ||
| $queryBuilder->getRootAliases()->shouldBeCalled(); | ||
| $queryBuilder->andWhere(".enabled = :enabled_p1")->shouldBeCalled()->willReturn($queryBuilder); | ||
| $queryBuilder->setParameter("enabled_p1", true)->shouldBeCalled(); | ||
|
|
||
| $this->applyFilter($conditions, $resourceClass, $queryBuilder); | ||
| } | ||
|
|
||
| function it_should_ignore_an_invalid_condition(ManagerRegistry $managerRegistry, ObjectManager $om, ClassMetadata $classMetadata, LoggerInterface $logger, QueryBuilder $queryBuilder) | ||
| { | ||
| $conditions = ['boolean'=>['enabled'=>'invalid']]; | ||
| $resourceClass = Product::class; | ||
|
|
||
|
|
||
| // is property an association false | ||
| $classMetadata->hasAssociation('enabled')->willReturn(false); | ||
| // yes, this is a boolean | ||
| $classMetadata->getTypeOfField('enabled')->willReturn('boolean'); | ||
| // is property mapped true | ||
| $classMetadata->hasField('enabled')->willReturn(true); | ||
|
|
||
| $om->getClassMetadata($resourceClass)->willReturn($classMetadata); | ||
| $managerRegistry->getManagerForClass($resourceClass)->willReturn($om); | ||
|
|
||
| $this->beConstructedWith($managerRegistry, $logger); | ||
| $queryBuilder->getRootAliases()->shouldNotBeCalled(); | ||
|
|
||
| $this->applyFilter($conditions, $resourceClass, $queryBuilder); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace spec\Sylius\ShopApiPlugin\FilterExtension\Filters; | ||
|
|
||
| use Doctrine\Common\Persistence\ManagerRegistry; | ||
| use Doctrine\Common\Persistence\ObjectManager; | ||
| use Doctrine\ORM\QueryBuilder; | ||
| use Doctrine\Common\Persistence\Mapping\ClassMetadata; | ||
| use PhpSpec\ObjectBehavior; | ||
| use Psr\Log\LoggerInterface; | ||
| use Sylius\Component\Core\Model\Product; | ||
| use Sylius\Component\Resource\Model\TranslationInterface; | ||
| use Sylius\ShopApiPlugin\FilterExtension\Filters\SearchFilter; | ||
|
|
||
| final class SearchFilterSpec extends ObjectBehavior | ||
| { | ||
| function let(ManagerRegistry $managerRegistry, LoggerInterface $logger) | ||
| { | ||
| $this->beConstructedWith($managerRegistry, $logger); | ||
| } | ||
|
|
||
| function it_is_initializable() | ||
| { | ||
| $this->shouldHaveType(SearchFilter::class); | ||
| } | ||
|
|
||
| function it_should_ignore_a_non_search_condition(QueryBuilder $queryBuilder) | ||
| { | ||
| $conditions = ['boolean'=>['translations.name'=>['exact'=>'Banane']]]; | ||
| $resourceClass = Product::class; | ||
|
|
||
| $queryBuilder->getRootAliases()->shouldNotBeCalled(); | ||
|
|
||
| $this->applyFilter($conditions, $resourceClass, $queryBuilder); | ||
| } | ||
|
|
||
| function it_should_apply_a_search_condition(ManagerRegistry $managerRegistry, ObjectManager $om, ClassMetadata $classMetadata, LoggerInterface $logger, QueryBuilder $queryBuilder) | ||
| { | ||
| $conditions = ['search'=>['translations.name'=>['exact'=>'Banane']]]; | ||
| $resourceClass = Product::class; | ||
|
|
||
| // is property an association true | ||
| $classMetadata->hasAssociation('translations')->willReturn(true); | ||
| $classMetadata->hasAssociation('name')->willReturn(false); | ||
|
|
||
| $classMetadata->getAssociationTargetClass('translations')->willReturn(TranslationInterface::class); | ||
|
|
||
| // yes, this is a string | ||
| $classMetadata->getTypeOfField('translations.name')->willReturn('string'); | ||
|
|
||
| // is property mapped true | ||
| $classMetadata->hasField('translations.name')->willReturn(true); | ||
| $classMetadata->hasField('name')->willReturn(true); | ||
| $classMetadata->getTypeOfField('name')->willReturn('string'); | ||
|
|
||
| $om->getClassMetadata($resourceClass)->willReturn($classMetadata); | ||
| $om->getClassMetadata(TranslationInterface::class)->willReturn($classMetadata); | ||
|
|
||
| $managerRegistry->getManagerForClass(TranslationInterface::class)->willReturn($om); | ||
| $managerRegistry->getManagerForClass($resourceClass)->willReturn($om); | ||
|
|
||
| $this->beConstructedWith($managerRegistry, $logger); | ||
| $queryBuilder->getRootAliases()->shouldBeCalled()->willReturn('o'); | ||
| $queryBuilder->getDQLPart('join')->shouldBeCalled()->willReturn([]); | ||
| $queryBuilder->innerJoin('o.translations', 'translations_a1', null, null)->shouldBeCalled(); | ||
| $queryBuilder->andWhere('translations_a1.name = :name_p1')->shouldBeCalled()->willReturn($queryBuilder); | ||
| $queryBuilder->setParameter("name_p1", "Banane")->shouldBeCalled()->willReturn($queryBuilder); | ||
|
|
||
| $this->applyFilter($conditions, $resourceClass, $queryBuilder); | ||
| } | ||
|
|
||
| function it_should_ignore_an_invalid_condition(ManagerRegistry $managerRegistry, ObjectManager $om, ClassMetadata $classMetadata, LoggerInterface $logger, QueryBuilder $queryBuilder) | ||
| { | ||
| $conditions = ['search'=>['translations.name'=>['invalid'=>[]]]]; | ||
| $resourceClass = Product::class; | ||
|
|
||
| // is property an association true | ||
| $classMetadata->hasAssociation('translations')->willReturn(true); | ||
| $classMetadata->hasAssociation('name')->willReturn(false); | ||
|
|
||
| $classMetadata->getAssociationTargetClass('translations')->willReturn(TranslationInterface::class); | ||
|
|
||
| // yes, this is a string | ||
| $classMetadata->getTypeOfField('translations.name')->willReturn('string'); | ||
|
|
||
| // is property mapped true | ||
| $classMetadata->hasField('translations.name')->willReturn(true); | ||
| $classMetadata->hasField('name')->willReturn(true); | ||
|
|
||
| $om->getClassMetadata($resourceClass)->willReturn($classMetadata); | ||
| $om->getClassMetadata(TranslationInterface::class)->willReturn($classMetadata); | ||
|
|
||
| $managerRegistry->getManagerForClass(TranslationInterface::class)->willReturn($om); | ||
| $managerRegistry->getManagerForClass($resourceClass)->willReturn($om); | ||
|
|
||
| $this->beConstructedWith($managerRegistry, $logger); | ||
| $queryBuilder->getRootAliases()->shouldBeCalled()->willReturn('o'); | ||
| $queryBuilder->getDQLPart('join')->shouldBeCalled()->willReturn([]); | ||
| $queryBuilder->innerJoin("o.translations", "translations_a1", null, null)->shouldBeCalled(); | ||
|
|
||
| $this->applyFilter($conditions, $resourceClass, $queryBuilder); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sylius\ShopApiPlugin\Controller\Product; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
src/DependencyInjection/Compiler/FiltersDefinitionPass.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sylius\ShopApiPlugin\DependencyInjection\Compiler; | ||
|
|
||
| use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
| use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
| use Symfony\Component\DependencyInjection\Reference; | ||
|
|
||
| /** | ||
| * Gets every filter definition and adds them to the Filter Extension. | ||
| * | ||
| * @author Grégoire Hébert <gregoire@les-tilleuls.coop> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hope you don't mind, but we have resigned from author blocks as they are redundant (you will be mentioned in code contributors) and not obvious to maintain. |
||
| */ | ||
| class FiltersDefinitionPass implements CompilerPassInterface | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing final |
||
| { | ||
| public function process(ContainerBuilder $container) | ||
| { | ||
| if (!$container->has('sylius.shop_api_plugin.filters.filter_extension')) { | ||
| return; | ||
| } | ||
|
|
||
| $definition = $container->findDefinition('sylius.shop_api_plugin.filters.filter_extension'); | ||
| $taggedServices = $container->findTaggedServiceIds('sylius.shop_api_plugin.filter'); | ||
|
|
||
| foreach ($taggedServices as $id => $tags) { | ||
| $definition->addMethodCall('addFilter', [new Reference($id)]); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sylius\ShopApiPlugin\Exception; | ||
|
|
||
| /** | ||
| * Invalid argument exception. | ||
| * | ||
| * @author Grégoire Hébert <gregoire@les-tilleuls.coop> | ||
| */ | ||
| class InvalidArgumentException extends \InvalidArgumentException | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little bit redundant ;) |
||
| { | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be more readable if we use full name instead of abbreviation for 'om'