[Validator] Wrapped collections into CollectionNode instances
This commit is contained in:
parent
94583a9289
commit
117b1b9a17
|
@ -0,0 +1,48 @@
|
|||
<?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\Mapping;
|
||||
|
||||
/**
|
||||
* @since %%NextVersion%%
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class CollectionMetadata implements MetadataInterface
|
||||
{
|
||||
private $traversalStrategy;
|
||||
|
||||
public function __construct($traversalStrategy)
|
||||
{
|
||||
$this->traversalStrategy = $traversalStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all constraints for a given validation group.
|
||||
*
|
||||
* @param string $group The validation group.
|
||||
*
|
||||
* @return \Symfony\Component\Validator\Constraint[] A list of constraint instances.
|
||||
*/
|
||||
public function findConstraints($group)
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getCascadingStrategy()
|
||||
{
|
||||
return CascadingStrategy::NONE;
|
||||
}
|
||||
|
||||
public function getTraversalStrategy()
|
||||
{
|
||||
return $this->traversalStrategy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?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\Node;
|
||||
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||
|
||||
/**
|
||||
* Represents an traversable collection in the validation graph.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class CollectionNode extends Node
|
||||
{
|
||||
/**
|
||||
* Creates a new collection node.
|
||||
*
|
||||
* @param array|\Traversable $collection The validated collection
|
||||
* @param MetadataInterface $metadata The class metadata of that
|
||||
* object
|
||||
* @param string $propertyPath The property path leading
|
||||
* to this node
|
||||
* @param string[] $groups The groups in which this
|
||||
* node should be validated
|
||||
* @param string[]|null $cascadedGroups The groups in which
|
||||
* cascaded objects should be
|
||||
* validated
|
||||
*
|
||||
* @throws UnexpectedTypeException If the given value is not an array or
|
||||
* an instance of {@link \Traversable}
|
||||
*/
|
||||
public function __construct($collection, MetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null)
|
||||
{
|
||||
if (!is_array($collection) && !$collection instanceof \Traversable) {
|
||||
throw new UnexpectedTypeException($collection, 'object');
|
||||
}
|
||||
|
||||
parent::__construct(
|
||||
$collection,
|
||||
$metadata,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
$cascadedGroups
|
||||
);
|
||||
}
|
||||
}
|
|
@ -16,9 +16,11 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
|||
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
||||
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\CollectionMetadata;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
use Symfony\Component\Validator\Node\CollectionNode;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\Node\PropertyNode;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
||||
|
@ -84,6 +86,8 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
|
||||
if ($node instanceof ClassNode) {
|
||||
$this->traverseClassNode($node, $traversal);
|
||||
} elseif ($node instanceof CollectionNode) {
|
||||
$this->traverseCollectionNode($node, $traversal);
|
||||
} else {
|
||||
$this->traverseNode($node, $traversal);
|
||||
}
|
||||
|
@ -145,13 +149,12 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
// Arrays are always traversed, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$this->cascadeEachObjectIn(
|
||||
$traversal->nodeQueue->enqueue(new CollectionNode(
|
||||
$node->value,
|
||||
new CollectionMetadata($traversalStrategy),
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
$cascadedGroups
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -188,13 +191,13 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
));
|
||||
}
|
||||
|
||||
$this->cascadeEachObjectIn(
|
||||
$traversal->nodeQueue->enqueue(new CollectionNode(
|
||||
$node->value,
|
||||
new CollectionMetadata($traversalStrategy),
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
$node->groups,
|
||||
$node->cascadedGroups
|
||||
));
|
||||
}
|
||||
|
||||
private function traverseClassNode(ClassNode $node, Traversal $traversal, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||
|
@ -252,13 +255,61 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
));
|
||||
}
|
||||
|
||||
$this->cascadeEachObjectIn(
|
||||
$traversal->nodeQueue->enqueue(new CollectionNode(
|
||||
$node->value,
|
||||
new CollectionMetadata($traversalStrategy),
|
||||
$node->propertyPath,
|
||||
$node->groups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
$node->cascadedGroups
|
||||
));
|
||||
}
|
||||
|
||||
private function traverseCollectionNode(CollectionNode $node, Traversal $traversal)
|
||||
{
|
||||
if (false === $this->visit($node, $traversal->context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$traversalStrategy = $node->metadata->getTraversalStrategy();
|
||||
|
||||
if ($traversalStrategy & TraversalStrategy::RECURSIVE) {
|
||||
// Try to traverse nested objects, but ignore if they do not
|
||||
// implement Traversable
|
||||
$traversalStrategy |= TraversalStrategy::IGNORE_NON_TRAVERSABLE;
|
||||
} else {
|
||||
// If the RECURSIVE bit is not set, change the strategy to IMPLICIT
|
||||
// in order to respect the metadata's traversal strategy of each entry
|
||||
// in the collection
|
||||
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||
}
|
||||
|
||||
foreach ($node->value as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Arrays are always cascaded, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$traversal->nodeQueue->enqueue(new CollectionNode(
|
||||
$value,
|
||||
new CollectionMetadata($traversalStrategy),
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups
|
||||
));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scalar and null values in the collection are ignored
|
||||
// (BC with Symfony < 2.5)
|
||||
if (is_object($value)) {
|
||||
$this->cascadeObject(
|
||||
$value,
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, Traversal $traversal)
|
||||
|
@ -287,57 +338,12 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
throw $e;
|
||||
}
|
||||
|
||||
// In that case, iterate the object and cascade each entry
|
||||
$this->cascadeEachObjectIn(
|
||||
$object,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function cascadeEachObjectIn($collection, $propertyPath, array $groups, $traversalStrategy, Traversal $traversal)
|
||||
{
|
||||
if ($traversalStrategy & TraversalStrategy::RECURSIVE) {
|
||||
// Try to traverse nested objects, but ignore if they do not
|
||||
// implement Traversable
|
||||
$traversalStrategy |= TraversalStrategy::IGNORE_NON_TRAVERSABLE;
|
||||
} else {
|
||||
// If the RECURSIVE bit is not set, change the strategy to IMPLICIT
|
||||
// in order to respect the metadata's traversal strategy of each entry
|
||||
// in the collection
|
||||
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||
}
|
||||
|
||||
foreach ($collection as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Arrays are always cascaded, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$this->cascadeEachObjectIn(
|
||||
$value,
|
||||
$propertyPath.'['.$key.']',
|
||||
$groups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scalar and null values in the collection are ignored
|
||||
// (BC with Symfony < 2.5)
|
||||
if (is_object($value)) {
|
||||
$this->cascadeObject(
|
||||
$value,
|
||||
$propertyPath.'['.$key.']',
|
||||
$groups,
|
||||
$traversalStrategy,
|
||||
$traversal
|
||||
);
|
||||
}
|
||||
$traversal->nodeQueue->enqueue(new CollectionNode(
|
||||
$object,
|
||||
new CollectionMetadata($traversalStrategy),
|
||||
$propertyPath,
|
||||
$groups
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Reference in New Issue