Search code examples
symfonysymfony5

Symfony5 Custom Validator in FormType - Validate password field when check box is on


I would like to validate a input password field in a form based in the value of checkbox. If the value is TRUE the password fields should be NOTBlank and Equal first to second. This what i have so far:

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;


class UsuarioFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
        ->add('nombre',null,[
            'required'   => true,
            'constraints'=> [
                new NotBlank([
                    'message' => 'Escriba el nombre por favor',
                ]),
            ]
        ])
        ->add('apellido',null,[
            'required'   => true,
            'constraints'=> [
                new NotBlank([
                    'message' => 'Escriba el apellido por favor',
                ]),
            ]
        ])
        ->add('user_name',null,[
            'required'   => true,
            'empty_data' => '',
            'constraints'=> [
                new NotBlank([
                    'message' => 'Escriba el nombre de usuario por favor',
                ]),
            ]
        ])
        ->add('active', CheckboxType::class, [
            'required' => false
        ])
        ->add('email',EmailType::class,[
            'constraints' => [
                new Email([
                    'mode'=> 'html5',
                    'message' => 'El correo electrónico {{ value }} no es un correo electrónico válido',
                ]),
            ]
        ])        
        ->add('plainPassword', RepeatedType::class, [
            'type' => PasswordType::class,
            'invalid_message' => 'Los campos de contraseña deben ser iguales',
            'mapped' => false,
            'required'   => false,
            'constraints' => [
                new Length([
                    'min' => 6,
                    'minMessage' => 'Your password should be at least {{ limit }} characters',
                    // max length allowed by Symfony for security reasons
                    'max' => 4096,
                ]),
            ],
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => User::class,
            'constraints' => [
                new Callback([$this, 'validate']),
            ],
        ]);
    }
    public function validate($data, ExecutionContextInterface $context): void
    {
        if ($data['chk_passUpdate']){
            if (trim($data['plainPassword']['first']) == '' ) {
                $context->buildViolation('El campo de contraseña no puede estar en blanco')
                ->atPath('plainPassword')
                ->addViolation();
            }
            if ($data['plainPassword']['first'] != $data['plainPassword']['second'] ) {
                $context->buildViolation('Los campos de contraseña deben ser iguales')
                ->atPath('plainPassword')
                ->addViolation();
            }
        }
    }
}

This code throw a Exception:

Argument 1 passed to App\Form\UsuarioFormType::validate() must be of the type array, object given, called in C:\Csi\CsiWorkspace\SynfonyTest\SymfonyTest\vendor\symfony\validator\Constraints\CallbackValidator.php on line 46


Solution

  • After a week i found the best solution is to use validation groups.

    So my code will be like this:

    namespace App\Form;
    
    use App\Entity\User;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Validator\Constraints\Email;
    use Symfony\Component\Validator\Constraints\NotBlank;
    use Symfony\Component\Validator\Constraints\Length;
    use Symfony\Component\Validator\Constraints\Callback;
    use Symfony\Component\Validator\Context\ExecutionContextInterface;
    use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
    use Symfony\Component\Form\Extension\Core\Type\PasswordType;
    use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
    use Symfony\Component\Form\Extension\Core\Type\EmailType;
    use Symfony\Component\Form\FormInterface;
    
    
    class UsuarioFormType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
            ->add('nombre',null,[
                'required'   => true,
                'constraints'=> [
                    new NotBlank([
                        'message' => 'Escriba el nombre por favor',
                    ]),
                ]
            ])
            ->add('apellido',null,[
                'required'   => true,
                'constraints'=> [
                    new NotBlank([
                        'message' => 'Escriba el apellido por favor',
                    ]),
                ]
            ])
            ->add('user_name',null,[
                'required'   => true,
                'empty_data' => '',
                'constraints'=> [
                    new NotBlank([
                        'message' => 'Escriba el nombre de usuario por favor',
                    ]),
                ]
            ])
            ->add('active', CheckboxType::class, [
                'required' => false
            ])
            ->add('password_update', CheckboxType::class, [
                'required'  => false,
                'mapped'    => false
            ])
            ->add('email',EmailType::class,[
                'constraints' => [
                    new Email([
                        'mode'=> 'html5',
                        'message' => 'El correo electrónico {{ value }} no es un correo electrónico válido',
                    ]),
                ]
            ])        
            ->add('plainPassword', RepeatedType::class, [
                'type' => PasswordType::class,
                'invalid_message' => 'Los campos de contraseña deben ser iguales',
                'mapped' => false,
                'required'   => false,
                'constraints' => [
                    new Length([
                        'min' => 6,
                        'minMessage' => 'Your password should be at least {{ limit }} characters',
                        // max length allowed by Symfony for security reasons
                        'max' => 4096,
                    ]),
                    new NotBlank([
                        'message' => 'Escriba su contraseña',
                        'groups' => 'password_update'
                    ]),
                ],
            ]);
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults([
                'data_class' => User::class,
                'validation_groups' => function(FormInterface $form){
                    if ($form['password_update']->getData()){
                        return array('Default', 'password_update');
                    }else{
                        return array('Default');
                    }
                }
    
            ]);
        }
    
    }
    

    In that way, when the checkbox is on, the form will validate 2 groups (default and password_udpate). If its off only the default group will be validated.

    Hope this help somebody some day.