4
votes

I have the following code:


    class User
    {
        /**
         * @Assert\Type(type="string")
         * @Assert\NotBlank()
         * @Assert\Email()
         * @Assert\Length(max=255)
         */
        public $email;
    }

This object is filled from an API call. When validation takes place and property is filled with array value instead of string then NotBlank, Email, and Length validations continuing to work and I get "UnexpectedTypeException".

I want validation system just to add one error about wrong value type and stop there.

I've made custom constraint validator


    class ChainConstraintValidator extends ConstraintValidator
    {
        /**
         * {@inheritdoc}
         */
        public function validate($value, Constraint $constraint)
        {
            if (!$constraint instanceof ChainConstraint) {
                throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\All');
            }

            $context = $this->context;
            $prevCount = $context->getViolations()->count();
            $validator = $context->getValidator()->inContext($context);

            foreach ($constraint->constraints as $constraintStep) {
                $errors = $validator->validate($value, $constraintStep);

                if ($errors->getViolations()->count() > $prevCount) {
                    break;
                }
            }
        }
    }

It works and I used it like this:


    @ChainConstraint(
        @Assert\Type(type="string"),
        @Assert\NotBlank(),
        @Assert\Email(),
        @Assert\Length(max=255)
    )
    

I have a lot of such classes in my project. Is there any more beautiful and requiring less code a way to achieve this?

3
I think this happens because you have type hints in your setters.Iwan Wijaya
This class has no setters only one public field.Sergey

3 Answers

2
votes

What about this :

$var = [];

$constraints = [
 new Type(['type'=>'string', 'groups'=> 'first-check']),
 new NotBlank(['groups'=> 'second-check']),
 new Email(['groups'=> 'second-check']),
 new Length(['min'=>2, 'groups'=> 'second-check'])]
    ;

$groups = ['first-check', 'second-check'];
$groupSequence = new GroupSequence($groups);
$error = $validator->validate($var, $constraints, $groupSequence);

if(count($error) > 0) {
    dd($error); // Contains 1 ConstraintViolation (Type) 
 }
2
votes

Starting from Symfony 5.1 a Sequentially constraint is available.

This constraint allows you to apply a set of rules that should be validated step-by-step, allowing to interrupt the validation once the first violation is raised.

1
votes

You may found some information in these issues which seem to be similar to the behavior you want to have:

As described in these issues I think the best way to do it properly is to use group validation even if it seems to be a bit clumsy to do it on every assertion.