[Validator] Made tests green (yay!)
This commit is contained in:
parent
680f1ee6c7
commit
8ae68c9543
@ -11,12 +11,16 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Validator\Context;
|
namespace Symfony\Component\Validator\Context;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
use Symfony\Component\Validator\ClassBasedInterface;
|
use Symfony\Component\Validator\ClassBasedInterface;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolation;
|
||||||
use Symfony\Component\Validator\ConstraintViolationList;
|
use Symfony\Component\Validator\ConstraintViolationList;
|
||||||
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
||||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||||
use Symfony\Component\Validator\Node\Node;
|
use Symfony\Component\Validator\Node\Node;
|
||||||
|
use Symfony\Component\Validator\Util\PropertyPath;
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since %%NextVersion%%
|
* @since %%NextVersion%%
|
||||||
@ -48,18 +52,30 @@ class ExecutionContext implements ExecutionContextInterface
|
|||||||
*/
|
*/
|
||||||
private $groupManager;
|
private $groupManager;
|
||||||
|
|
||||||
public function __construct(ValidatorInterface $validator, GroupManagerInterface $groupManager)
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $translationDomain;
|
||||||
|
|
||||||
|
public function __construct($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||||
{
|
{
|
||||||
|
$this->root = $root;
|
||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
$this->groupManager = $groupManager;
|
$this->groupManager = $groupManager;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->translationDomain = $translationDomain;
|
||||||
$this->violations = new ConstraintViolationList();
|
$this->violations = new ConstraintViolationList();
|
||||||
|
$this->nodeStack = new \SplStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pushNode(Node $node)
|
public function pushNode(Node $node)
|
||||||
{
|
{
|
||||||
if (null === $this->node) {
|
if (null !== $this->node) {
|
||||||
$this->root = $node->value;
|
|
||||||
} else {
|
|
||||||
$this->nodeStack->push($this->node);
|
$this->nodeStack->push($this->node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,13 +105,32 @@ class ExecutionContext implements ExecutionContextInterface
|
|||||||
return $poppedNode;
|
return $poppedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
|
public function addViolation($message, array $parameters = array())
|
||||||
{
|
{
|
||||||
|
$this->violations->add(new ConstraintViolation(
|
||||||
|
$this->translator->trans($message, $parameters, $this->translationDomain),
|
||||||
|
$message,
|
||||||
|
$parameters,
|
||||||
|
$this->root,
|
||||||
|
$this->getPropertyPath(),
|
||||||
|
$this->getValue(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildViolation($message)
|
public function buildViolation($message, array $parameters = array())
|
||||||
{
|
{
|
||||||
|
return new ConstraintViolationBuilder(
|
||||||
|
$this->violations,
|
||||||
|
$message,
|
||||||
|
$parameters,
|
||||||
|
$this->root,
|
||||||
|
$this->getPropertyPath(),
|
||||||
|
$this->getValue(),
|
||||||
|
$this->translator,
|
||||||
|
$this->translationDomain
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getViolations()
|
public function getViolations()
|
||||||
@ -141,15 +176,7 @@ class ExecutionContext implements ExecutionContextInterface
|
|||||||
{
|
{
|
||||||
$propertyPath = $this->node ? $this->node->propertyPath : '';
|
$propertyPath = $this->node ? $this->node->propertyPath : '';
|
||||||
|
|
||||||
if (strlen($subPath) > 0) {
|
return PropertyPath::append($propertyPath, $subPath);
|
||||||
if ('[' === $subPath{1}) {
|
|
||||||
return $propertyPath.$subPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $propertyPath ? $propertyPath.'.'.$subPath : $subPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $propertyPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,11 +30,11 @@ interface ExecutionContextInterface
|
|||||||
* Adds a violation at the current node of the validation graph.
|
* Adds a violation at the current node of the validation graph.
|
||||||
*
|
*
|
||||||
* @param string $message The error message.
|
* @param string $message The error message.
|
||||||
* @param array $params The parameters substituted in the error message.
|
* @param array $parameters The parameters substituted in the error message.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public function addViolation($message, array $params = array());
|
public function addViolation($message, array $parameters = array());
|
||||||
|
|
||||||
public function buildViolation($message);
|
public function buildViolation($message);
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Validator\Context;
|
namespace Symfony\Component\Validator\Context;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
||||||
use Symfony\Component\Validator\Node\Node;
|
use Symfony\Component\Validator\Node\Node;
|
||||||
use Symfony\Component\Validator\NodeVisitor\AbstractVisitor;
|
use Symfony\Component\Validator\NodeVisitor\AbstractVisitor;
|
||||||
@ -42,9 +43,21 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
|
|||||||
*/
|
*/
|
||||||
private $contextStack;
|
private $contextStack;
|
||||||
|
|
||||||
public function __construct(GroupManagerInterface $groupManager)
|
/**
|
||||||
|
* @var TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $translationDomain;
|
||||||
|
|
||||||
|
public function __construct(GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||||
{
|
{
|
||||||
$this->groupManager = $groupManager;
|
$this->groupManager = $groupManager;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->translationDomain = $translationDomain;
|
||||||
$this->contextStack = new \SplStack();
|
$this->contextStack = new \SplStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,13 +66,23 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
|
|||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function startContext()
|
public function startContext($root)
|
||||||
{
|
{
|
||||||
|
if (null === $this->validator) {
|
||||||
|
// TODO error, call initialize() first
|
||||||
|
}
|
||||||
|
|
||||||
if (null !== $this->currentContext) {
|
if (null !== $this->currentContext) {
|
||||||
$this->contextStack->push($this->currentContext);
|
$this->contextStack->push($this->currentContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->currentContext = new ExecutionContext($this->validator, $this->groupManager);
|
$this->currentContext = new LegacyExecutionContext(
|
||||||
|
$root,
|
||||||
|
$this->validator,
|
||||||
|
$this->groupManager,
|
||||||
|
$this->translator,
|
||||||
|
$this->translationDomain
|
||||||
|
);
|
||||||
|
|
||||||
return $this->currentContext;
|
return $this->currentContext;
|
||||||
}
|
}
|
||||||
@ -100,7 +123,7 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
|
|||||||
public function enterNode(Node $node)
|
public function enterNode(Node $node)
|
||||||
{
|
{
|
||||||
if (null === $this->currentContext) {
|
if (null === $this->currentContext) {
|
||||||
// error no context started
|
// TODO error call startContext() first
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->currentContext->pushNode($node);
|
$this->currentContext->pushNode($node);
|
||||||
|
@ -18,9 +18,11 @@ namespace Symfony\Component\Validator\Context;
|
|||||||
interface ExecutionContextManagerInterface
|
interface ExecutionContextManagerInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* @param mixed $root
|
||||||
|
*
|
||||||
* @return ExecutionContextInterface The started context
|
* @return ExecutionContextInterface The started context
|
||||||
*/
|
*/
|
||||||
public function startContext();
|
public function startContext($root);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ExecutionContextInterface The stopped context
|
* @return ExecutionContextInterface The stopped context
|
||||||
|
@ -11,7 +11,12 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Validator\Context;
|
namespace Symfony\Component\Validator\Context;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
use Symfony\Component\Validator\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface;
|
use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface;
|
||||||
|
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since %%NextVersion%%
|
* @since %%NextVersion%%
|
||||||
@ -19,23 +24,84 @@ use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionCont
|
|||||||
*/
|
*/
|
||||||
class LegacyExecutionContext extends ExecutionContext implements LegacyExecutionContextInterface
|
class LegacyExecutionContext extends ExecutionContext implements LegacyExecutionContextInterface
|
||||||
{
|
{
|
||||||
public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
|
public function __construct($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||||
{
|
{
|
||||||
|
if (!$validator instanceof LegacyValidatorInterface) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'The validator passed to LegacyExecutionContext must implement '.
|
||||||
|
'"Symfony\Component\Validator\ValidatorInterface".'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($root, $validator, $groupManager, $translator, $translationDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addViolation($message, array $parameters = array(), $invalidValue = null, $pluralization = null, $code = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() >= 3) {
|
||||||
|
$this
|
||||||
|
->buildViolation($message, $parameters)
|
||||||
|
->setInvalidValue($invalidValue)
|
||||||
|
->setPluralization($pluralization)
|
||||||
|
->setCode($code)
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::addViolation($message, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $pluralization = null, $code = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() >= 3) {
|
||||||
|
$this
|
||||||
|
->buildViolation($message, $parameters)
|
||||||
|
->atPath($subPath)
|
||||||
|
->setInvalidValue($invalidValue)
|
||||||
|
->setPluralization($pluralization)
|
||||||
|
->setCode($code)
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this
|
||||||
|
->buildViolation($message, $parameters)
|
||||||
|
->atPath($subPath)
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false)
|
public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false)
|
||||||
{
|
{
|
||||||
|
// TODO handle $traverse and $deep
|
||||||
|
|
||||||
|
return $this
|
||||||
|
->getValidator()
|
||||||
|
->inContext($this)
|
||||||
|
->atPath($subPath)
|
||||||
|
->validateObject($value, $groups)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateValue($value, $constraints, $subPath = '', $groups = null)
|
public function validateValue($value, $constraints, $subPath = '', $groups = null)
|
||||||
{
|
{
|
||||||
|
return $this
|
||||||
|
->getValidator()
|
||||||
|
->inContext($this)
|
||||||
|
->atPath($subPath)
|
||||||
|
->validateValue($value, $constraints, $groups)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMetadataFactory()
|
public function getMetadataFactory()
|
||||||
{
|
{
|
||||||
|
return $this->getValidator()->getMetadataFactory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<?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 CascadingStrategy
|
||||||
|
{
|
||||||
|
const NONE = 0;
|
||||||
|
|
||||||
|
const CASCADE = 1;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ use Symfony\Component\Validator\Exception\GroupDefinitionException;
|
|||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassBasedInterface, PropertyMetadataContainerInterface
|
class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassBasedInterface, PropertyMetadataContainerInterface, ClassMetadataInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -63,6 +63,8 @@ class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassB
|
|||||||
*/
|
*/
|
||||||
public $groupSequenceProvider = false;
|
public $groupSequenceProvider = false;
|
||||||
|
|
||||||
|
public $traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \ReflectionClass
|
* @var \ReflectionClass
|
||||||
*/
|
*/
|
||||||
@ -423,4 +425,19 @@ class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassB
|
|||||||
{
|
{
|
||||||
return $this->groupSequenceProvider;
|
return $this->groupSequenceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class nodes are never cascaded.
|
||||||
|
*
|
||||||
|
* @return Boolean Always returns false.
|
||||||
|
*/
|
||||||
|
public function getCascadingStrategy()
|
||||||
|
{
|
||||||
|
return CascadingStrategy::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTraversalStrategy()
|
||||||
|
{
|
||||||
|
return $this->traversalStrategy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace Symfony\Component\Validator\Mapping;
|
|||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
abstract class ElementMetadata
|
abstract class ElementMetadata implements MetadataInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var Constraint[]
|
* @var Constraint[]
|
||||||
|
@ -12,20 +12,18 @@
|
|||||||
namespace Symfony\Component\Validator\Mapping;
|
namespace Symfony\Component\Validator\Mapping;
|
||||||
|
|
||||||
use Symfony\Component\Validator\ValidationVisitorInterface;
|
use Symfony\Component\Validator\ValidationVisitorInterface;
|
||||||
use Symfony\Component\Validator\ClassBasedInterface;
|
use Symfony\Component\Validator\PropertyMetadataInterface as LegacyPropertyMetadataInterface;
|
||||||
use Symfony\Component\Validator\PropertyMetadataInterface;
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
use Symfony\Component\Validator\Constraints\Valid;
|
use Symfony\Component\Validator\Constraints\Valid;
|
||||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||||
|
|
||||||
abstract class MemberMetadata extends ElementMetadata implements PropertyMetadataInterface, ClassBasedInterface
|
abstract class MemberMetadata extends ElementMetadata implements PropertyMetadataInterface, LegacyPropertyMetadataInterface
|
||||||
{
|
{
|
||||||
public $class;
|
public $class;
|
||||||
public $name;
|
public $name;
|
||||||
public $property;
|
public $property;
|
||||||
public $cascaded = false;
|
public $cascadingStrategy = CascadingStrategy::NONE;
|
||||||
public $collectionCascaded = false;
|
public $traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||||
public $collectionCascadedDeeply = false;
|
|
||||||
private $reflMember = array();
|
private $reflMember = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,10 +62,15 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($constraint instanceof Valid) {
|
if ($constraint instanceof Valid) {
|
||||||
$this->cascaded = true;
|
$this->cascadingStrategy = CascadingStrategy::CASCADE;
|
||||||
/* @var Valid $constraint */
|
|
||||||
$this->collectionCascaded = $constraint->traverse;
|
if ($constraint->traverse) {
|
||||||
$this->collectionCascadedDeeply = $constraint->deep;
|
$this->traversalStrategy = TraversalStrategy::TRAVERSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($constraint->deep) {
|
||||||
|
$this->traversalStrategy |= TraversalStrategy::RECURSIVE;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
parent::addConstraint($constraint);
|
parent::addConstraint($constraint);
|
||||||
}
|
}
|
||||||
@ -86,9 +89,8 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
|
|||||||
'class',
|
'class',
|
||||||
'name',
|
'name',
|
||||||
'property',
|
'property',
|
||||||
'cascaded',
|
'cascadingStrategy',
|
||||||
'collectionCascaded',
|
'traversalStrategy',
|
||||||
'collectionCascadedDeeply',
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +160,16 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
|
|||||||
return $this->getReflectionMember($objectOrClassName)->isPrivate();
|
return $this->getReflectionMember($objectOrClassName)->isPrivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCascadingStrategy()
|
||||||
|
{
|
||||||
|
return $this->cascadingStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTraversalStrategy()
|
||||||
|
{
|
||||||
|
return $this->traversalStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether objects stored in this member should be validated
|
* Returns whether objects stored in this member should be validated
|
||||||
*
|
*
|
||||||
@ -165,7 +177,7 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
|
|||||||
*/
|
*/
|
||||||
public function isCascaded()
|
public function isCascaded()
|
||||||
{
|
{
|
||||||
return $this->cascaded;
|
return $this->cascadingStrategy & CascadingStrategy::CASCADE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,7 +188,7 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
|
|||||||
*/
|
*/
|
||||||
public function isCollectionCascaded()
|
public function isCollectionCascaded()
|
||||||
{
|
{
|
||||||
return $this->collectionCascaded;
|
return $this->traversalStrategy & TraversalStrategy::TRAVERSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,7 +199,7 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
|
|||||||
*/
|
*/
|
||||||
public function isCollectionCascadedDeeply()
|
public function isCollectionCascadedDeeply()
|
||||||
{
|
{
|
||||||
return $this->collectionCascadedDeeply;
|
return $this->traversalStrategy & TraversalStrategy::RECURSIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,5 +53,7 @@ interface MetadataInterface
|
|||||||
*/
|
*/
|
||||||
public function findConstraints($group);
|
public function findConstraints($group);
|
||||||
|
|
||||||
public function supportsCascading();
|
public function getCascadingStrategy();
|
||||||
|
|
||||||
|
public function getTraversalStrategy();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<?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 TraversalStrategy
|
||||||
|
{
|
||||||
|
const IMPLICIT = 0;
|
||||||
|
|
||||||
|
const NONE = 1;
|
||||||
|
|
||||||
|
const TRAVERSE = 2;
|
||||||
|
|
||||||
|
const RECURSIVE = 4;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -11,35 +11,48 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Validator\Mapping;
|
namespace Symfony\Component\Validator\Mapping;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraints\Valid;
|
||||||
|
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since %%NextVersion%%
|
* @since %%NextVersion%%
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
*/
|
*/
|
||||||
class ValueMetadata implements MetadataInterface
|
class ValueMetadata extends ElementMetadata
|
||||||
{
|
{
|
||||||
/**
|
public function __construct(array $constraints)
|
||||||
* Returns all constraints for a given validation group.
|
{
|
||||||
*
|
foreach ($constraints as $constraint) {
|
||||||
* @param string $group The validation group.
|
if ($constraint instanceof Valid) {
|
||||||
*
|
// Why can't the Valid constraint be executed directly?
|
||||||
* @return \Symfony\Component\Validator\Constraint[] A list of constraint instances.
|
//
|
||||||
*/
|
// It cannot be executed like regular other constraints, because regular
|
||||||
public function findConstraints($group)
|
// constraints are only executed *if they belong to the validated group*.
|
||||||
|
// The Valid constraint, on the other hand, is always executed and propagates
|
||||||
|
// the group to the cascaded object. The propagated group depends on
|
||||||
|
//
|
||||||
|
// * Whether a group sequence is currently being executed. Then the default
|
||||||
|
// group is propagated.
|
||||||
|
//
|
||||||
|
// * Otherwise the validated group is propagated.
|
||||||
|
|
||||||
|
throw new ValidatorException(sprintf(
|
||||||
|
'The constraint "%s" cannot be validated. Use the method '.
|
||||||
|
'validate() instead.',
|
||||||
|
get_class($constraint)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addConstraint($constraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCascadingStrategy()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsCascading()
|
public function getTraversalStrategy()
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsIteration()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsRecursiveIteration()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class ClassNode extends Node
|
|||||||
*/
|
*/
|
||||||
public $metadata;
|
public $metadata;
|
||||||
|
|
||||||
public function __construct($value, ClassMetadataInterface $metadata, $propertyPath, array $groups)
|
public function __construct($value, ClassMetadataInterface $metadata, $propertyPath, array $groups, array $cascadedGroups)
|
||||||
{
|
{
|
||||||
if (!is_object($value)) {
|
if (!is_object($value)) {
|
||||||
// error
|
// error
|
||||||
@ -34,7 +34,8 @@ class ClassNode extends Node
|
|||||||
$value,
|
$value,
|
||||||
$metadata,
|
$metadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups
|
$groups,
|
||||||
|
$cascadedGroups
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,11 +27,14 @@ abstract class Node
|
|||||||
|
|
||||||
public $groups;
|
public $groups;
|
||||||
|
|
||||||
public function __construct($value, MetadataInterface $metadata, $propertyPath, array $groups)
|
public $cascadedGroups;
|
||||||
|
|
||||||
|
public function __construct($value, MetadataInterface $metadata, $propertyPath, array $groups, array $cascadedGroups)
|
||||||
{
|
{
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
$this->metadata = $metadata;
|
$this->metadata = $metadata;
|
||||||
$this->propertyPath = $propertyPath;
|
$this->propertyPath = $propertyPath;
|
||||||
$this->groups = $groups;
|
$this->groups = $groups;
|
||||||
|
$this->cascadedGroups = $cascadedGroups;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,14 @@ class PropertyNode extends Node
|
|||||||
*/
|
*/
|
||||||
public $metadata;
|
public $metadata;
|
||||||
|
|
||||||
public function __construct($value, PropertyMetadataInterface $metadata, $propertyPath, array $groups)
|
public function __construct($value, PropertyMetadataInterface $metadata, $propertyPath, array $groups, array $cascadedGroups)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
$value,
|
$value,
|
||||||
$metadata,
|
$metadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups
|
$groups,
|
||||||
|
$cascadedGroups
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
namespace Symfony\Component\Validator\NodeTraverser;
|
namespace Symfony\Component\Validator\NodeTraverser;
|
||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
||||||
|
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
||||||
|
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||||
use Symfony\Component\Validator\Node\ClassNode;
|
use Symfony\Component\Validator\Node\ClassNode;
|
||||||
use Symfony\Component\Validator\Node\Node;
|
use Symfony\Component\Validator\Node\Node;
|
||||||
@ -98,15 +101,25 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
|
|
||||||
// Stop the traversal, but execute the leaveNode() methods anyway to
|
// Stop the traversal, but execute the leaveNode() methods anyway to
|
||||||
// perform possible cleanups
|
// perform possible cleanups
|
||||||
if (!$stopTraversal && is_object($node->value) && $node->metadata->supportsCascading()) {
|
if (!$stopTraversal && null !== $node->value) {
|
||||||
$classMetadata = $this->metadataFactory->getMetadataFor($node->value);
|
$cascadingStrategy = $node->metadata->getCascadingStrategy();
|
||||||
|
$traversalStrategy = $node->metadata->getTraversalStrategy();
|
||||||
|
|
||||||
$this->traverseClassNode(new ClassNode(
|
if (is_array($node->value)) {
|
||||||
$node->value,
|
$this->cascadeCollection(
|
||||||
$classMetadata,
|
$node->value,
|
||||||
$node->propertyPath,
|
$node->propertyPath,
|
||||||
$node->groups
|
$node->cascadedGroups,
|
||||||
));
|
$traversalStrategy
|
||||||
|
);
|
||||||
|
} elseif ($cascadingStrategy & CascadingStrategy::CASCADE) {
|
||||||
|
$this->cascadeObject(
|
||||||
|
$node->value,
|
||||||
|
$node->propertyPath,
|
||||||
|
$node->cascadedGroups,
|
||||||
|
$traversalStrategy
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->visitors as $visitor) {
|
foreach ($this->visitors as $visitor) {
|
||||||
@ -114,7 +127,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function traverseClassNode(ClassNode $node)
|
private function traverseClassNode(ClassNode $node, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||||
{
|
{
|
||||||
$stopTraversal = false;
|
$stopTraversal = false;
|
||||||
|
|
||||||
@ -135,14 +148,89 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
$node->propertyPath
|
$node->propertyPath
|
||||||
? $node->propertyPath.'.'.$propertyName
|
? $node->propertyPath.'.'.$propertyName
|
||||||
: $propertyName,
|
: $propertyName,
|
||||||
$node->groups
|
$node->groups,
|
||||||
|
$node->cascadedGroups
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||||
|
$traversalStrategy = $node->metadata->getTraversalStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($traversalStrategy & TraversalStrategy::TRAVERSE) {
|
||||||
|
$this->cascadeCollection(
|
||||||
|
$node->value,
|
||||||
|
$node->propertyPath,
|
||||||
|
$node->groups,
|
||||||
|
$traversalStrategy
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->visitors as $visitor) {
|
foreach ($this->visitors as $visitor) {
|
||||||
$visitor->leaveNode($node);
|
$visitor->leaveNode($node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||||
|
|
||||||
|
$classNode = new ClassNode(
|
||||||
|
$object,
|
||||||
|
$classMetadata,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
$groups
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->traverseClassNode($classNode, $traversalStrategy);
|
||||||
|
} catch (NoSuchMetadataException $e) {
|
||||||
|
if (!$object instanceof \Traversable || !($traversalStrategy & TraversalStrategy::TRAVERSE)) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata doesn't necessarily have to exist for
|
||||||
|
// traversable objects, because we know how to validate
|
||||||
|
// them anyway.
|
||||||
|
$this->cascadeCollection(
|
||||||
|
$object,
|
||||||
|
$propertyPath,
|
||||||
|
$groups,
|
||||||
|
$traversalStrategy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cascadeCollection($collection, $propertyPath, array $groups, $traversalStrategy)
|
||||||
|
{
|
||||||
|
if (!($traversalStrategy & TraversalStrategy::RECURSIVE)) {
|
||||||
|
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($collection as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$this->cascadeCollection(
|
||||||
|
$value,
|
||||||
|
$propertyPath.'['.$key.']',
|
||||||
|
$groups,
|
||||||
|
$traversalStrategy
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar and null values in the collection are ignored
|
||||||
|
if (is_object($value)) {
|
||||||
|
$this->cascadeObject(
|
||||||
|
$value,
|
||||||
|
$propertyPath.'['.$key.']',
|
||||||
|
$groups,
|
||||||
|
$traversalStrategy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Validator\NodeVisitor;
|
namespace Symfony\Component\Validator\NodeVisitor;
|
||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\Constraints\GroupSequence;
|
||||||
use Symfony\Component\Validator\Node\ClassNode;
|
use Symfony\Component\Validator\Node\ClassNode;
|
||||||
use Symfony\Component\Validator\Node\Node;
|
use Symfony\Component\Validator\Node\Node;
|
||||||
|
|
||||||
@ -31,7 +32,11 @@ class GroupSequenceResolver extends AbstractVisitor
|
|||||||
$groupSequence = $node->metadata->getGroupSequence();
|
$groupSequence = $node->metadata->getGroupSequence();
|
||||||
} elseif ($node->metadata->isGroupSequenceProvider()) {
|
} elseif ($node->metadata->isGroupSequenceProvider()) {
|
||||||
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
|
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
|
||||||
$groupSequence = $value->getGroupSequence();
|
$groupSequence = $node->value->getGroupSequence();
|
||||||
|
|
||||||
|
if (!$groupSequence instanceof GroupSequence) {
|
||||||
|
$groupSequence = new GroupSequence($groupSequence);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,7 +48,7 @@ class GroupSequenceResolver extends AbstractVisitor
|
|||||||
$node->groups[$key] = $groupSequence;
|
$node->groups[$key] = $groupSequence;
|
||||||
|
|
||||||
// Cascade the "Default" group when validating the sequence
|
// Cascade the "Default" group when validating the sequence
|
||||||
$node->groups[$key]->cascadedGroup = Constraint::DEFAULT_GROUP;
|
$groupSequence->cascadedGroup = Constraint::DEFAULT_GROUP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
|
|||||||
if ($node instanceof ClassNode) {
|
if ($node instanceof ClassNode) {
|
||||||
$objectHash = spl_object_hash($node->value);
|
$objectHash = spl_object_hash($node->value);
|
||||||
$this->objectHashStack->push($objectHash);
|
$this->objectHashStack->push($objectHash);
|
||||||
} elseif ($node instanceof PropertyNode) {
|
} elseif ($node instanceof PropertyNode && count($this->objectHashStack) > 0) {
|
||||||
$objectHash = $this->objectHashStack->top();
|
$objectHash = $this->objectHashStack->top();
|
||||||
} else {
|
} else {
|
||||||
$objectHash = null;
|
$objectHash = null;
|
||||||
@ -112,10 +112,8 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only traverse group sequences at class, not at property level
|
// Skip the group sequence when validating properties
|
||||||
if (!$node instanceof ClassNode) {
|
unset($node->groups[$key]);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse group sequence until a violation is generated
|
// Traverse group sequence until a violation is generated
|
||||||
$this->traverseGroupSequence($node, $group);
|
$this->traverseGroupSequence($node, $group);
|
||||||
@ -130,24 +128,29 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function leaveNode(Node $node)
|
||||||
|
{
|
||||||
|
if ($node instanceof ClassNode) {
|
||||||
|
$this->objectHashStack->pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getCurrentGroup()
|
public function getCurrentGroup()
|
||||||
{
|
{
|
||||||
return $this->currentGroup;
|
return $this->currentGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function traverseGroupSequence(ClassNode $node, GroupSequence $groupSequence)
|
private function traverseGroupSequence(Node $node, GroupSequence $groupSequence)
|
||||||
{
|
{
|
||||||
$context = $this->contextManager->getCurrentContext();
|
$context = $this->contextManager->getCurrentContext();
|
||||||
$violationCount = count($context->getViolations());
|
$violationCount = count($context->getViolations());
|
||||||
|
|
||||||
foreach ($groupSequence->groups as $groupInSequence) {
|
foreach ($groupSequence->groups as $groupInSequence) {
|
||||||
$this->nodeTraverser->traverse(array(new ClassNode(
|
$node = clone $node;
|
||||||
$node->value,
|
$node->groups = array($groupInSequence);
|
||||||
$node->metadata,
|
$node->cascadedGroups = array($groupSequence->cascadedGroup ?: $groupInSequence);
|
||||||
$node->propertyPath,
|
|
||||||
array($groupInSequence),
|
$this->nodeTraverser->traverse(array($node));
|
||||||
array($groupSequence->cascadedGroup ?: $groupInSequence)
|
|
||||||
)));
|
|
||||||
|
|
||||||
// Abort sequence validation if a violation was generated
|
// Abort sequence validation if a violation was generated
|
||||||
if (count($context->getViolations()) > $violationCount) {
|
if (count($context->getViolations()) > $violationCount) {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Validator\Tests;
|
namespace Symfony\Component\Validator\Tests\Validator;
|
||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
use Symfony\Component\Validator\Constraints\Callback;
|
use Symfony\Component\Validator\Constraints\Callback;
|
||||||
@ -788,7 +788,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
$test->assertNull($context->getPropertyName());
|
$test->assertNull($context->getPropertyName());
|
||||||
$test->assertSame('', $context->getPropertyPath());
|
$test->assertSame('', $context->getPropertyPath());
|
||||||
$test->assertSame('Group', $context->getGroup());
|
$test->assertSame('Group', $context->getGroup());
|
||||||
$test->assertNull($context->getMetadata());
|
|
||||||
$test->assertSame($test->metadataFactory, $context->getMetadataFactory());
|
$test->assertSame($test->metadataFactory, $context->getMetadataFactory());
|
||||||
$test->assertSame('Bernhard', $context->getRoot());
|
$test->assertSame('Bernhard', $context->getRoot());
|
||||||
$test->assertSame('Bernhard', $context->getValue());
|
$test->assertSame('Bernhard', $context->getValue());
|
||||||
@ -942,8 +941,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testNoDuplicateValidationIfConstraintInMultipleGroups()
|
public function testNoDuplicateValidationIfConstraintInMultipleGroups()
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Currently not supported');
|
|
||||||
|
|
||||||
$entity = new Entity();
|
$entity = new Entity();
|
||||||
|
|
||||||
$callback = function ($value, ExecutionContextInterface $context) {
|
$callback = function ($value, ExecutionContextInterface $context) {
|
||||||
@ -963,8 +960,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testGroupSequenceAbortsAfterFailedGroup()
|
public function testGroupSequenceAbortsAfterFailedGroup()
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Currently not supported');
|
|
||||||
|
|
||||||
$entity = new Entity();
|
$entity = new Entity();
|
||||||
|
|
||||||
$callback1 = function ($value, ExecutionContextInterface $context) {
|
$callback1 = function ($value, ExecutionContextInterface $context) {
|
||||||
@ -997,8 +992,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testGroupSequenceIncludesReferences()
|
public function testGroupSequenceIncludesReferences()
|
||||||
{
|
{
|
||||||
$this->markTestSkipped('Currently not supported');
|
|
||||||
|
|
||||||
$entity = new Entity();
|
$entity = new Entity();
|
||||||
$entity->reference = new Reference();
|
$entity->reference = new Reference();
|
||||||
|
|
@ -9,17 +9,32 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Validator\Tests;
|
namespace Symfony\Component\Validator\Tests\Validator;
|
||||||
|
|
||||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||||
use Symfony\Component\Validator\Validator;
|
use Symfony\Component\Validator\Validator;
|
||||||
use Symfony\Component\Validator\DefaultTranslator;
|
use Symfony\Component\Validator\DefaultTranslator;
|
||||||
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
||||||
|
|
||||||
class ValidatorTest extends AbstractValidatorTest
|
class LegacyValidatorTest extends AbstractValidatorTest
|
||||||
{
|
{
|
||||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||||
{
|
{
|
||||||
return new Validator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
|
return new Validator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNoDuplicateValidationIfConstraintInMultipleGroups()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Currently not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupSequenceAbortsAfterFailedGroup()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Currently not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupSequenceIncludesReferences()
|
||||||
|
{
|
||||||
|
$this->markTestSkipped('Currently not supported');
|
||||||
|
}
|
||||||
}
|
}
|
@ -15,19 +15,20 @@ use Symfony\Component\Validator\DefaultTranslator;
|
|||||||
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextManager;
|
use Symfony\Component\Validator\Context\ExecutionContextManager;
|
||||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||||
|
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolver;
|
||||||
use Symfony\Component\Validator\NodeVisitor\NodeValidator;
|
use Symfony\Component\Validator\NodeVisitor\NodeValidator;
|
||||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
||||||
use Symfony\Component\Validator\Tests\AbstractValidatorTest;
|
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||||
use Symfony\Component\Validator\Validator\Validator;
|
|
||||||
|
|
||||||
class TraversingValidatorTest extends AbstractValidatorTest
|
class ValidatorTest extends AbstractValidatorTest
|
||||||
{
|
{
|
||||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||||
{
|
{
|
||||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
$nodeTraverser = new NodeTraverser($metadataFactory);
|
||||||
$nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory());
|
$nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory());
|
||||||
$contextManager = new ExecutionContextManager($nodeValidator, new DefaultTranslator());
|
$contextManager = new ExecutionContextManager($nodeValidator, new DefaultTranslator());
|
||||||
$validator = new Validator($nodeTraverser, $metadataFactory, $contextManager);
|
$validator = new LegacyValidator($nodeTraverser, $metadataFactory, $contextManager);
|
||||||
|
$groupSequenceResolver = new GroupSequenceResolver();
|
||||||
|
|
||||||
// The context manager needs the validator for passing it to created
|
// The context manager needs the validator for passing it to created
|
||||||
// contexts
|
// contexts
|
||||||
@ -37,6 +38,7 @@ class TraversingValidatorTest extends AbstractValidatorTest
|
|||||||
// context to the constraint validators
|
// context to the constraint validators
|
||||||
$nodeValidator->initialize($contextManager);
|
$nodeValidator->initialize($contextManager);
|
||||||
|
|
||||||
|
$nodeTraverser->addVisitor($groupSequenceResolver);
|
||||||
$nodeTraverser->addVisitor($contextManager);
|
$nodeTraverser->addVisitor($contextManager);
|
||||||
$nodeTraverser->addVisitor($nodeValidator);
|
$nodeTraverser->addVisitor($nodeValidator);
|
||||||
|
|
36
src/Symfony/Component/Validator/Util/PropertyPath.php
Normal file
36
src/Symfony/Component/Validator/Util/PropertyPath.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?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\Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since %%NextVersion%%
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class PropertyPath
|
||||||
|
{
|
||||||
|
public static function append($basePath, $subPath)
|
||||||
|
{
|
||||||
|
if ('' !== (string) $subPath) {
|
||||||
|
if ('[' === $subPath{1}) {
|
||||||
|
return $basePath.$subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $basePath ? $basePath.'.'.$subPath : $subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Validator;
|
|||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||||
use Symfony\Component\Validator\Mapping\ValueMetadata;
|
use Symfony\Component\Validator\Mapping\ValueMetadata;
|
||||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||||
@ -20,6 +22,7 @@ use Symfony\Component\Validator\Node\ClassNode;
|
|||||||
use Symfony\Component\Validator\Node\PropertyNode;
|
use Symfony\Component\Validator\Node\PropertyNode;
|
||||||
use Symfony\Component\Validator\Node\ValueNode;
|
use Symfony\Component\Validator\Node\ValueNode;
|
||||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
|
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
|
||||||
|
use Symfony\Component\Validator\Util\PropertyPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since %%NextVersion%%
|
* @since %%NextVersion%%
|
||||||
@ -75,15 +78,22 @@ abstract class AbstractValidator implements ValidatorInterface
|
|||||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||||
|
|
||||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||||
// error
|
throw new ValidatorException(sprintf(
|
||||||
|
'The metadata factory should return instances of '.
|
||||||
|
'"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||||
|
'got: "%s".',
|
||||||
|
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
|
|
||||||
$this->nodeTraverser->traverse(array(new ClassNode(
|
$this->nodeTraverser->traverse(array(new ClassNode(
|
||||||
$object,
|
$object,
|
||||||
$classMetadata,
|
$classMetadata,
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
// TODO use cascade group here
|
$groups,
|
||||||
$groups ? $this->normalizeGroups($groups) : $this->defaultGroups
|
$groups
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +102,12 @@ abstract class AbstractValidator implements ValidatorInterface
|
|||||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||||
|
|
||||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||||
// error
|
throw new ValidatorException(sprintf(
|
||||||
|
'The metadata factory should return instances of '.
|
||||||
|
'"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||||
|
'got: "%s".',
|
||||||
|
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
@ -105,7 +120,8 @@ abstract class AbstractValidator implements ValidatorInterface
|
|||||||
$nodes[] = new PropertyNode(
|
$nodes[] = new PropertyNode(
|
||||||
$propertyValue,
|
$propertyValue,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
$this->defaultPropertyPath,
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
|
$groups,
|
||||||
$groups
|
$groups
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -118,7 +134,12 @@ abstract class AbstractValidator implements ValidatorInterface
|
|||||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||||
|
|
||||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||||
// error
|
throw new ValidatorException(sprintf(
|
||||||
|
'The metadata factory should return instances of '.
|
||||||
|
'"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||||
|
'got: "%s".',
|
||||||
|
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
@ -129,7 +150,8 @@ abstract class AbstractValidator implements ValidatorInterface
|
|||||||
$nodes[] = new PropertyNode(
|
$nodes[] = new PropertyNode(
|
||||||
$value,
|
$value,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
$this->defaultPropertyPath,
|
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||||
|
$groups,
|
||||||
$groups
|
$groups
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -139,13 +161,19 @@ abstract class AbstractValidator implements ValidatorInterface
|
|||||||
|
|
||||||
protected function traverseValue($value, $constraints, $groups = null)
|
protected function traverseValue($value, $constraints, $groups = null)
|
||||||
{
|
{
|
||||||
|
if (!is_array($constraints)) {
|
||||||
|
$constraints = array($constraints);
|
||||||
|
}
|
||||||
|
|
||||||
$metadata = new ValueMetadata($constraints);
|
$metadata = new ValueMetadata($constraints);
|
||||||
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
|
|
||||||
$this->nodeTraverser->traverse(array(new ValueNode(
|
$this->nodeTraverser->traverse(array(new ValueNode(
|
||||||
$value,
|
$value,
|
||||||
$metadata,
|
$metadata,
|
||||||
$this->defaultPropertyPath,
|
$this->defaultPropertyPath,
|
||||||
$groups ? $this->normalizeGroups($groups) : $this->defaultGroups
|
$groups,
|
||||||
|
$groups
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class Validator extends AbstractValidator
|
|||||||
|
|
||||||
public function validateObject($object, $groups = null)
|
public function validateObject($object, $groups = null)
|
||||||
{
|
{
|
||||||
$this->contextManager->startContext();
|
$this->contextManager->startContext($object);
|
||||||
|
|
||||||
$this->traverseObject($object, $groups);
|
$this->traverseObject($object, $groups);
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class Validator extends AbstractValidator
|
|||||||
|
|
||||||
public function validateProperty($object, $propertyName, $groups = null)
|
public function validateProperty($object, $propertyName, $groups = null)
|
||||||
{
|
{
|
||||||
$this->contextManager->startContext();
|
$this->contextManager->startContext($object);
|
||||||
|
|
||||||
$this->traverseProperty($object, $propertyName, $groups);
|
$this->traverseProperty($object, $propertyName, $groups);
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ class Validator extends AbstractValidator
|
|||||||
|
|
||||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
||||||
{
|
{
|
||||||
$this->contextManager->startContext();
|
$this->contextManager->startContext($object);
|
||||||
|
|
||||||
$this->traversePropertyValue($object, $propertyName, $value, $groups);
|
$this->traversePropertyValue($object, $propertyName, $value, $groups);
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class Validator extends AbstractValidator
|
|||||||
|
|
||||||
public function validateValue($value, $constraints, $groups = null)
|
public function validateValue($value, $constraints, $groups = null)
|
||||||
{
|
{
|
||||||
$this->contextManager->startContext();
|
$this->contextManager->startContext($value);
|
||||||
|
|
||||||
$this->traverseValue($value, $constraints, $groups);
|
$this->traverseValue($value, $constraints, $groups);
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ interface ValidatorInterface
|
|||||||
*/
|
*/
|
||||||
public function validateObject($object, $groups = null);
|
public function validateObject($object, $groups = null);
|
||||||
|
|
||||||
|
// public function validateCollection($collection, $groups = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a property of a value against its current value.
|
* Validates a property of a value against its current value.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
<?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\Violation;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolation;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolationList;
|
||||||
|
use Symfony\Component\Validator\Util\PropertyPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since %%NextVersion%%
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
|
||||||
|
{
|
||||||
|
private $violations;
|
||||||
|
|
||||||
|
private $message;
|
||||||
|
|
||||||
|
private $parameters;
|
||||||
|
|
||||||
|
private $root;
|
||||||
|
|
||||||
|
private $invalidValue;
|
||||||
|
|
||||||
|
private $propertyPath;
|
||||||
|
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
private $translationDomain;
|
||||||
|
|
||||||
|
private $pluralization;
|
||||||
|
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
public function __construct(ConstraintViolationList $violations, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null)
|
||||||
|
{
|
||||||
|
$this->violations = $violations;
|
||||||
|
$this->message = $message;
|
||||||
|
$this->parameters = $parameters;
|
||||||
|
$this->root = $root;
|
||||||
|
$this->propertyPath = $propertyPath;
|
||||||
|
$this->invalidValue = $invalidValue;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->translationDomain = $translationDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function atPath($subPath)
|
||||||
|
{
|
||||||
|
$this->propertyPath = PropertyPath::append($this->propertyPath, $subPath);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setParameter($key, $value)
|
||||||
|
{
|
||||||
|
$this->parameters[$key] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setParameters(array $parameters)
|
||||||
|
{
|
||||||
|
$this->parameters = $parameters;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTranslationDomain($translationDomain)
|
||||||
|
{
|
||||||
|
$this->translationDomain = $translationDomain;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInvalidValue($invalidValue)
|
||||||
|
{
|
||||||
|
$this->invalidValue = $invalidValue;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPluralization($pluralization)
|
||||||
|
{
|
||||||
|
$this->pluralization = $pluralization;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode($code)
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addViolation()
|
||||||
|
{
|
||||||
|
if (null === $this->pluralization) {
|
||||||
|
$translatedMessage = $this->translator->trans(
|
||||||
|
$this->message,
|
||||||
|
$this->parameters,
|
||||||
|
$this->translationDomain
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$translatedMessage = $this->translator->transChoice(
|
||||||
|
$this->message,
|
||||||
|
$this->pluralization,
|
||||||
|
$this->parameters,
|
||||||
|
$this->translationDomain#
|
||||||
|
);
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$translatedMessage = $this->translator->trans(
|
||||||
|
$this->message,
|
||||||
|
$this->parameters,
|
||||||
|
$this->translationDomain
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->violations->add(new ConstraintViolation(
|
||||||
|
$translatedMessage,
|
||||||
|
$this->message,
|
||||||
|
$this->parameters,
|
||||||
|
$this->root,
|
||||||
|
$this->propertyPath,
|
||||||
|
$this->invalidValue,
|
||||||
|
$this->pluralization,
|
||||||
|
$this->code
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?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\Violation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since %%NextVersion%%
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
|
interface ConstraintViolationBuilderInterface
|
||||||
|
{
|
||||||
|
public function atPath($subPath);
|
||||||
|
|
||||||
|
public function setParameter($key, $value);
|
||||||
|
|
||||||
|
public function setParameters(array $parameters);
|
||||||
|
|
||||||
|
public function setTranslationDomain($translationDomain);
|
||||||
|
|
||||||
|
public function setInvalidValue($invalidValue);
|
||||||
|
|
||||||
|
public function setPluralization($pluralization);
|
||||||
|
|
||||||
|
public function setCode($code);
|
||||||
|
|
||||||
|
public function addViolation();
|
||||||
|
}
|
Reference in New Issue
Block a user