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
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
You need to add
->atPath()
when creating the violation in yourValidator
class:It’s documented at https://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator