Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symonfy2 validation: define constraints in yml, and validate an array

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?

like image 339
Mickey Shine Avatar asked Sep 21 '12 09:09

Mickey Shine


1 Answers

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.

like image 61
Alain Tiemblo Avatar answered Nov 03 '22 22:11

Alain Tiemblo