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.