Search code examples
phpsymfonysymfony-forms

Symfony Form: How to apply different themes/widgets to specific form fields?


A Symfony 3.4 based form contains multiple CheckboxType fields. Some of these fields should be displayed as normal check boxes (= using the default checkbox widget) while some others should be displayed as on/off switches (like iOS UISwitche).

So, within the same form fields of the same type (CheckboxType) should use different widgets. How can I do this?


Solution 1: Of course I can apply a custom form_theme to the complete form ({% form_theme form 'AppBundle:Form:myTheme.html.twig' %}). But this effect all checkboxes within the form and not just only one. So this is not a solution.


Solution 2: A solution that would work is to apply a different widget to a one specific field using its id:

{% block _user_registration_form_someCheckBoxRow %}
    ...
{% endblock %}

While this solution works, I would have to create/override the same blocks for all effected field in all effected forms. This quite cumbersome and thus not a good solution.


Solution 3: Another solution that would work is to create a custom FormType to be used by the check boxes which should be displayed as switches. This form type would extend CheckboxType and do nothing but but override the blockPrefix.

While this comes pretty close to a good solution there is one major downside: Assume there are different themes / widgets for the check box switches. One with the label on top, one with the label next to the switch, etc. Each of these styles would not only need its own widget but also its own form type which need to be registered in the FormPass.php, etc. This would work of course but is also quite cumbersome and error prone.

So, this would be solution but not a nice one.


Isn't there a better way? I was searching for something like {{ form_row(form.someCheckbox, 'someWidget') }}. There would be only one extra file per theme and one could precisely tell which field uses which theme. No extra classes, config, etc. needed. I found nothing in this way.

Is using dummy form types really the best solution?


Solution

  • Referring to my last comment above, the following works with Symfony 5.0.x (though no major changes should be required for 3.4, probably only some extra configuration):

    To abstract the theme handling, define your own checkbox type, as follows (for example):

    <?php
    declare(strict_types=1);
    
    namespace App\Form\Type;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
    use Symfony\Component\OptionsResolver\Options;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    class ThemedCheckboxType extends AbstractType
    {
        /**
         * @param OptionsResolver $resolver
         */
        public function configureOptions(OptionsResolver $resolver): void
        {
            $resolver
                ->setDefaults([
                    'theme' => 'default',
                    'block_prefix' => static function (Options $options): ?string {
                        return [
                            'default' => null,
                            'theme-a' => 'theme_a_checkbox',
                            'theme-b' => 'theme_b_checkbox',
                        ][$options['theme']];
                    },
                ])
                ->setAllowedValues('theme', ['default', 'theme-a', 'theme-b']);
        }
    
        /**
         * @inheritDoc
         */
        public function getParent(): string
        {
            return CheckboxType::class;
        }
    }
    

    Then simply use it where required, e.g.:

    $builder->add('test_a1', ThemedCheckboxType::class, [
        'theme' => 'theme-a', // or others
    ]);
    

    This way, you you can inject your own form theme, to directly manipulate the checkboxes when required, e.g.

    {% block theme_a_checkbox_widget %}
        <em>Theme-A</em>
        {{ block('checkbox_widget') }}
    {% endblock %}
    
    {% block theme_b_checkbox_widget %}
        <em>Theme-B</em>
        {{ block('checkbox_widget') }}
    {% endblock %}
    

    If you don't want to use the extra form type, the same could be achieved using a custom form extension to add options to the default checkbox type. See: https://symfony.com/doc/current/form/create_form_type_extension.html

    For example:

    <?php
    declare(strict_types=1);
    
    namespace App\Form\Extension;
    
    use Symfony\Component\Form\AbstractTypeExtension;
    use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
    use Symfony\Component\OptionsResolver\Options;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    class CheckboxTypeExtension extends AbstractTypeExtension
    {
        /**
         * @inheritDoc
         */
        public static function getExtendedTypes(): iterable
        {
            return [CheckboxType::class];
        }
    
        /**
         * @inheritDoc
         */
        public function configureOptions(OptionsResolver $resolver): void
        {
            $resolver
                ->setDefaults([
                    'theme' => 'default',
                    'block_prefix' => static function (Options $options): ?string {
                        return [
                            'default' => null,
                            'theme-a' => 'theme_a_checkbox',
                            'theme-b' => 'theme_b_checkbox',
                        ][$options['theme']];
                    },
                ])
                ->setAllowedValues('theme', ['default', 'theme-a', 'theme-b']);
        }
    }
    

    which allows for:

    $builder->add('test_a1', CheckboxType::class, [
        'theme' => 'theme-a',
    ]);