skip to Main Content

I’m working on a custom captcha and I created a custom validator which is supposed to validate the captcha.

The problem is that when the captcha is not valid, the error seems to be attached to the parent form raver than the current field (or form because I’m using a custom form with 2 fields).

I tried to look in the documentation but I don’t see where my form is wrong …

// RegistrationType
class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options):void {
        $builder
            ->add('username', TextType::class, [
                'required' => false, 
                'attr' => [ 
                    'class' => 'login__input',
                ],
                'constraints' => [
                    new NotBlank(),
                ]
            ])
            ->add('password', PasswordType::class, [
              'required' => true,
              'attr' => [
                'class' => 'login__input',
              ]
            ])
            ->add('captcha', CaptchaType::class, [
                'mapped' => false,
                'route' => 'app_captcha',
                'error_mapping' => [
                    'captcha' => 'captcha'
                ],
                'constraints' => [ new Challenge()]
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
} 

As you can see, I tried to use error_mapping. I also use the challenge constraints multiple time in differents configurations to see if there’s a difference but nop

// CaptchaType
class CaptchaType extends AbstractType
{

    public function __construct(private readonly ChallengeInterface $challenge, private readonly UrlGeneratorInterface $urlGenerator) {}

    public function configureOptions(OptionsResolver $resolver) 
    {
        $resolver->setDefaults([
            'constraints' => [
                new Challenge()
            ], 
            'route' => 'app_captcha',
        ]);
        parent::configureOptions($resolver);
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('challenge', HiddenType::class, [
            'attr' => [
                'class' => 'captcha-challenge'
            ],
            'data' => $this->challenge->generateKey()
            ])
            ->add('answer', HiddenType::class, [
                'attr' => [
                    'class' => 'captcha-answer'
                ]
            ]
        );

        parent::buildForm($builder, $options);
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {

        $view->vars['attr'] = [
            'width' => PuzzleChallenge::WIDTH,
            'height' => PuzzleChallenge::HEIGHT,
            'piece-width' => PuzzleChallenge::PIECE_WIDTH,
            'piece-height' => PuzzleChallenge::PIECE_HEIGHT,
            'src' => $this->urlGenerator->generate($options['route'], [
                'challenge' => $form->get('challenge')->getData()
            ])
        ];
        
        parent::buildView($view, $form, $options);
    }
}

The validator :

class ChallengeValidator extends ConstraintValidator
{
    public function __construct(private readonly ChallengeInterface $challenge)
    {
        
    }

    /**
     * 
     * @params array{challenge: string, answer: string} $value
     */
    public function validate(mixed $value, Constraint $constraint): void
    {
        // dump($this->context);
        if (null === $value || '' === $value) {
            return;
        }

        if (!$this->challenge->verify($value['challenge'], $value['answer'])) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();
        }

    }
}

I’m using Symfony 7.1 and php 8.2

I tried several things. For exemple you can see here the error in the form parent with a dump :

Form parent with attributes and an error in the error attribute

I tried the constraints "NotBlank" from the username field to see if the error is in the form parent or the field. It is in the field :
Children fields with an error in the attribute error of the username

Here, you can also see the captcha children with his 2 fields and there’s no error
Captcha children from the registration form with no errors

2

Answers


  1. Chosen as BEST ANSWER

    I found out that I was not using error_bubbling correctly. As my CaptchaType is compound, error_bubbling is by default set to true.

    Default answer : Symfony Validation: Error message not showing at associated field

    public function configureOptions(OptionsResolver $resolver): void 
    {
        $resolver->setDefaults([
            'constraints' => [
                new Challenge()
            ], 
            'error_bubbling' => false,
            'route' => 'app_captcha',
        ]);
        parent::configureOptions($resolver);
    }
    

  2. You need to add ->atPath() when creating the violation in your Validator class:

    $this->context->buildViolation($constraint->message)
        ->addViolation()
        ->atPath('captcha') // name of the form field in your `RegistrationType`
    ;
    

    It’s documented at https://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search