7

All I am trying to do is:

  1. define constraints in yml

  2. use this to validate an array

Say, a product array:

$product['name'] = 'A book';
$product['date'] = '2012-09';
$product['price'] = '21.5';

How to do that?

3 Answers 3

4

First of all, you need to know that Symfony2 validators are not ready to do that easily. It took me some time and some Symfony2 source reading to get a working solution for your case, and my solution is not that natural.

I made a class that takes the validator, your array, and your yaml configuration file so you'll be able to do what you expect. This class extends the YamlFileLoader from Symfony to access the protected parseNodes method : this is not beautiful but that's the only way I found to transform a custom Yaml configuration file into an array of Constraint object.

So here we are. I give you my code, you'll need to replace some namespaces according to your own context.

First, create a controller for our demo :

    public function indexAction()
    {

        // We create a sample validation file for the demo
        $demo = <<< EOT
name:
    - NotBlank: ~
    - MinLength: { limit: 3 }
    - MaxLength: { limit: 10 }
date:
    - NotBlank: ~
    - Regex: "/^[0-9]{4}\-[0-9]{2}$/"
price:
    - Min: 0

EOT;
        file_put_contents("/tmp/test.yml", $demo);

        // We create your array to validate
        $product = array ();
        $product['name'] = 'A book';
        $product['date'] = '2012-09';
        $product['price'] = '21.5';

        $validator = $this->get('validator');
        $service = new \Fuz\TestsBundle\Services\ArrayValidator($validator, $product, "/tmp/test.yml");
        $errors = $service->validate();

        echo '<pre>';
        var_dump($errors);
        die();

        return $this->render('FuzTestsBundle:Default:index.html.twig');
    }

Then create a class named ArrayValidator.php. Again, take care of the namespace.

<?php

namespace Fuz\TestsBundle\Services;

use Symfony\Component\Validator\ValidatorInterface;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;

/**
 * This class inherits from YamlFileLoader because we need to call the
 * parseNodes() protected method.
 */
class ArrayValidator extends YamlFileLoader
{

    /* the @validator service */
    private $validator;

    /* The array to check */
    private $array;

    /* The file that contains your validation rules */
    private $validationFile;

    public function __construct(ValidatorInterface $validator, array $array = array(), $validationFile)
    {
        $this->validator = $validator;
        $this->array = $array;
        $this->validationFile = $validationFile;
    }

    /* The method that does what you want */
    public function validate()
    {
        $yaml = file_get_contents($this->validationFile);

        // We parse the yaml validation file
        $parser = new Parser();
        $parsedYaml = $parser->parse($yaml);

        // We transform this validation array to a Constraint array
        $arrayConstraints = $this->parseNodes($parsedYaml);

        // For each elements of the array, we execute the validation
        $errors = array();
        foreach ($this->array as $key => $value)
        {
            $errors[$key] = array();

            // If the array key (eg: price) has validation rules, we check the value
            if (isset($arrayConstraints[$key]))
            {
                foreach ($arrayConstraints[$key] as $constraint)
                {
                    // If there is constraint violations, we list messages
                    $violationList = $this->validator->validateValue($value, $constraint);
                    if (count($violationList) > 0)
                    {
                        foreach ($violationList as $violation)
                        {
                            $errors[$key][] = $violation->getMessage();
                        }
                    }
                }
            }
        }

        return $errors;
    }

}

Finally, test it with different values in your $product array.

By default :

        $product = array ();
        $product['name'] = 'A book';
        $product['date'] = '2012-09';
        $product['price'] = '21.5';

Will display :

array(3) {
  ["name"]=>
  array(0) {
  }
  ["date"]=>
  array(0) {
  }
  ["price"]=>
  array(0) {
  }
}

If we change values to :

    $product = array ();
    $product['name'] = 'A very interesting book';
    $product['date'] = '2012-09-03';
    $product['price'] = '-21.5';

You'll get :

array(3) {
  ["name"]=>
  array(1) {
    [0]=>
    string(61) "This value is too long. It should have 10 characters or less."
  }
  ["date"]=>
  array(1) {
    [0]=>
    string(24) "This value is not valid."
  }
  ["price"]=>
  array(1) {
    [0]=>
    string(31) "This value should be 0 or more."
  }
}

Hope this helps.

Sign up to request clarification or add additional context in comments.

1 Comment

thank you, this is a great answer/explanation. God bless you;)
1

the way to validate an array is easy, I've learned it in silex documentation

use Symfony\Component\Validator\Constraints as Assert;

...
...

$constraint = new Assert\Collection(array(
    'Name' => new Assert\MinLength(10),
    'author' => new Assert\Collection(array(
        'first_name' => array(new Assert\NotBlank(), new Assert\MinLength(10)),
        'last_name'  => new Assert\MinLength(10),
    )),
));
$errors = $this->get('validator')->validateValue($book, $constraint);

or you can create directly forms with constraints

$form = $this->get('form.factory')->createBuilder('form',array(),array(
    'csrf_protection'       => false,
    'validation_constraint' => new Assert\Collection(array(
        'name'       => new Assert\NotBlank(array(
            'message' => 'Can\'t be null'
        )),
        'email'      => new Assert\Email(array(
            'message' => 'Invalid email'
        )),
    ))
))
->add('name', 'text')
->add('email', 'email')
->getForm();

}

this code can solve your second point but for the first point i suggest you to write a custom class that transforms your yaml definition to a valid constraints array with instantiated validation constraint objects or maybe that gives directly a form!

I don't know of a class ready to do this in symfony2.

I've done it in other projects that do not have a good data model, but in symfony you can create your models and define the validation associated with it.

2 Comments

Thank you but is that possible to define constraints in yml?
As you can see the constraints are a simple array of objects, you can define this array in a yaml file, then load the yaml file to array and instantiate the objects by their names. I'm sorry but you need to do it with a custom class/method that you need to create, I told you just how it should be:)
-1

in your validation.yml:

Acme\DemoBundle\Entity\AcmeEntity:
    properties:
        price:
            - NotBlank: ~
            - Acme\DemoBundle\Validator\Constraints\ContainsAlphanumeric: ~

and your ContainsAlphanumeric:

<?php
    namespace Acme\DemoBundle\Validator\Constraints;
    use Symfony\Component\Validator\Constraint;
    use Symfony\Component\Validator\ConstraintValidator;

    class ContainsAlphanumericValidator extends ConstraintValidator
    {
        public function validate($value, Constraint $constraint)
        {
            if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) {
                $this->context->addViolation($constraint->message, array('%string%' => $value));
            }
        }
    }
?>

1 Comment

This is for validating an entity object, not what I want

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.