[Validator] Improved test coverage of NonRecursiveNodeTraverser
This commit is contained in:
parent
822fe4712f
commit
186c115894
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Exception;
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class UnsupportedMetadataException extends InvalidArgumentException
|
||||
{
|
||||
}
|
|
@ -64,6 +64,15 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface,
|
|||
*/
|
||||
public $groupSequenceProvider = false;
|
||||
|
||||
/**
|
||||
* The strategy for traversing traversable objects.
|
||||
*
|
||||
* By default, only instances of {@link \Traversable} are traversed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||
|
||||
/**
|
||||
* @var \ReflectionClass
|
||||
*/
|
||||
|
@ -215,6 +224,8 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface,
|
|||
$constraint->addImplicitGroupName($this->getDefaultGroup());
|
||||
|
||||
parent::addConstraint($constraint);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,10 +13,13 @@ namespace Symfony\Component\Validator\Mapping;
|
|||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
use Symfony\Component\Validator\Exception\BadMethodCallException;
|
||||
use Symfony\Component\Validator\ValidationVisitorInterface;
|
||||
|
||||
/**
|
||||
* @since %%NextVersion%%
|
||||
* A generic container of {@link Constraint} objects.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class GenericMetadata implements MetadataInterface
|
||||
|
@ -31,9 +34,27 @@ class GenericMetadata implements MetadataInterface
|
|||
*/
|
||||
public $constraintsByGroup = array();
|
||||
|
||||
/**
|
||||
* The strategy for cascading objects.
|
||||
*
|
||||
* By default, objects are not cascaded.
|
||||
*
|
||||
* @var integer
|
||||
*
|
||||
* @see CascadingStrategy
|
||||
*/
|
||||
public $cascadingStrategy = CascadingStrategy::NONE;
|
||||
|
||||
public $traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||
/**
|
||||
* The strategy for traversing traversable objects.
|
||||
*
|
||||
* By default, traversable objects are not traversed.
|
||||
*
|
||||
* @var integer
|
||||
*
|
||||
* @see TraversalStrategy
|
||||
*/
|
||||
public $traversalStrategy = TraversalStrategy::NONE;
|
||||
|
||||
/**
|
||||
* Returns the names of the properties that should be serialized.
|
||||
|
@ -66,11 +87,22 @@ class GenericMetadata implements MetadataInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a constraint to this element.
|
||||
* Adds a constraint.
|
||||
*
|
||||
* @param Constraint $constraint
|
||||
* If the constraint {@link Valid} is added, the cascading strategy will be
|
||||
* changed to {@link CascadingStrategy::CASCADE}. Depending on the
|
||||
* properties $traverse and $deep of that constraint, the traversal strategy
|
||||
* will be set to one of the following:
|
||||
*
|
||||
* @return ElementMetadata
|
||||
* - {@link TraversalStrategy::IMPLICIT} if $traverse is enabled and $deep
|
||||
* is enabled
|
||||
* - {@link TraversalStrategy::IMPLICIT} | {@link TraversalStrategy::STOP_RECURSION}
|
||||
* if $traverse is enabled, but $deep is disabled
|
||||
* - {@link TraversalStrategy::NONE} if $traverse is disabled
|
||||
*
|
||||
* @param Constraint $constraint The constraint to add
|
||||
*
|
||||
* @return GenericMetadata This object
|
||||
*/
|
||||
public function addConstraint(Constraint $constraint)
|
||||
{
|
||||
|
@ -100,17 +132,26 @@ class GenericMetadata implements MetadataInterface
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an list of constraints.
|
||||
*
|
||||
* @param Constraint[] $constraints The constraints to add
|
||||
*
|
||||
* @return GenericMetadata This object
|
||||
*/
|
||||
public function addConstraints(array $constraints)
|
||||
{
|
||||
foreach ($constraints as $constraint) {
|
||||
$this->addConstraint($constraint);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all constraints of this element.
|
||||
*
|
||||
* @return Constraint[] An array of Constraint instances
|
||||
* @return Constraint[] A list of Constraint instances
|
||||
*/
|
||||
public function getConstraints()
|
||||
{
|
||||
|
@ -132,30 +173,45 @@ class GenericMetadata implements MetadataInterface
|
|||
*
|
||||
* @param string $group The group name
|
||||
*
|
||||
* @return array An array with all Constraint instances belonging to the group
|
||||
* @return Constraint[] An list of all the Constraint instances belonging
|
||||
* to the group
|
||||
*/
|
||||
public function findConstraints($group)
|
||||
{
|
||||
return isset($this->constraintsByGroup[$group])
|
||||
? $this->constraintsByGroup[$group]
|
||||
: array();
|
||||
? $this->constraintsByGroup[$group]
|
||||
: array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCascadingStrategy()
|
||||
{
|
||||
return $this->cascadingStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTraversalStrategy()
|
||||
{
|
||||
return $this->traversalStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Exists for compatibility with the deprecated
|
||||
* {@link Symfony\Component\Validator\MetadataInterface}.
|
||||
*
|
||||
* Should not be used.
|
||||
*
|
||||
* @throws BadMethodCallException
|
||||
*
|
||||
* @deprecated Implemented for backwards compatibility with Symfony < 2.5.
|
||||
* Will be removed in Symfony 3.0.
|
||||
*/
|
||||
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath)
|
||||
{
|
||||
// Thanks PHP < 5.3.9
|
||||
throw new BadMethodCallException('Not supported.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,10 @@ namespace Symfony\Component\Validator\NodeTraverser;
|
|||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
||||
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
|
||||
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
|
@ -191,6 +193,9 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||
* @param \SplStack $nodeStack The stack for storing the
|
||||
* successor nodes
|
||||
*
|
||||
* @throws UnsupportedMetadataException If a property metadata does not
|
||||
* implement {@link PropertyMetadataInterface}
|
||||
*
|
||||
* @see ClassNode
|
||||
* @see PropertyNode
|
||||
* @see CollectionNode
|
||||
|
@ -215,6 +220,15 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||
|
||||
foreach ($node->metadata->getConstrainedProperties() as $propertyName) {
|
||||
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
||||
if (!$propertyMetadata instanceof PropertyMetadataInterface) {
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The property metadata instances should implement '.
|
||||
'"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$nodeStack->push(new PropertyNode(
|
||||
$node->value,
|
||||
$propertyMetadata->getPropertyValue($node->value),
|
||||
|
@ -424,24 +438,12 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||
return;
|
||||
}
|
||||
|
||||
// Traverse only if IMPLICIT or TRAVERSE
|
||||
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
||||
return;
|
||||
}
|
||||
// Currently, the traversal strategy can only be TRAVERSE for a
|
||||
// generic node if the cascading strategy is CASCADE. Thus, traversable
|
||||
// objects will always be handled within cascadeObject() and there's
|
||||
// nothing more to do here.
|
||||
|
||||
// If IMPLICIT, stop unless we deal with a Traversable
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$node->value instanceof \Traversable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If TRAVERSE, the constructor will fail if we have no Traversable
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
// see GenericMetadata::addConstraint()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -464,7 +466,9 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||
* and does not implement {@link \Traversable}
|
||||
* or if traversal is disabled via the
|
||||
* $traversalStrategy argument
|
||||
*
|
||||
* @throws UnsupportedMetadataException If the metadata returned by the
|
||||
* metadata factory does not implement
|
||||
* {@link ClassMetadataInterface}
|
||||
*/
|
||||
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, \SplStack $nodeStack)
|
||||
{
|
||||
|
@ -472,7 +476,12 @@ class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
|||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
// error
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The metadata factory should return instances of '.
|
||||
'"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$nodeStack->push(new ClassNode(
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Validator\ClassBasedInterface;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\MetadataInterface;
|
||||
use Symfony\Component\Validator\PropertyMetadataContainerInterface;
|
||||
|
||||
class FakeClassMetadata extends ClassMetadata
|
||||
{
|
||||
public function addPropertyMetadata($propertyName, $metadata)
|
||||
{
|
||||
if (!isset($this->members[$propertyName])) {
|
||||
$this->members[$propertyName] = array();
|
||||
}
|
||||
|
||||
$this->members[$propertyName][] = $metadata;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,10 @@ class FakeMetadataFactory implements MetadataFactoryInterface
|
|||
|
||||
public function getMetadataFor($class)
|
||||
{
|
||||
$hash = null;
|
||||
|
||||
if (is_object($class)) {
|
||||
$hash = spl_object_hash($class);
|
||||
$class = get_class($class);
|
||||
}
|
||||
|
||||
|
@ -31,6 +34,10 @@ class FakeMetadataFactory implements MetadataFactoryInterface
|
|||
}
|
||||
|
||||
if (!isset($this->metadatas[$class])) {
|
||||
if (isset($this->metadatas[$hash])) {
|
||||
return $this->metadatas[$hash];
|
||||
}
|
||||
|
||||
throw new NoSuchMetadataException(sprintf('No metadata for "%s"', $class));
|
||||
}
|
||||
|
||||
|
@ -39,24 +46,28 @@ class FakeMetadataFactory implements MetadataFactoryInterface
|
|||
|
||||
public function hasMetadataFor($class)
|
||||
{
|
||||
$hash = null;
|
||||
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
$hash = spl_object_hash($hash);
|
||||
}
|
||||
|
||||
if (!is_string($class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($this->metadatas[$class]);
|
||||
return isset($this->metadatas[$class]) || isset($this->metadatas[$hash]);
|
||||
}
|
||||
|
||||
public function addMetadata(ClassMetadata $metadata)
|
||||
public function addMetadata($metadata)
|
||||
{
|
||||
$this->metadatas[$metadata->getClassName()] = $metadata;
|
||||
}
|
||||
|
||||
public function addMetadataForValue($value, MetadataInterface $metadata)
|
||||
{
|
||||
$this->metadatas[$value] = $metadata;
|
||||
$key = is_object($value) ? spl_object_hash($value) : $value;
|
||||
$this->metadatas[$key] = $metadata;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Validator\ClassBasedInterface;
|
||||
use Symfony\Component\Validator\MetadataInterface;
|
||||
use Symfony\Component\Validator\PropertyMetadataContainerInterface;
|
||||
|
||||
interface LegacyClassMetadata extends MetadataInterface, PropertyMetadataContainerInterface, ClassBasedInterface
|
||||
{
|
||||
}
|
|
@ -20,6 +20,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\Entity;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\FakeClassMetadata;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\Reference;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
|
@ -386,4 +387,58 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
|
|||
$this->assertSame(2, $violations[0]->getMessagePluralization());
|
||||
$this->assertSame('Code', $violations[0]->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
|
||||
*/
|
||||
public function testMetadataMustImplementClassMetadataInterface()
|
||||
{
|
||||
$entity = new Entity();
|
||||
|
||||
$metadata = $this->getMock('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata');
|
||||
$metadata->expects($this->any())
|
||||
->method('getClassName')
|
||||
->will($this->returnValue(get_class($entity)));
|
||||
|
||||
$this->metadataFactory->addMetadata($metadata);
|
||||
|
||||
$this->validator->validate($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
|
||||
*/
|
||||
public function testReferenceMetadataMustImplementClassMetadataInterface()
|
||||
{
|
||||
$entity = new Entity();
|
||||
$entity->reference = new Reference();
|
||||
|
||||
$metadata = $this->getMock('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata');
|
||||
$metadata->expects($this->any())
|
||||
->method('getClassName')
|
||||
->will($this->returnValue(get_class($entity->reference)));
|
||||
|
||||
$this->metadataFactory->addMetadata($metadata);
|
||||
|
||||
$this->metadata->addPropertyConstraint('reference', new Valid());
|
||||
|
||||
$this->validator->validate($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
|
||||
*/
|
||||
public function testPropertyMetadataMustImplementPropertyMetadataInterface()
|
||||
{
|
||||
$entity = new Entity();
|
||||
|
||||
// Legacy interface
|
||||
$propertyMetadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
|
||||
$metadata = new FakeClassMetadata(get_class($entity));
|
||||
$metadata->addPropertyMetadata('firstName', $propertyMetadata);
|
||||
|
||||
$this->metadataFactory->addMetadata($metadata);
|
||||
|
||||
$this->validator->validate($entity);
|
||||
}
|
||||
}
|
||||
|
|
Reference in New Issue