Today we ran in to an issue (or more like an unimplemented enhancement request), where we wanted to do conditional validation.
In our use case, we had a checkbox, which is basically an enable/disable switch. We also had three text inputs, parameters to the feature enabled by the checkbox.
In this case, we wanted the validation of the inputs to depend on whether the switch was enabled.
There are a few ways to do this. You could write your own validator specifically for this purpose. You could write a callback validator, that validates the object. Both these approaches have a big drawback. Even though what all we needed was a conditional NotBlank validator, who knows what we might wish to do in the future with it? We wanted to keep the flexibility of the validation component.
It turns out that’s entirely possible, albeit a bit tricky. To illustrate, let’s imagine a petition. Your form is for entering supporters for a petition. Let’s say that you optionally allow the supporter to enter which city they live in, and that you optionally show that city on a public petition page. Something like this POPO:
<?php
class PetitionSupporter
{
protected $name;
protected $city;
protected $is_city_public;
// Setters, getters etc. are omitted for brevity
}
Now, of course it wouldn’t be much of a petition if you didn’t require names of the supporters. As for the city, like we’ve said, it’s optional. However, it makes no sense to have the city public unless something is entered.
This can be accomplished using the callback validator, getting the graph walker from the execution context, and validating a separate validation group.
<?php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContext;
/**
* @Assert\Callback(methods={"validateCityExistance"})
*/
class PetitionSupporter
{
/** @Assert\NotBlank */
protected $name;
/** @Assert\NotBlank(groups="public_city_group") */
protected $city;
/** @Assert\Type(type="bool") */
protected $is_city_public;
public function validateCityExistance(ExecutionContext $ec)
{
if ($this->is_city_public)
{
$ec->getGraphWalker()->walkReference($this, 'public_city_group', $ec->getPropertyPath(), true);
}
}
// Setters, getters etc. are omitted for brevity
}
This way, the non-grouped assertions are always validated, and the assertions in the public_city_group group are validated if is_city_public is true.
Of course, you can have more complex validation rules, and you could also have multiple conditional groups depending on one or more of the instance variables.
— Developer blog
This has been very timely and helpful. I have very complicated validations that I need and I found this has worked, though does feel a little dirty. I’d love to see S2 tackle this out of the box.
Thank you! I came across the same problem in my project and your blog entry helped me a lot!