Skip to content

Commit

Permalink
Refactor list filters (#92)
Browse files Browse the repository at this point in the history
Thanks to @CheapHasz initiative and work :

* All list filter forms use ListFilter as model
* Add more built-in operators to list filters
* Improved list filter forms guesser
* Decoupling `name` and `property` for a list filter form=> allows to define many list filters dealing with the same entity property
  • Loading branch information
alterphp committed Jan 30, 2019
1 parent 69ff607 commit 348e0af
Show file tree
Hide file tree
Showing 15 changed files with 613 additions and 102 deletions.
38 changes: 38 additions & 0 deletions README.md
Expand Up @@ -125,6 +125,13 @@ class Animation
*/
private $type;

/**
* @var int
*
* @ORM\Column(type="integer", nullable=false)
*/
private $maxSubscriptions;

/**
* @var Organization
*
Expand Down Expand Up @@ -169,13 +176,44 @@ Let's see the result !

![Embedded list example](/doc/res/img/list-form-filters.png)

#### Automatic list filter guesser

Guesser for list form filters are based on mapped entity property :
* _boolean_: guessed filter is a choice list (null, Yes, No)
* _string_: guessed filter is multiple choice list that requires either `choices` (value/label array) or `choices_static_callback` (static callback from entity class returning a value/label array) in `type_options`.
* _integer_, _smallint_, _bigint_: guessed filter is an integer input
* _decimal_, _float_: guessed filter is a number input
* _*-to-one-relation_: guessed filter is a multiple autocomplete of relation target entity.

Filters form's method is GET and submitted through `form_filter` parameter. It is transmitted to the referer used for post update/delete/create redirection AND for search !

#### List filter operator

By default, list filter use `equals` operator or `in` for multiple value filters.

But you can use more operators with the `operator` attribute :

```yaml
entities:
Animation:
class: App\Entity\Animation
list:
form_filters:
- { name: maxSubscriptionGTE, property: maxSubscriptions, label: 'Max subscriptions >=', operator: gte }
- { name: maxSubscriptionLTE, property: maxSubscriptions, label: 'Max subscriptions <=', operator: lte }
```

Available built-in operators are listed in `AlterPHP\EasyAdminExtensionBundle\Model\ListFilter` class, as constant `OPERATOR_*` :
* __equals__: Is equal to
* __not__: Is different of
* __in__: Is in (`array` or Doctrine `Collection` expected)
* __notin__: Is not in (`array` or Doctrine `Collection` expected)
* __gt__: Is greater than
* __gte__: Is greater than or equal to
* __lt__: Is lower than
* __lte__: Is lower than or equal to


### Filter list and search on request parameters

* EasyAdmin allows filtering list with `dql_filter` configuration entry. But this is not dynamic and must be configured as an apart list in `easy_admin` configuration.*
Expand Down
31 changes: 31 additions & 0 deletions UPGRADE.md
@@ -0,0 +1,31 @@
# UPGRADE guide for EasyAdminExtension bundle

## v2.1.0

List filters form have been improved, with minor BC breaks :

* Label must be configured on the root level, not in the `type_options` attribiute.

__BEFORE__

```yaml
easy_admin:
entities:
MyEntity:
class: App\Entity\MyEntity
list:
form_filters:
- { name: myFilter, property: status, type_options: { label: 'Filter on status' } }
```

__AFTER__

```yaml
easy_admin:
entities:
MyEntity:
class: App\Entity\MyEntity
list:
form_filters:
- { name: myFilter, property: status, label: 'Filter on status' }
```
58 changes: 43 additions & 15 deletions src/Configuration/ListFormFiltersConfigPass.php
Expand Up @@ -2,11 +2,15 @@

namespace AlterPHP\EasyAdminExtensionBundle\Configuration;

use AlterPHP\EasyAdminExtensionBundle\Model\ListFilter;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\DBAL\Types\type as DBALType;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use EasyCorp\Bundle\EasyAdminBundle\Configuration\ConfigPassInterface;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminAutocompleteType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;

/**
* Guess form types for list form filters.
Expand Down Expand Up @@ -52,7 +56,7 @@ public function process(array $backendConfig): array

// Key mapping
if (\is_string($formFilter)) {
$filterConfig = ['property' => $formFilter];
$filterConfig = ['name' => $formFilter, 'property' => $formFilter];
} else {
if (!\array_key_exists('property', $formFilter)) {
throw new \RuntimeException(
Expand All @@ -64,6 +68,8 @@ public function process(array $backendConfig): array
}

$filterConfig = $formFilter;
// Auto set name with property value
$filterConfig['name'] = $filterConfig['name'] ?? $filterConfig['property'];
}

$this->configureFilter(
Expand All @@ -77,7 +83,7 @@ public function process(array $backendConfig): array
continue;
}

$formFilters[$filterConfig['property']] = $filterConfig;
$formFilters[$filterConfig['name']] = $filterConfig;
}

// set form filters config and form !
Expand All @@ -89,11 +95,6 @@ public function process(array $backendConfig): array

private function configureFilter(string $entityClass, array &$filterConfig, string $translationDomain)
{
// No need to guess type
if (isset($filterConfig['type'])) {
return;
}

$em = $this->doctrine->getManagerForClass($entityClass);
$entityMetadata = $em->getMetadataFactory()->getMetadataFor($entityClass);

Expand All @@ -116,11 +117,13 @@ private function configureFilter(string $entityClass, array &$filterConfig, stri
}
}

private function configureFieldFilter(string $entityClass, array $fieldMapping, array &$filterConfig, string $translationDomain)
{
private function configureFieldFilter(
string $entityClass, array $fieldMapping, array &$filterConfig, string $translationDomain
) {
switch ($fieldMapping['type']) {
case 'boolean':
$filterConfig['type'] = ChoiceType::class;
case DBALType::BOOLEAN:
$filterConfig['operator'] = $filterConfig['operator'] ?? ListFilter::OPERATOR_EQUALS;
$filterConfig['type'] = $filterConfig['type'] ?? ChoiceType::class;
$defaultFilterConfigTypeOptions = [
'choices' => [
'list_form_filters.default.boolean.true' => true,
Expand All @@ -129,19 +132,43 @@ private function configureFieldFilter(string $entityClass, array $fieldMapping,
'choice_translation_domain' => 'EasyAdminBundle',
];
break;
case 'string':
$filterConfig['type'] = ChoiceType::class;
case DBALType::STRING:
$filterConfig['operator'] = $filterConfig['operator'] ?? ListFilter::OPERATOR_IN;
$filterConfig['type'] = $filterConfig['type'] ?? ChoiceType::class;
$defaultFilterConfigTypeOptions = [
'multiple' => true,
'multiple' => in_array($filterConfig['operator'], [ListFilter::OPERATOR_IN, ListFilter::OPERATOR_NOTIN]),
'placeholder' => '-',
'choices' => $this->getChoiceList($entityClass, $filterConfig['property'], $filterConfig),
'attr' => ['data-widget' => 'select2'],
'choice_translation_domain' => $translationDomain,
];
break;
case DBALType::SMALLINT:
case DBALType::INTEGER:
case DBALType::BIGINT:
$filterConfig['operator'] = $filterConfig['operator'] ?? ListFilter::OPERATOR_EQUALS;
$filterConfig['type'] = $filterConfig['type'] ?? IntegerType::class;
$defaultFilterConfigTypeOptions = [];
break;
case DBALType::DECIMAL:
case DBALType::FLOAT:
$filterConfig['operator'] = $filterConfig['operator'] ?? ListFilter::OPERATOR_EQUALS;
$filterConfig['type'] = $filterConfig['type'] ?? NumberType::class;
$defaultFilterConfigTypeOptions = [];
break;
default:
return;
}

// Auto set multiple on ChoiceType when operator requires array
if (ChoiceType::class === $filterConfig['type']) {
$defaultFilterConfigTypeOptions['choices'] = $defaultFilterConfigTypeOptions['choices'] ?? $this->getChoiceList($entityClass, $filterConfig['property'], $filterConfig);

if (in_array($filterConfig['operator'], [ListFilter::OPERATOR_IN, ListFilter::OPERATOR_NOTIN])) {
$defaultFilterConfigTypeOptions['multiple'] = $defaultFilterConfigTypeOptions['multiple'] ?? true;
}
}

// Merge default type options when defined
if (null !== $defaultFilterConfigTypeOptions) {
$filterConfig['type_options'] = \array_merge(
Expand All @@ -155,7 +182,8 @@ private function configureAssociationFilter(string $entityClass, array $associat
{
// To-One (EasyAdminAutocompleteType)
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
$filterConfig['type'] = EasyAdminAutocompleteType::class;
$filterConfig['operator'] = $filterConfig['operator'] ?? ListFilter::OPERATOR_IN;
$filterConfig['type'] = $filterConfig['type'] ?? EasyAdminAutocompleteType::class;
$filterConfig['type_options'] = \array_merge(
[
'class' => $associationMapping['targetEntity'],
Expand Down
2 changes: 2 additions & 0 deletions src/Configuration/ShortFormTypeConfigPass.php
Expand Up @@ -3,6 +3,7 @@
namespace AlterPHP\EasyAdminExtensionBundle\Configuration;

use AlterPHP\EasyAdminExtensionBundle\Form\Type\EasyAdminEmbeddedListType;
use AlterPHP\EasyAdminExtensionBundle\Form\Type\ListFilterType;
use AlterPHP\EasyAdminExtensionBundle\Form\Type\Security\AdminRolesType;
use EasyCorp\Bundle\EasyAdminBundle\Configuration\ConfigPassInterface;
use EasyCorp\Bundle\EasyAdminBundle\Form\Util\FormTypeHelper;
Expand All @@ -23,6 +24,7 @@ class ShortFormTypeConfigPass implements ConfigPassInterface
private static $nativeShortFormTypes = [
'embedded_list' => EasyAdminEmbeddedListType::class,
'admin_roles' => AdminRolesType::class,
'list_filter' => ListFilterType::class,
];

public function __construct(array $customFormTypes = [])
Expand Down

0 comments on commit 348e0af

Please sign in to comment.