Procházet zdrojové kódy

[Validator][Form] Removed support for match-all group "*"

The constraint "Valid" does not accept any options or groups anymore. As per
JSR303 1.0 final, section 3.5.1 "Object graph validation" (page 39),
properties  annotated with valid should be cascaded independent of the current
group (i.e. always). Thus the group "*" is not necessary anymore and was
removed from the "Valid" constraint in the Form validation.xml.
Bernhard Schussek před 14 roky
rodič
revize
6a148465da

+ 1 - 3
src/Symfony/Component/Form/Resources/config/validation.xml

@@ -17,9 +17,7 @@
 
   <class name="Symfony\Component\Form\Form">
     <getter property="data">
-      <constraint name="Valid">
-        <option name="groups">*</option>
-      </constraint>
+      <constraint name="Valid" />
     </getter>
     <getter property="postMaxSizeReached">
       <constraint name="AssertFalse">

+ 1 - 1
src/Symfony/Component/Validator/Constraint.php

@@ -30,7 +30,7 @@ abstract class Constraint
 {
     const DEFAULT_GROUP = 'Default';
 
-    public $groups = self::DEFAULT_GROUP;
+    public $groups = array(self::DEFAULT_GROUP);
 
     /**
      * Initializes the constraint with options.

+ 15 - 2
src/Symfony/Component/Validator/Constraints/Valid.php

@@ -11,8 +11,21 @@ namespace Symfony\Component\Validator\Constraints;
  * with this source code in the file LICENSE.
  */
 
+use Symfony\Component\Validator\Exception\InvalidOptionsException;
+
 class Valid extends \Symfony\Component\Validator\Constraint
 {
-    public $message = 'This value should be instance of class {{ class }}';
-    public $class;
+    /**
+     * This constraint does not accept any options
+     *
+     * @param  mixed $options           Unsupported argument!
+     *
+     * @throws InvalidOptionsException  When the parameter $options is not NULL
+     */
+    public function __construct($options = null)
+    {
+        if (null !== $options) {
+            throw new InvalidOptionsException('The constraint Valid does not accept any options');
+        }
+    }
 }

+ 0 - 49
src/Symfony/Component/Validator/Constraints/ValidValidator.php

@@ -1,49 +0,0 @@
-<?php
-
-namespace Symfony\Component\Validator\Constraints;
-
-/*
- * This file is part of the Symfony framework.
- *
- * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
-use Symfony\Component\Validator\Constraint;
-use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
-use Symfony\Component\Validator\Exception\UnexpectedTypeException;
-
-class ValidValidator extends ConstraintValidator
-{
-    public function isValid($value, Constraint $constraint)
-    {
-        if ($value === null) {
-            return true;
-        }
-
-        $walker = $this->context->getGraphWalker();
-        $group = $this->context->getGroup();
-        $propertyPath = $this->context->getPropertyPath();
-        $factory = $this->context->getClassMetadataFactory();
-
-        if (is_array($value)) {
-            foreach ($value as $key => $element) {
-                $walker->walkConstraint($constraint, $element, $group, $propertyPath.'['.$key.']');
-            }
-        } else if (!is_object($value)) {
-            throw new UnexpectedTypeException($value, 'object or array');
-        } else if ($constraint->class && !$value instanceof $constraint->class) {
-            $this->setMessage($constraint->message, array('{{ class }}' => $constraint->class));
-
-            return false;
-        } else {
-            $metadata = $factory->getClassMetadata(get_class($value));
-            $walker->walkClass($metadata, $value, $group, $propertyPath);
-        }
-
-        return true;
-    }
-}

+ 20 - 0
src/Symfony/Component/Validator/GraphWalker.php

@@ -76,6 +76,26 @@ class GraphWalker
         foreach ($metadata->findConstraints($group) as $constraint) {
             $this->walkConstraint($constraint, $value, $group, $propertyPath);
         }
+
+        if ($metadata->isCascaded()) {
+            $this->walkReference($value, $group, $propertyPath);
+        }
+    }
+
+    protected function walkReference($value, $group, $propertyPath)
+    {
+        if (null !== $value) {
+            if (is_array($value)) {
+                foreach ($value as $key => $element) {
+                    $this->walkReference($element, $group, $propertyPath.'['.$key.']');
+                }
+            } else if (!is_object($value)) {
+                throw new UnexpectedTypeException($value, 'object or array');
+            } else {
+                $metadata = $this->metadataFactory->getClassMetadata(get_class($value));
+                $this->walkClass($metadata, $value, $group, $propertyPath);
+            }
+        }
     }
 
     public function walkConstraint(Constraint $constraint, $value, $group, $propertyPath)

+ 6 - 1
src/Symfony/Component/Validator/Mapping/ClassMetadata.php

@@ -12,7 +12,8 @@ namespace Symfony\Component\Validator\Mapping;
  */
 
 use Symfony\Component\Validator\Constraint;
-use Symfony\Component\Validator\Exception\ValidatorException;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
 
 class ClassMetadata extends ElementMetadata
 {
@@ -77,6 +78,10 @@ class ClassMetadata extends ElementMetadata
      */
     public function addConstraint(Constraint $constraint)
     {
+        if ($constraint instanceof Valid) {
+            throw new ConstraintDefinitionException('The constraint Valid can only be put on properties or getters');
+        }
+
         $constraint->addImplicitGroupName($this->getShortClassName());
 
         parent::addConstraint($constraint);

+ 1 - 7
src/Symfony/Component/Validator/Mapping/ElementMetadata.php

@@ -95,14 +95,8 @@ abstract class ElementMetadata
      */
     public function findConstraints($group)
     {
-        $globalConstraints  = isset($this->constraintsByGroup['*'])
-                ? $this->constraintsByGroup['*']
-                : array();
-
-        $groupConstraints   = isset($this->constraintsByGroup[$group])
+        return isset($this->constraintsByGroup[$group])
                 ? $this->constraintsByGroup[$group]
                 : array();
-
-        return array_merge((array) $globalConstraints, (array) $groupConstraints);
     }
 }

+ 29 - 1
src/Symfony/Component/Validator/Mapping/MemberMetadata.php

@@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Mapping;
  * with this source code in the file LICENSE.
  */
 
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\Valid;
 use Symfony\Component\Validator\Exception\ValidatorException;
 
 abstract class MemberMetadata extends ElementMetadata
@@ -18,6 +20,7 @@ abstract class MemberMetadata extends ElementMetadata
     public $class;
     public $name;
     public $property;
+    public $cascaded = false;
     private $reflMember;
 
     /**
@@ -34,6 +37,20 @@ abstract class MemberMetadata extends ElementMetadata
         $this->property = $property;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function addConstraint(Constraint $constraint)
+    {
+        if ($constraint instanceof Valid) {
+            $this->cascaded = true;
+        } else {
+            parent::addConstraint($constraint);
+        }
+
+        return $this;
+    }
+
     /**
      * Returns the names of the properties that should be serialized
      *
@@ -44,7 +61,8 @@ abstract class MemberMetadata extends ElementMetadata
         return array_merge(parent::__sleep(), array(
             'class',
             'name',
-            'property'
+            'property',
+            'cascaded', // TESTME
         ));
     }
 
@@ -108,6 +126,16 @@ abstract class MemberMetadata extends ElementMetadata
         return $this->getReflectionMember()->isPrivate();
     }
 
+    /**
+     * Returns whether objects stored in this member should be validated
+     *
+     * @return boolean
+     */
+    public function isCascaded()
+    {
+        return $this->cascaded;
+    }
+
     /**
      * Returns the value of this property in the given object
      *

+ 0 - 105
tests/Symfony/Tests/Component/Validator/Constraints/ValidValidatorTest.php

@@ -1,105 +0,0 @@
-<?php
-
-namespace Symfony\Tests\Component\Validator;
-
-require_once __DIR__.'/../Fixtures/Entity.php';
-
-use Symfony\Component\Validator\ValidationContext;
-use Symfony\Component\Validator\Constraints\Valid;
-use Symfony\Component\Validator\Constraints\ValidValidator;
-use Symfony\Tests\Component\Validator\Fixtures\Entity;
-
-class ValidValidatorTest extends \PHPUnit_Framework_TestCase
-{
-    const CLASSNAME = 'Symfony\Tests\Component\Validator\Fixtures\Entity';
-
-    protected $validator;
-    protected $factory;
-    protected $walker;
-    protected $context;
-
-    public function setUp()
-    {
-        $this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false);
-        $this->factory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
-
-        $this->context = new ValidationContext('Root', $this->walker, $this->factory);
-
-        $this->validator = new ValidValidator();
-        $this->validator->initialize($this->context);
-    }
-
-    public function testNullIsValid()
-    {
-        $this->assertTrue($this->validator->isValid(null, new Valid()));
-    }
-
-    public function testThrowsExceptionIfNotObjectOrArray()
-    {
-        $this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
-
-        $this->validator->isValid('foobar', new Valid());
-    }
-
-    public function testWalkObject()
-    {
-        $this->context->setGroup('MyGroup');
-        $this->context->setPropertyPath('foo');
-
-        $metadata = $this->createClassMetadata();
-        $entity = new Entity();
-
-        $this->factory->expects($this->once())
-                                    ->method('getClassMetadata')
-                                    ->with($this->equalTo(self::CLASSNAME))
-                                    ->will($this->returnValue($metadata));
-
-        $this->walker->expects($this->once())
-                                 ->method('walkClass')
-                                 ->with($this->equalTo($metadata), $this->equalTo($entity), 'MyGroup', 'foo');
-
-        $this->assertTrue($this->validator->isValid($entity, new Valid()));
-    }
-
-    public function testWalkArray()
-    {
-        $this->context->setGroup('MyGroup');
-        $this->context->setPropertyPath('foo');
-
-        $constraint = new Valid();
-        $entity = new Entity();
-        // can only test for one object due to PHPUnit's mocking limitations
-        $array = array('key' => $entity);
-
-        $this->walker->expects($this->once())
-                                 ->method('walkConstraint')
-                                 ->with($this->equalTo($constraint), $this->equalTo($entity), 'MyGroup', 'foo[key]');
-
-        $this->assertTrue($this->validator->isValid($array, $constraint));
-    }
-
-    public function testValidateClass_Succeeds()
-    {
-        $metadata = $this->createClassMetadata();
-        $entity = new Entity();
-
-        $this->factory->expects($this->any())
-                                    ->method('getClassMetadata')
-                                    ->with($this->equalTo(self::CLASSNAME))
-                                    ->will($this->returnValue($metadata));
-
-        $this->assertTrue($this->validator->isValid($entity, new Valid(array('class' => self::CLASSNAME))));
-    }
-
-    public function testValidateClass_Fails()
-    {
-        $entity = new \stdClass();
-
-        $this->assertFalse($this->validator->isValid($entity, new Valid(array('class' => self::CLASSNAME))));
-    }
-
-    protected function createClassMetadata()
-    {
-        return $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadata', array(), array(), '', false);
-    }
-}

+ 1 - 0
tests/Symfony/Tests/Component/Validator/Fixtures/Entity.php

@@ -26,6 +26,7 @@ class Entity extends EntityParent implements EntityInterface
      */
     protected $firstName;
     protected $lastName;
+    protected $reference;
 
     private $internal;
 

+ 9 - 0
tests/Symfony/Tests/Component/Validator/Fixtures/FailingConstraint.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace Symfony\Tests\Component\Validator\Fixtures;
+
+use Symfony\Component\Validator\Constraint;
+
+class FailingConstraint extends Constraint
+{
+}

+ 14 - 0
tests/Symfony/Tests/Component/Validator/Fixtures/FailingConstraintValidator.php

@@ -0,0 +1,14 @@
+<?php
+
+namespace Symfony\Tests\Component\Validator\Fixtures;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+class FailingConstraintValidator extends ConstraintValidator
+{
+    public function isValid($value, Constraint $constraint)
+    {
+        return false;
+    }
+}

+ 25 - 0
tests/Symfony/Tests/Component/Validator/Fixtures/FakeClassMetadataFactory.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Symfony\Tests\Component\Validator\Fixtures;
+
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
+
+class FakeClassMetadataFactory implements ClassMetadataFactoryInterface
+{
+    protected $metadatas = array();
+
+    public function getClassMetadata($class)
+    {
+        if (!isset($this->metadatas[$class])) {
+            throw new \RuntimeException('No metadata for class ' . $class);
+        }
+
+        return $this->metadatas[$class];
+    }
+
+    public function addClassMetadata(ClassMetadata $metadata)
+    {
+        $this->metadatas[$metadata->getClassName()] = $metadata;
+    }
+}

+ 101 - 4
tests/Symfony/Tests/Component/Validator/GraphWalkerTest.php

@@ -5,17 +5,19 @@ namespace Symfony\Tests\Component\Validator;
 require_once __DIR__.'/Fixtures/Entity.php';
 require_once __DIR__.'/Fixtures/ConstraintA.php';
 require_once __DIR__.'/Fixtures/ConstraintAValidator.php';
+require_once __DIR__.'/Fixtures/FailingConstraint.php';
+require_once __DIR__.'/Fixtures/FailingConstraintValidator.php';
+require_once __DIR__.'/Fixtures/FakeClassMetadataFactory.php';
 
 use Symfony\Tests\Component\Validator\Fixtures\Entity;
+use Symfony\Tests\Component\Validator\Fixtures\FakeClassMetadataFactory;
 use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
+use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint;
 use Symfony\Component\Validator\GraphWalker;
 use Symfony\Component\Validator\ConstraintViolation;
 use Symfony\Component\Validator\ConstraintViolationList;
 use Symfony\Component\Validator\ConstraintValidatorFactory;
 use Symfony\Component\Validator\Mapping\ClassMetadata;
-use Symfony\Component\Validator\Mapping\PropertyMetadata;
-use Symfony\Component\Validator\Constraints\All;
-use Symfony\Component\Validator\Constraints\Any;
 use Symfony\Component\Validator\Constraints\Valid;
 
 class GraphWalkerTest extends \PHPUnit_Framework_TestCase
@@ -27,7 +29,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
 
     public function setUp()
     {
-        $this->factory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
+        $this->factory = new FakeClassMetadataFactory();
         $this->walker = new GraphWalker('Root', $this->factory, new ConstraintValidatorFactory());
         $this->metadata = new ClassMetadata(self::CLASSNAME);
     }
@@ -68,6 +70,101 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals(1, count($this->walker->getViolations()));
     }
 
+    public function testWalkCascadedPropertyValidatesReferences()
+    {
+        $entity = new Entity();
+        $entityMetadata = new ClassMetadata(get_class($entity));
+        $this->factory->addClassMetadata($entityMetadata);
+
+        // add a constraint for the entity that always fails
+        $entityMetadata->addConstraint(new FailingConstraint());
+
+        // validate entity when validating the property "reference"
+        $this->metadata->addPropertyConstraint('reference', new Valid());
+
+        // invoke validation on an object
+        $this->walker->walkPropertyValue(
+            $this->metadata,
+            'reference',
+            $entity,  // object!
+            'Default',
+            'path'
+        );
+
+        $violations = new ConstraintViolationList();
+        $violations->add(new ConstraintViolation(
+            '',
+            array(),
+            'Root',
+            'path',
+            $entity
+        ));
+
+        $this->assertEquals($violations, $this->walker->getViolations());
+    }
+
+    public function testWalkCascadedPropertyValidatesArrays()
+    {
+        $entity = new Entity();
+        $entityMetadata = new ClassMetadata(get_class($entity));
+        $this->factory->addClassMetadata($entityMetadata);
+
+        // add a constraint for the entity that always fails
+        $entityMetadata->addConstraint(new FailingConstraint());
+
+        // validate array when validating the property "reference"
+        $this->metadata->addPropertyConstraint('reference', new Valid());
+
+        $this->walker->walkPropertyValue(
+            $this->metadata,
+            'reference',
+            array('key' => $entity), // array!
+            'Default',
+            'path'
+        );
+
+        $violations = new ConstraintViolationList();
+        $violations->add(new ConstraintViolation(
+            '',
+            array(),
+            'Root',
+            'path[key]',
+            $entity
+        ));
+
+        $this->assertEquals($violations, $this->walker->getViolations());
+    }
+
+    public function testWalkCascadedPropertyDoesNotValidateNullValues()
+    {
+        $this->metadata->addPropertyConstraint('reference', new Valid());
+
+        $this->walker->walkPropertyValue(
+            $this->metadata,
+            'reference',
+            null,
+            'Default',
+            ''
+        );
+
+        $this->assertEquals(0, count($this->walker->getViolations()));
+    }
+
+    public function testWalkCascadedPropertyRequiresObjectOrArray()
+    {
+        $this->metadata->addPropertyConstraint('reference', new Valid());
+
+        $this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
+
+        $this->walker->walkPropertyValue(
+            $this->metadata,
+            'reference',
+            'no object',
+            'Default',
+            ''
+        );
+    }
+
     public function testWalkConstraintBuildsAViolationIfFailed()
     {
         $constraint = new ConstraintA();

+ 8 - 0
tests/Symfony/Tests/Component/Validator/Mapping/ClassMetadataTest.php

@@ -9,6 +9,7 @@ require_once __DIR__.'/../Fixtures/ConstraintB.php';
 use Symfony\Tests\Component\Validator\Fixtures\Entity;
 use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
 use Symfony\Tests\Component\Validator\Fixtures\ConstraintB;
+use Symfony\Component\Validator\Constraints\Valid;
 use Symfony\Component\Validator\Mapping\ClassMetadata;
 use Symfony\Component\Validator\Mapping\PropertyMetadata;
 
@@ -24,6 +25,13 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
         $this->metadata = new ClassMetadata(self::CLASSNAME);
     }
 
+    public function testAddConstraintDoesNotAcceptValid()
+    {
+        $this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
+
+        $this->metadata->addConstraint(new Valid());
+    }
+
     public function testAddPropertyConstraints()
     {
         $this->metadata->addPropertyConstraint('firstName', new ConstraintA());

+ 19 - 0
tests/Symfony/Tests/Component/Validator/Mapping/MemberMetadataTest.php

@@ -7,6 +7,7 @@ require_once __DIR__.'/../Fixtures/ConstraintB.php';
 
 use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
 use Symfony\Tests\Component\Validator\Fixtures\ConstraintB;
+use Symfony\Component\Validator\Constraints\Valid;
 use Symfony\Component\Validator\Mapping\MemberMetadata;
 
 class MemberMetadataTest extends \PHPUnit_Framework_TestCase
@@ -22,6 +23,24 @@ class MemberMetadataTest extends \PHPUnit_Framework_TestCase
         );
     }
 
+    public function testAddValidSetsMemberToCascaded()
+    {
+        $result = $this->metadata->addConstraint(new Valid());
+
+        $this->assertEquals(array(), $this->metadata->getConstraints());
+        $this->assertEquals($result, $this->metadata);
+        $this->assertTrue($this->metadata->isCascaded());
+    }
+
+    public function testAddOtherConstraintDoesNotSetMemberToCascaded()
+    {
+        $result = $this->metadata->addConstraint($constraint = new ConstraintA());
+
+        $this->assertEquals(array($constraint), $this->metadata->getConstraints());
+        $this->assertEquals($result, $this->metadata);
+        $this->assertFalse($this->metadata->isCascaded());
+    }
+
     public function testSerialize()
     {
         $this->metadata->addConstraint(new ConstraintA(array('property1' => 'A')));