Reputation: 46040
How can I validate array keys using Symfony Validation?
Say I have the following, and each key of the emails
array is an ID. How can I validate them using a callback, or some other constraint (say for example a regex constraint rather than a callback)?
$input = [
'emails' => [
7 => '[email protected]',
12 => '[email protected]',
],
'user' => 'bob',
'amount' => 7,
];
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints;
$validator = Validation::createValidator();
$constraint = new Constraints\Collection(array(
'emails' => new Constraints\All(array(
new Constraints\Email(),
)),
'user' => new Constraints\Regex('/[a-z]/i'),
'amount' => new Constraints\Range(['min' => 5, 'max' => 10]),
));
$violations = $validator->validateValue($input, $constraint);
echo $violations;
(using latest dev-master symfony)
Upvotes: 17
Views: 16679
Reputation: 245
A little bit late, but here you go, my friend:
use Symfony\Component\Validator\Constraints as Assert;
public function getConstraints()
{
return [
'employee' => [new Assert\NotBlank(), new Assert\Type("integer")],
'terms' => [new Assert\NotBlank(), new Assert\Type("array")],
'pivotData' => new Assert\All([
new Assert\Type("array"),
new Assert\Collection([
'amount' => new Assert\Optional(new Assert\Type('double'))
])
]),
];
}
A couple of things to notice here:
In the example provided above, we're validating the pivotData
key. The pivotData
should be an array of extra data we want to validate.
Each time we want to validate an array, we begin with new Assert\All
, meaning we want to validate everything in pivotData
Afterwards, we add new Assert\Type("array")
to check if an array really has been passed from the front end.
And, most importantly, we create a new Assert\Collection
where we define our new properties in one by one, as a standard. In the example above, I've added an amount
key that represents a pivotData property. You can freely list all your properties here, they will get validated :)
Good luck :)
Upvotes: -3
Reputation: 2118
There is a callback constraint. See http://symfony.com/doc/master/reference/constraints/Callback.html
Update:
I could not find a cleaner way to get the keys of the current value being validated. There probably is a better way, I did not spend too much time on this but it works for your case.
use Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
// ...
$input = array(
'emails' => array(
7 => '[email protected]',
12 => '[email protected]',
),
'user' => 'bob',
'amount' => 7,
);
// inside a sf2 controller: $validator = $this->get('validator.builder')->getValidator();
$validator = Validation::createValidator();
$constraint = new Constraints\Collection(array(
'emails' => new Constraints\All(array(
new Constraints\Email(),
new Constraints\Callback(array('methods' => array(function($value, ExecutionContextInterface $context){
$propertyPath = $context->getPropertyPath();
$valueKey = preg_replace('/[^0-9]/','',$propertyPath);
if($valueKey == 7){
$context->addViolationAt('email', sprintf('E-Mail %s Has Has Key 7',$value), array(), null);
}
})))
)),
'user' => new Constraints\Regex('/[a-z]/i'),
'amount' => new Constraints\Range(array('min' => 5, 'max' => 10)),
));
$violations = $validator->validate($input, $constraint);
echo $violations;
Upvotes: 3
Reputation: 1880
I would create a custom validation constraint which applies constraints on each key-value pair (or key only if you want to) in array. Similar to All
constraint, but validation is performed on key-value pair, not value only.
namespace GLS\DemoBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
class AssocAll extends Constraint
{
public $constraints = array();
public function __construct($options = null)
{
parent::__construct($options);
if (! is_array($this->constraints)) {
$this->constraints = array($this->constraints);
}
foreach ($this->constraints as $constraint) {
if (!$constraint instanceof Constraint) {
throw new ConstraintDefinitionException('The value ' . $constraint . ' is not an instance of Constraint in constraint ' . __CLASS__);
}
}
}
public function getDefaultOption()
{
return 'constraints';
}
public function getRequiredOptions()
{
return array('constraints');
}
}
Constraint validator, which passes an array with key-value pair to each constraint:
namespace GLS\DemooBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class AssocAllValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (null === $value) {
return;
}
if (!is_array($value) && !$value instanceof \Traversable) {
throw new UnexpectedTypeException($value, 'array or Traversable');
}
$walker = $this->context->getGraphWalker();
$group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
foreach ($value as $key => $element) {
foreach ($constraint->constraints as $constr) {
$walker->walkConstraint($constr, array($key, $element), $group, $propertyPath.'['.$key.']');
}
}
}
}
I guess, only Callback
constraint makes sense to be applied on each key-value pair, where you put your validation logic.
use GLS\DemoBundle\Validator\Constraints\AssocAll;
$validator = Validation::createValidator();
$constraint = new Constraints\Collection(array(
'emails' => new AssocAll(array(
new Constraints\Callback(array(
'methods' => array(function($item, ExecutionContext $context) {
$key = $item[0];
$value = $item[1];
//your validation logic goes here
//...
}
))),
)),
'user' => new Constraints\Regex('/^[a-z]+$/i'),
'amount' => new Constraints\Range(['min' => 5, 'max' => 10]),
));
$violations = $validator->validateValue($input, $constraint);
var_dump($violations);
Upvotes: 8
Reputation: 2970
You can use the php function array_flip in order to reverse the keys, values, and use a validator or a custom one.
Hope it's helpful.
Best regard.
Upvotes: -2