Search code examples
phpsymfonyformbuilder

Apply constraint if conditions match


I have a Symfony form. In this form, I have two fields:

"House number" and "Po Box". They're defined like this:

$builder->add('houseNumber', TextType::class, [
    'label' => 'Huisnummer',
    'attr' => [
        'maxlength' => 8,
    ],
    'constraints' => [
        new NotBlank([
            'groups' => $options['constraint_groups']
        ]),
        new Regex([
            'pattern' => '/^[0-9]+$/',
            'message' => 'Vul alleen het huisnummer in cijfers in.'
        ]),
        new Length([
            'groups' => $options['constraint_groups'],
            'max' => 8
        ])
    ]
])->add('poBox', TextType::class, [
    'label' => 'Postbus',
    'attr' => [
        'maxlength' => 10,
    ],
    'constraints' => [
        new Length([
            'groups' => $options['constraint_groups'],
            'max' => 10
        ])
    ]
]);

Is there a way for me to make it so that the Housenumber not required if the PoBox is present and vice versa?

Thanks.


Solution

  • You can create a custom validation constraint

    namespace App\Validator\Constraints;
    
    use Symfony\Component\Validator\Constraint;
    
    /**
     * @Annotation
     * @Target({"CLASS", "ANNOTATION"})
     */
    class HouseNumber extends Constraint
    {
        public $message = 'Some message';
    
        public function getTargets()
        {
            return self::CLASS_CONSTRAINT;
        }
    }
    

    Validator

    namespace App\Validator\Constraints;
    
    use Symfony\Component\Form\Exception\UnexpectedTypeException;
    use Symfony\Component\Validator\Constraint;
    use Symfony\Component\Validator\ConstraintValidator;
    
    class HouseNumberValidator extends ConstraintValidator
    {
        public function validate($obj, Constraint $constraint)
        {
            if (!$constraint instanceof HouseNUmber) {
                throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\HouseNUmber');
            }
    
            $error = null;
    
            if (!$this->isValid($obj)) {
                $error = $constraint->message;
            }
    
            if (!empty($error)) {
                $this->context->buildViolation($error)
                    ->atPath('houseNumber')
                    ->addViolation();
            }
        }
    
        private function isValid($obj)
        {    
            if (!$obj instanceof SomeClass) {
                throw new UnexpectedTypeException($obj, SomeClass::class);
            }
    
            if (!empty($obj->getPoBox())) {
                return true;
            }
    
            return !empty($obj->getHouseNumber());
        }
    }
    

    And to your field $houseNumber in class add annotation

    use App\Validator\Constraints as AppAssert;
    
    /**
     * @AppAssert\HouseNumber
     */
    class SomeClass 
    {
        private $houseNumber;
        ...
    }