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

All I am trying to do is:

  • define constraints in yml

  • 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?


    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 FuzTestsBundleServicesArrayValidator($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 FuzTestsBundleServices;
    
    use SymfonyComponentValidatorValidatorInterface;
    use SymfonyComponentYamlParser;
    use SymfonyComponentValidatorMappingLoaderYamlFileLoader;
    
    /**
     * 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.


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

    use SymfonyComponentValidatorConstraints as Assert;
    
    ...
    ...
    
    $constraint = new AssertCollection(array(
        'Name' => new AssertMinLength(10),
        'author' => new AssertCollection(array(
            'first_name' => array(new AssertNotBlank(), new AssertMinLength(10)),
            'last_name'  => new AssertMinLength(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 AssertCollection(array(
            'name'       => new AssertNotBlank(array(
                'message' => 'Can't be null'
            )),
            'email'      => new AssertEmail(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.


    in your validation.yml:

    AcmeDemoBundleEntityAcmeEntity:
        properties:
            price:
                - NotBlank: ~
                - AcmeDemoBundleValidatorConstraintsContainsAlphanumeric: ~
    

    and your ContainsAlphanumeric:

    <?php
        namespace AcmeDemoBundleValidatorConstraints;
        use SymfonyComponentValidatorConstraint;
        use SymfonyComponentValidatorConstraintValidator;
    
        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));
                }
            }
        }
    ?>
    
    链接地址: http://www.djcxy.com/p/64004.html

    上一篇: MySQL的

    下一篇: Symonfy2验证:在yml中定义约束,并验证数组