How can I validate array keys using Symfony Validation?

php, symfony, validation

Solution

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);

Problem

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 => 'david@panmedia.co.nz', 12 => 'some@email.add', ], '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)

Original source