Search code examples
symfonysymfony-forms

How to build and reuse a Symfony form?


I am using Symfony 6.4, and I'm not sure how to factorize the code in my forms.

Here's a specific example:

In UserRegistrationType, I do:

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder
        ->add('email', TextType::class, [
            'label' => 'Email',
            'attr' => [
                'placeholder' => 'Email'
            ],
        ])
        ->add('plainPassword', RepeatedType::class, [
            'type' => PasswordType::class,
            'invalid_message' => 'Les mots de passe ne correspondent pas.',
            'options' => [
                'attr' => [
                    'autocomplete' => 'new-password',
                ],
            ],
            'required' => true,
            'first_options' =>
                [
                    'label' => 'Mot de passe',
                    'attr' =>
                        [
                            'placeholder' => 'Mot de passe',
                            'class' => 'verify-password'
                        ]
                ],
            'second_options' => ['label' => 'Répéter le mot de passe', 'attr' => ['placeholder' => 'Répéter le mot de passe']],

        ])
        ->add('nom', TextType::class, [
            'label' => 'Nom',
            'attr' => [
                'placeholder' => 'Nom',
            ],
        ])
        ->add('prenom', TextType::class, [
            'label' => 'Prénom',
            'attr' => [
                'placeholder' => 'Prénom',
            ],
        ])
}

In one part of the site, I only need to be able to modify the user's password. Not the email, not the name, not the first name, just the password.

So, I use a UserPasswordType:

public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('plainPassword', RepeatedType::class, [
                'type' => PasswordType::class,
                'invalid_message' => 'Les mots de passe ne correspondent pas.',
                'options' => [
                    'attr' => [
                        'autocomplete' => 'new-password',
                    ],
                ],
                'required' => true,
                'first_options' =>
                    [
                        'label' => 'Mot de passe',
                        'attr' =>
                            [
                                'placeholder' => 'Mot de passe',
                                'class' => 'verify-password'
                            ]
                    ],
                'second_options' => ['label' => 'Répéter le mot de passe', 'attr' => ['placeholder' => 'Répéter le mot de passe']],
            ]);
    }

But I can see that I'm copying and pasting the same logic twice.

Is there a solution to write the logic only once and use it in each FormType where I'll need it?


Solution

  • Yes you can. There are a few methods to do this. Pick what works for you.

    Method 1. Create your own form type and configure the defaults.

    https://symfony.com/doc/current/form/create_custom_field_type.html

    This is the preferred solution because you can still override the defaults the way symfony meant to. For example if you want to change the invalid_message in different forms.

    class MyRepeatedPasswordType extends AbstractType
    {
        public function configureOptions(OptionsResolver $resolver): void
        {
            $resolver->setDefaults([
                'type' => PasswordType::class,
                'invalid_message' => 'Les mots de passe ne correspondent pas.',
                'options' => [
                    'attr' => [
                        'autocomplete' => 'new-password',
                    ],
                ],
                'required' => true,
                'first_options' =>
                    [
                        'label' => 'Mot de passe',
                        'attr' =>
                            [
                                'placeholder' => 'Mot de passe',
                                'class' => 'verify-password'
                            ]
                    ],
                'second_options' => ['label' => 'Répéter le mot de passe', 'attr' => ['placeholder' => 'Répéter le mot de passe']],
            ]);
        }
    
        public function getParent(): string
        {
            return RepeatedType::class;
        }
    }
    
    

    You can add it as follows to your Forms:

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('password', MyRepeatedPasswordType::class);
    }
    

    Method 2: Factory pattern

    This might be the simplest method, but maybe not preferable.

    You can create a factory class that you can call to deduplicate code.

    class MyFormBuilderFactory
    {
        public static function passwordField(FormBuilderInterface $builder): void
        {
            $builder
                ->add('plainPassword', RepeatedType::class, [
                    'type' => PasswordType::class,
                    'invalid_message' => 'Les mots de passe ne correspondent pas.',
                    'options' => [
                        'attr' => [
                            'autocomplete' => 'new-password',
                        ],
                    ],
                    'required' => true,
                    'first_options' =>
                        [
                            'label' => 'Mot de passe',
                            'attr' =>
                                [
                                    'placeholder' => 'Mot de passe',
                                    'class' => 'verify-password'
                                ]
                        ],
                    'second_options' => ['label' => 'Répéter le mot de passe', 'attr' => ['placeholder' => 'Répéter le mot de passe']],
                ]);
        }
    }
    

    You can then use the factory in your Forms.

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('email', TextType::class);
    
        MyFormBuilderFactory::passwordField($builder);
        
        $builder
            ->add('other_field', ...);
    }
    

    There are probably more methods but these I have come across.