-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Use filters on nested properties #4882
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
base: 4.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| <?php | ||
|
|
||
| namespace EasyCorp\Bundle\EasyAdminBundle\Filter\Configurator; | ||
|
|
||
| use Doctrine\Persistence\ManagerRegistry; | ||
| use Doctrine\Persistence\ObjectManager; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Contracts\Filter\FilterConfiguratorInterface; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Contracts\Filter\FilterInterface; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDto; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Filter\NestedFilter; | ||
|
|
||
| /** | ||
| * @author Brandon Marcachi <brandon.marcachi@gmail.com> | ||
| */ | ||
| final class NestedConfigurator implements FilterConfiguratorInterface | ||
| { | ||
| private $doctrine; | ||
| private $filterConfigurators; | ||
|
|
||
| public function __construct(ManagerRegistry $doctrine, iterable $filterConfigurators = []) | ||
| { | ||
| $this->doctrine = $doctrine; | ||
| $this->filterConfigurators = $filterConfigurators; | ||
| } | ||
|
|
||
| public function supports(FilterDto $filterDto, ?FieldDto $fieldDto, EntityDto $entityDto, AdminContext $context): bool | ||
| { | ||
| return NestedFilter::class === $filterDto->getFqcn(); | ||
| } | ||
|
|
||
| public function configure(FilterDto $filterDto, ?FieldDto $fieldDto, EntityDto $entityDto, AdminContext $context): void | ||
| { | ||
| $entityFqcn = $entityDto->getFqcn(); | ||
|
|
||
| [$targetClassMetadata, $targetProperty] = NestedFilter::extractTargets( | ||
| $this->getObjectManager($entityFqcn), | ||
| $entityFqcn, | ||
| $filterDto->getProperty() | ||
| ); | ||
|
|
||
| $wrappedEntityDto = new EntityDto($targetClassMetadata->getName(), $targetClassMetadata); | ||
| $wrappedFilter = $this->extractWrappedFilter($filterDto); | ||
| $wrappedFilterDto = $wrappedFilter->getAsDto(); | ||
| $wrappedFilterDto->setProperty($targetProperty); | ||
|
|
||
| $this->configureFilter($wrappedFilterDto, $wrappedEntityDto, $context); | ||
|
|
||
| $filterDto->setFormType($wrappedFilterDto->getFormType()); | ||
| $filterDto->setFormTypeOptions($wrappedFilterDto->getFormTypeOptions()); | ||
|
|
||
| $this->removeWrappedFilterOption($filterDto); | ||
| } | ||
|
|
||
| private function extractWrappedFilter(FilterDto $filterDto): FilterInterface | ||
| { | ||
| return $filterDto->getFormTypeOption(NestedFilter::FORM_OPTION_WRAPPED_FILTER); | ||
| } | ||
|
|
||
| private function removeWrappedFilterOption(FilterDto $filterDto): void | ||
| { | ||
| [$root, $filterKey] = explode('.', NestedFilter::FORM_OPTION_WRAPPED_FILTER); | ||
|
|
||
| $data = $filterDto->getFormTypeOption($root); | ||
| unset($data[$filterKey]); | ||
| $filterDto->setFormTypeOption($root, $data); | ||
| } | ||
|
|
||
| private function configureFilter(FilterDto $filterDto, EntityDto $entityDto, AdminContext $context): void | ||
| { | ||
| foreach ($this->filterConfigurators as $configurator) { | ||
| if ($configurator->supports($filterDto, null, $entityDto, $context)) { | ||
| $configurator->configure($filterDto, null, $entityDto, $context); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private function getObjectManager(string $entityFqcn): ObjectManager | ||
| { | ||
| if (null === $objectManager = $this->doctrine->getManagerForClass($entityFqcn)) { | ||
| throw new \RuntimeException(sprintf('There is no Doctrine Object Manager defined for the "%s" class.', $entityFqcn)); | ||
| } | ||
|
|
||
| return $objectManager; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,159 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace EasyCorp\Bundle\EasyAdminBundle\Filter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Doctrine\ORM\QueryBuilder; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Doctrine\Persistence\ObjectManager; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use EasyCorp\Bundle\EasyAdminBundle\Contracts\Filter\FilterInterface; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @author Brandon Marcachi <brandon.marcachi@gmail.com> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final class NestedFilter implements FilterInterface | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use FilterTrait; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public const FORM_OPTION_WRAPPED_FILTER = 'attr.wrapped_filter'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public const PATH_SEPARATOR_EXPECTED = '.'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public const PATH_SEPARATOR = '_'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** @var FilterInterface */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private $wrappedFilter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static function new(string $propertyName, string $label = null): self | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 don't see a reason for this method to be present, it just confuses more. There is no Also, I see a point in renaming |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new \RuntimeException('Instead of this method, use the "wrap()" method.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static function wrap(FilterInterface $filter): FilterInterface | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $filterDto = $filter->getAsDto(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $property = $filterDto->getProperty(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (false === strpos($property, self::PATH_SEPARATOR_EXPECTED)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $filter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (new self()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ->setFilterFqcn(__CLASS__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ->setProperty($property) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ->setFormType($filterDto->getFormType()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ->setFormTypeOptions($filterDto->getFormTypeOptions()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ->setWrappedFilter($filter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $propertyPath = $filterDataDto->getProperty(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [$targetClassMetadata, $targetProperty] = self::extractTargets( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $queryBuilder->getEntityManager(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $entityDto->getFqcn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $propertyPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $wrappedEntityDto = new EntityDto($targetClassMetadata->getName(), $targetClassMetadata); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $wrappedFilter = $this->getWrappedFilter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $wrappedFilterDto = $wrappedFilter->getAsDto(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $wrappedFilterDto->setProperty($targetProperty); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Apply required left joins and get the alias we have to work with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $alias = $this->applyLeftJoins($queryBuilder, $filterDataDto->getEntityAlias(), $propertyPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. This adds support for embeddables.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Recreate FilterDataDto adapted for the wrapped filter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $wrappedFilterDataDto = FilterDataDto::new($filterDataDto->getIndex(), $wrappedFilterDto, $alias, [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'value' => $filterDataDto->getValue(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'value2' => $filterDataDto->getValue2(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'comparison' => $filterDataDto->getComparison(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $wrappedFilterDto->apply($queryBuilder, $wrappedFilterDataDto, null, $wrappedEntityDto); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static function extractTargets(ObjectManager $objectManager, string $class, string $propertyPath): array | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $segments = explode(self::PATH_SEPARATOR, $propertyPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $metadata = $objectManager->getClassMetadata($class); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $lastIndex = \count($segments) - 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $property = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($segments as $i => $prop) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!$metadata->hasField($prop) && !$metadata->hasAssociation($prop)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self::throwInvalidPropertyPathException($propertyPath, $class); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The target property must be at the end of path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($i === $lastIndex) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $property = $prop; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+84
to
+94
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. This adds support for embeddables.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!$metadata->hasAssociation($prop)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self::throwInvalidPropertyPathException($propertyPath, $class); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Move to next nested class | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $metadata = $objectManager->getClassMetadata($metadata->getAssociationTargetClass($prop)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [$metadata, $property]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function setWrappedFilter(FilterInterface $filter): self | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->wrappedFilter = $filter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $this->setFormTypeOption(self::FORM_OPTION_WRAPPED_FILTER, $filter); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function getWrappedFilter(): FilterInterface | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $this->wrappedFilter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function setProperty(string $propertyName): self | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Replace dots with underscore to avoid errors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->dto->setProperty( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| str_replace(self::PATH_SEPARATOR_EXPECTED, self::PATH_SEPARATOR, $propertyName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private function applyLeftJoins(QueryBuilder $qb, string $alias, string $propertyPath): string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $path = explode(self::PATH_SEPARATOR, $propertyPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $lastIndex = \count($path) - 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $currentAlias = $alias; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($path as $i => $prop) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($i === $lastIndex) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $nextAlias = sprintf('%s_%s', $currentAlias, $prop); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!\in_array($nextAlias, $qb->getAllAliases(), true)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $qb->leftJoin(sprintf('%s.%s', $currentAlias, $prop), $nextAlias); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $currentAlias = $nextAlias; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $currentAlias; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static function throwInvalidPropertyPathException(string $propertyPath, string $class): void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new \InvalidArgumentException(sprintf( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'The property path "%s" for class "%s" is invalid.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| str_replace(self::PATH_SEPARATOR, self::PATH_SEPARATOR_EXPECTED, $propertyPath), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $class | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| <?php | ||
|
|
||
| namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Filter; | ||
|
|
||
| use Doctrine\Bundle\DoctrineBundle\Registry; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Filter\Configurator\NestedConfigurator; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Filter\NestedFilter; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Filter\TextFilter; | ||
| use EasyCorp\Bundle\EasyAdminBundle\Tests\TestApplication\Entity\User; | ||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||
|
|
||
| class NestedConfiguratorTest extends KernelTestCase | ||
| { | ||
| /** @var Registry */ | ||
| private $doctrine; | ||
|
|
||
| /** @var NestedConfigurator */ | ||
| private $nestedConfigurator; | ||
|
|
||
| protected function setUp(): void | ||
| { | ||
| self::bootKernel(); | ||
|
|
||
| $container = self::getContainer(); | ||
|
|
||
| $this->doctrine = $container->get('doctrine'); | ||
| $this->nestedConfigurator = $container->get(NestedConfigurator::class); | ||
| } | ||
|
|
||
| public function testConfigure() | ||
| { | ||
| $class = User::class; | ||
| $attr = ['class' => 'foo']; | ||
|
|
||
| $textFilter = TextFilter::new('blogPosts.categories.name'); | ||
| $textFilter->setFormTypeOption('attr', $attr); | ||
|
|
||
| $nestedFilter = NestedFilter::wrap($textFilter); | ||
|
|
||
| $objectManager = $this->doctrine->getManagerForClass($class); | ||
| $entityDto = new EntityDto($class, $objectManager->getClassMetadata($class)); | ||
| $adminContext = $this->getMockBuilder(AdminContext::class)->disableOriginalConstructor()->getMock(); | ||
|
|
||
| $wrappedFilter = $nestedFilter->getWrappedFilter(); | ||
| $wrappedFilterDto = $wrappedFilter->getAsDto(); | ||
| $nestedFilterDto = $nestedFilter->getAsDto(); | ||
|
|
||
| self::assertEquals('blogPosts.categories.name', $wrappedFilterDto->getProperty()); | ||
|
|
||
| $this->nestedConfigurator->configure($nestedFilter->getAsDto(), null, $entityDto, $adminContext); | ||
|
|
||
| self::assertEquals('name', $wrappedFilterDto->getProperty()); | ||
| self::assertEquals('blogPosts_categories_name', $nestedFilterDto->getProperty()); | ||
|
|
||
| self::assertEquals($wrappedFilterDto->getFormType(), $nestedFilterDto->getFormType()); | ||
| self::assertEquals($wrappedFilterDto->getFormTypeOptions(), $nestedFilterDto->getFormTypeOptions()); | ||
| } | ||
| } |
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.
Could you please elaborate a bit: can this be implemented without adding
NestedFilter? Why you chose this way?For me, it looks much more natural to not have a wrapper for filters – similar to Fields. Just add a dot to filter and you're done... Isn't it?
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.
Hello,
Thank you for the PR and the review. Not sure but I think it's much more complex than just add a dot tbh. Otherwise I really don't understand why this feature is not already implemented. I did a PR for that job and I had to modify much more files to handle all cases due to internal mechanism : #4840