Skip to content

Symfony Forms ChoiceType chained choice_filter #60346

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

Open
RafaelKr opened this issue May 5, 2025 · 3 comments
Open

Symfony Forms ChoiceType chained choice_filter #60346

RafaelKr opened this issue May 5, 2025 · 3 comments

Comments

@RafaelKr
Copy link

RafaelKr commented May 5, 2025

Description

We have a lot of fields which use EnumType. Now some options start to become deprecated. We still need the enum cases to show them for previous form submissions but they shouldn't be select-able for future form submissions anymore. We now solve this by implementing

interface DeprecationAwareInterface
{
    public function isDeprecated(): bool;
}

on our Enum and then use

'choice_filter' => fn (MyEnum $myEnum) => !$myEnum->isDeprecated(),

to filter the available options.

It would be a nice DX improvement if we could handle this on a more global level (ChoiceType TypeExtension). Maybe by making the choice_filter chainable. Then I could add a global filter with high priority to the chain checking for instanceof DeprecationAwareInterface.

We're not using PHP 8.4 yet, but I would like to use the native #[\Deprecated] attribute as soon as we switch to 8.4 which would also nicely come into play for determining the deprecated options.

Example

No response

@yceruto
Copy link
Member

yceruto commented May 5, 2025

Hi Rafael, all form options are chainable by design (using the natural parent-child hierarchy and DI registration priority)

For example you can add a global type extension for EnumType, and do this:

$resolver->setDefault('choice_filter', function (Options $options, mixed $previousFilter): string {
    if (is_subclass_of($options['class'], DeprecationAwareInterface::class)) {
        return fn (DeprecationAwareInterface $myEnum) => !$myEnum->isDeprecated();
    }

    // Take default value configured in the base class
    return $previousValue;
});

Does this address your request?

@RafaelKr
Copy link
Author

RafaelKr commented May 5, 2025

Hi Yonel, nice, I didn't know this! But this only sets a default option, so if a developer defines a choice_filter on the field itself our extension is overwritten. I would like to define a default choice_filter which is always applied first. If it returns true it should step into the field-level choice_filter, if it's defined.

Probably it's somehow possible via DependencyInejction, maybe by decorating DefaultChoiceListFactory and creating a custom FilterChoiceLoaderDecorator, but I'd wish there's a simpler way to do this.

@yceruto
Copy link
Member

yceruto commented May 5, 2025

You could use setNormalizer() instead. It allows you to work with the user filter if provided, and then apply your own chaining strategy: default filters + user filters:

$resolver->setNormalizer('choice_filter', function (Options $options, mixed $userFilter) {
    return new CompositeChoiceFilter(...$this->defaultFilters, $userFilter);
});

Being $this->defaultFilters your custom list of filters (e.g. DI-based invokable services), then you can create your own CompositeChoiceFilter and iterate over all filters given.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants