user13859151
user13859151

Reputation: 323

Symfony5 custom validator - pass multiple submitted fields (no Entity)

We have a simple form:

namespace App\Form;

...

class SimpleForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('field_1', TextType::class, [                
                'required'    => true,
                'mapped'      => false,
                'constraints' => [                   
                    new NotBlank()
                ]
            ])
            ->add('field_2', TextType::class, [                
                'required'    => true,
                'mapped'      => false,
                'constraints' => [                   
                    new NotBlank()
                ]
            ]);
    }
    
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'constraints' => [
                new CustomCheck()
            ]
        ]);
    }
}

If my understanding is right, CustomCheck() can refer to a complex validation over the whole form data (for instance, to validate some combinations of inputs).

My next step is to create the App\Validator\CustomCheck and App\Validator\CustomCheckValidator classes, as per Symfony's manual.

However, I do not know how to pass the submitted field_1 and field_2 data to "new CustomCheck()". Or, how to access all submitted fields from within my custom validator.

I found it is possible if I were using an Entity (Class Constraint Validator, https://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator). But I want to know if it's possible without using an Entity.

Upvotes: 1

Views: 1423

Answers (1)

user13859151
user13859151

Reputation: 323

Okay, so my findings on the matter is that there is no programmatically way to access and pass the form unmapped fields data as arguments at the level of CustomCheck() within:

$resolver->setDefaults([
    'constraints' => [
        new CustomCheck()
    ]
]);

In my case, with no mapped Entity and no mapped fields, I found two ways to have a custom validator that can access any form field data:

  1. A custom in-form callback validator:
// custom callback validator
public function CustomCheck($data, ExecutionContextInterface $context){
    // $data doesn't contain the unmapped fields, so I need to extract the form data differently
    //var_dump($data['field_1']); // this works only for mapped fields (no Entity/DTO needed for this to work, only mapped fields is sufficient)
    
    $field1_data = $context->getRoot()->get('field_1')->getData(); // this works
    $field2_data = $context->getRoot()->get('field_2')->getData();
    
    if(...something_not_good...) {
        $context
            ->buildViolation('Custom error here')
            ->addViolation();
    }
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'constraints' => [
            new Callback([$this, 'CustomCheck'])
        ]
    ]);
}
  1. A custom validator where form data needs to be extracted with $this->context:
// form builder
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'constraints' => [
            new CustomCheck()
        ]
    ]);
}
// CustomCheck constraint
namespace App\Validator;

use Symfony\Component\Validator\Constraint;

class CustomCheck extends Constraint
{
    public string $message = 'Invalid blah blah.';
}
// CustomCheck validator
namespace App\Validator;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class CustomCheckValidator extends ConstraintValidator
{
    // $value will always be null, because nothing gets passed in the first argument to this custom validator (no mapped entity, no mapped fields)
    /**
     * @param mixed $value
     */
    public function validate($value, Constraint $constraint)
    {
        // extract unmapped form fields data manually
        $values = [
            'field_1' => $this->context->getRoot()->get('field_1')->getData(),
            'field_2' => $this->context->getRoot()->get('field_2')->getData()
        ];

        if(...something_not_good...) {
            $this->context->buildViolation('Custom error here')->addViolation();
        }
    }
}

Upvotes: 4

Related Questions