diff --git a/doc/book/list-search-show-configuration.rst b/doc/book/list-search-show-configuration.rst index 89a69c61da..1bc7d90112 100644 --- a/doc/book/list-search-show-configuration.rst +++ b/doc/book/list-search-show-configuration.rst @@ -715,6 +715,26 @@ new filter to the field which will use it: # optionally you can pass options to the filter class # type_options: {} +.. tip:: + + When configuring filters to entities, all filters are validated. Any filter + properties that do not exist on the entity class will cause an exception to be + thrown. + + In cases where you need extra filters in the form that will not be mapped to + the underlying entity class, you need to set the ``mapped`` option to ``false``:: + + # config/packages/easy_admin.yaml + easy_admin: + entities: + Users: + class: App\Entity\User + list: + filters: + - property: 'unmapped' + type: 'App\Form\Filter\CustomFilterType' + mapped: false + If the options passed to the filter are dynamic, you can't define them in the YAML config file. Instead, :ref:`create a custom controller ` for your entity and override the ``createFiltersForm()`` method:: diff --git a/src/Configuration/PropertyConfigPass.php b/src/Configuration/PropertyConfigPass.php index e94278e2ce..b7dac88abc 100644 --- a/src/Configuration/PropertyConfigPass.php +++ b/src/Configuration/PropertyConfigPass.php @@ -242,26 +242,26 @@ private function processFilterConfig(array $backendConfig): array foreach ($entityConfig['list']['filters'] ?? [] as $propertyName => $filterConfig) { $originalFilterConfig = $filterConfig; - if (!\array_key_exists($propertyName, $entityConfig['properties'])) { - throw new \InvalidArgumentException(\sprintf('The "%s" filter configured in the "list" view of the "%s" entity refers to a property called "%s" which is not defined in that entity.', $propertyName, $entityName, $propertyName)); - } - - // if the original filter didn't define the 'type' option, it will now - // be defined thanks to the 'type' value added by Doctrine's metadata - $filterConfig += $entityConfig['properties'][$propertyName]; - - if (!isset($originalFilterConfig['type'])) { - $guessedType = $this->filterRegistry->getTypeGuesser() - ->guessType($entityConfig['class'], $propertyName); - - if (null !== $guessedType) { - $filterConfig['type'] = $guessedType->getType(); - $filterConfig['type_options'] = \array_replace_recursive($guessedType->getOptions(), $filterConfig['type_options']); + if (\array_key_exists($propertyName, $entityConfig['properties'])) { + // if the original filter didn't define the 'type' option, it will now + // be defined thanks to the 'type' value added by Doctrine's metadata + $filterConfig += $entityConfig['properties'][$propertyName]; + + if (!isset($originalFilterConfig['type'])) { + $guessedType = $this->filterRegistry->getTypeGuesser() + ->guessType($entityConfig['class'], $propertyName); + + if (null !== $guessedType) { + $filterConfig['type'] = $guessedType->getType(); + $filterConfig['type_options'] = \array_replace_recursive($guessedType->getOptions(), $filterConfig['type_options']); + } } + } elseif ($filterConfig['mapped'] ?? true) { + throw new \InvalidArgumentException(\sprintf('The "%s" filter configured in the "list" view of the "%s" entity refers to a property called "%s" which is not defined in that entity. Set the "mapped" option to false if it is not intended to be a mapped property.', $propertyName, $entityName, $propertyName)); } if (!isset($filterConfig['type'])) { - throw new \InvalidArgumentException(\sprintf('The "%s" filter defined in the "list" view of the "%s" entity must define its own "type" explicitly because EasyAdmin cannot autoconfigure it using the "%s" data type of the associated property.', $propertyName, $entityName, $filterConfig['type'])); + throw new \InvalidArgumentException(\sprintf('The "%s" filter defined in the "list" view of the "%s" entity must define its own "type" explicitly because EasyAdmin cannot autoconfigure it.', $propertyName, $entityName)); } $backendConfig['entities'][$entityName]['list']['filters'][$propertyName] = $filterConfig; diff --git a/tests/Configuration/fixtures/exceptions/filters_unknown_property.yml b/tests/Configuration/fixtures/exceptions/filters_unknown_property.yml index 02db22739a..dffb6b2fb3 100644 --- a/tests/Configuration/fixtures/exceptions/filters_unknown_property.yml +++ b/tests/Configuration/fixtures/exceptions/filters_unknown_property.yml @@ -4,7 +4,7 @@ # EXCEPTION expected_exception: class: \InvalidArgumentException - message_string: 'The "this-property-does-not-exist" filter configured in the "list" view of the "Category" entity refers to a property called "this-property-does-not-exist" which is not defined in that entity.' + message_string: 'The "this-property-does-not-exist" filter configured in the "list" view of the "Category" entity refers to a property called "this-property-does-not-exist" which is not defined in that entity. Set the "mapped" option to false if it is not intended to be a mapped property.' # CONFIGURATION easy_admin: diff --git a/tests/Configuration/fixtures/exceptions/filters_unmapped_without_type.yml b/tests/Configuration/fixtures/exceptions/filters_unmapped_without_type.yml new file mode 100644 index 0000000000..cfe57b1571 --- /dev/null +++ b/tests/Configuration/fixtures/exceptions/filters_unmapped_without_type.yml @@ -0,0 +1,15 @@ +# TEST +# items in the 'filters' option must define the 'type' option when they are unmapped + +# EXCEPTION +expected_exception: + class: \InvalidArgumentException + message_string: 'The "foo" filter defined in the "list" view of the "Category" entity must define its own "type" explicitly because EasyAdmin cannot autoconfigure it.' + +# CONFIGURATION +easy_admin: + entities: + Category: + class: AppTestBundle\Entity\UnitTests\Category + list: + filters: [{ property: 'foo', mapped: false }]