diff --git a/docs/reference/filter_field_definition.rst b/docs/reference/filter_field_definition.rst index ca2d1f737..27486849a 100644 --- a/docs/reference/filter_field_definition.rst +++ b/docs/reference/filter_field_definition.rst @@ -44,6 +44,7 @@ For now, only `Doctrine ORM` filters are available: * ``Sonata\DoctrineORMAdminBundle\Filter\DateTimeFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\DateTimeType`` Form Type, renders a datetime field, * ``Sonata\DoctrineORMAdminBundle\Filter\DateTimeRangeFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\DateTimeRangeType`` Form Type, renders a 2 datetime fields, * ``Sonata\DoctrineORMAdminBundle\Filter\ClassFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\DefaultType`` Form type, renders a choice list field. +* ``Sonata\DoctrineORMAdminBundle\Filter\EmptyFieldFilter``: depends on the ``Sonata\AdminBundle\Form\Type\Filter\DefaultType`` Form type, renders a choice list field. Example ------- @@ -162,6 +163,26 @@ ClassFilter } } +Empty +----- + +``Sonata\DoctrineORMAdminBundle\Filter\EmptyFieldFilter`` supports filtering for empty (null) entity fields:: + + namespace Sonata\NewsBundle\Admin; + + use Sonata\AdminBundle\Admin\AbstractAdmin; + use Sonata\AdminBundle\Datagrid\DatagridMapper; + use Sonata\AdminBundle\Filter\EmptyFieldFilter; + + final class PostAdmin extends AbstractAdmin + { + protected function configureDatagridFilters(DatagridMapper $datagridMapper) + { + $datagridMapper + ->add('deleted', EmptyFieldFilter::class, ['field_name' => 'deletedAt']); + } + } + Advanced usage -------------- diff --git a/src/Filter/EmptyFieldFilter.php b/src/Filter/EmptyFieldFilter.php new file mode 100644 index 000000000..6ff42ae27 --- /dev/null +++ b/src/Filter/EmptyFieldFilter.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineORMAdminBundle\Filter; + +use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; +use Sonata\AdminBundle\Form\Type\Filter\DefaultType; +use Sonata\Form\Type\BooleanType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; + +final class EmptyFieldFilter extends Filter +{ + /** + * @param string $alias + * @param string $field + * @param mixed[]|null $data + */ + public function filter(ProxyQueryInterface $queryBuilder, $alias, $field, $data): void + { + if (null === $data || !\is_array($data) || !\array_key_exists('value', $data)) { + return; + } + + if (BooleanType::TYPE_NO === (int) $data['value']) { + $this->applyWhere( + $queryBuilder, + $queryBuilder + ->expr() + ->isNull(sprintf('%s.%s', $alias, $field)) + ); + } elseif (BooleanType::TYPE_YES === (int) $data['value']) { + $this->applyWhere( + $queryBuilder, + $queryBuilder + ->expr() + ->isNotNull(sprintf('%s.%s', $alias, $field)) + ); + } + } + + public function getDefaultOptions() + { + return [ + 'field_type' => BooleanType::class, + 'operator_type' => HiddenType::class, + 'operator_options' => [], + ]; + } + + public function getRenderSettings() + { + return [DefaultType::class, [ + 'field_type' => $this->getFieldType(), + 'field_options' => $this->getFieldOptions(), + 'operator_type' => $this->getOption('operator_type'), + 'operator_options' => $this->getOption('operator_options'), + 'label' => $this->getLabel(), + ]]; + } + + public function getParentAssociationMappings() + { + $mappings = $this->getOption('parent_association_mappings', []); + + $fields = explode('.', $this->getFieldName(), -1); + + foreach ($fields as $field) { + $mappings[] = ['fieldName' => $field]; + } + + return $mappings; + } + + /** + * @param mixed|null $data + * + * @return string[] + */ + protected function association(ProxyQueryInterface $queryBuilder, $data): array + { + $alias = $queryBuilder->entityJoin($this->getParentAssociationMappings()); + $part = strrchr('.'.$this->getFieldName(), '.'); + $fieldName = substr(false === $part ? $this->getFieldType() : $part, 1); + + return [$alias, $fieldName]; + } +} diff --git a/src/Resources/config/doctrine_orm_filter_types.xml b/src/Resources/config/doctrine_orm_filter_types.xml index e921938cf..a26de8139 100644 --- a/src/Resources/config/doctrine_orm_filter_types.xml +++ b/src/Resources/config/doctrine_orm_filter_types.xml @@ -40,5 +40,8 @@ + + + diff --git a/tests/Filter/EmptyFieldFilterTest.php b/tests/Filter/EmptyFieldFilterTest.php new file mode 100644 index 000000000..3ccc2a21a --- /dev/null +++ b/tests/Filter/EmptyFieldFilterTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\DoctrineORMAdminBundle\Tests\Filter; + +use PHPUnit\Framework\TestCase; +use Sonata\DoctrineORMAdminBundle\Filter\EmptyFieldFilter; +use Sonata\Form\Type\BooleanType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +final class EmptyFieldFilterTest extends TestCase +{ + public function testRenderSettings(): void + { + $filter = new EmptyFieldFilter(); + $filter->initialize('field_name', [ + 'field_options' => ['class' => 'FooBar'], + ]); + $options = $filter->getRenderSettings()[1]; + + $this->assertSame(BooleanType::class, $options['field_type']); + } +}