How can I avoid applying attr to all options of my choice field? How can I avoid applying attr to all options of my choice field? symfony symfony

How can I avoid applying attr to all options of my choice field?


Thanks to the comment about choice_attr by @user2268997 I found the related blog post New in Symfony 2.7: Choice form type refactorization which details the use of the (as of now undocumented) choice_attr option.

It seems Symfony merges the attributes in choice_attr with the ones in attr when rendering the field. This means we need to overwrite the class attribute in choice_attr.

I tried doing this in the code next to where I define attr but had no luck. It seems you need to do this in your form type definition. Here is an excerpt from my form after adding the choice_attr option:

namespace MyBundle\Form;public function buildForm(FormBuilderInterface $builder, array $options) {    $builder        ->add('roles',            'entity',            [                'class' => 'MyBundle:Role',                'choice_label' => 'name',                'multiple' => true,                'choice_attr' => function () { return ["class" => ""]; }            ]);}

The result is as I had hoped. I will probably also refactor this to my own custom form type so I do not need to repeat it all over my bundle.


I have now decided to create a custom choice type with the desired behavior described above and use that one throughout my application.

Here is my choice type:

use Symfony\Component\Form\Extension\Core\Type\ChoiceType;use Symfony\Component\OptionsResolver\OptionsResolver;class ChoiceNoOptAttrType extends ChoiceType {    public function configureOptions(OptionsResolver $resolver) {        parent::configureOptions($resolver);        $resolver->setDefault("choice_attr", function () { return ["class" => ""]; });    }}

I did not feel like refactoring all my existing forms to use this new type, so instead I opted to replace the Symfony-provided choice type with mine. This can be achieved by modifying the service configuration for the choice form type. To do this, I created a compiler pass for my bundle.

Further reading: Creating a Compiler Pass

namespace MyBundle\DependencyInjection\Compiler;use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;use Symfony\Component\DependencyInjection\ContainerBuilder;class MyCompilerPass implements CompilerPassInterface{    public function process(ContainerBuilder $container)    {        $definition = $container->getDefinition("form.type.choice");        $definition->setClass('MyBundle\Form\ChoiceNoOptAttrType');    }}

Now all that is left to do is register the compiler pass in the bundle.

Further reading: How to Work with Compiler Passes in Bundles

namespace MyBundle;use Symfony\Component\DependencyInjection\ContainerBuilder;use Symfony\Component\HttpKernel\Bundle\Bundle;use MyBundle\DependencyInjection\Compiler\MyCompilerPass;class MyBundle extends Bundle{    public function build(ContainerBuilder $container)    {        parent::build($container);        $container->addCompilerPass(new MyCompilerPass());    }}

And this is it. Now all my choice fields are using my custom class which makes sure that the CSS class set in attr is not propagated to my <option> elements.


There might be a simpler solution, but you might want to take a look at Form Themes. Override the Template for choice_widget_options so that the classes are not applied to the option tags.

{%- block choice_widget_options -%}    {% for group_label, choice in options %}        {%- if choice is iterable -%}            <optgroup label="{{ choice_translation_domain is sameas(false) ? group_label : group_label|trans({}, choice_translation_domain) }}">                {% set options = choice %}                {{- block('choice_widget_options') -}}            </optgroup>        {%- else -%}            {% set attr = choice.attr %}            <option value="{{ choice.value }}" {# DELETE THIS PART: {{ block('attributes') }}#}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is sameas(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>        {%- endif -%}    {% endfor %}{%- endblock choice_widget_options -%}