[Validator] Made tests green (yay!)

This commit is contained in:
Bernhard Schussek 2014-02-18 13:03:34 +01:00
parent 680f1ee6c7
commit 8ae68c9543
27 changed files with 695 additions and 121 deletions

View File

@ -11,12 +11,16 @@
namespace Symfony\Component\Validator\Context;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\ClassBasedInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Group\GroupManagerInterface;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\Util\PropertyPath;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
/**
* @since %%NextVersion%%
@ -48,18 +52,30 @@ class ExecutionContext implements ExecutionContextInterface
*/
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->groupManager = $groupManager;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
$this->violations = new ConstraintViolationList();
$this->nodeStack = new \SplStack();
}
public function pushNode(Node $node)
{
if (null === $this->node) {
$this->root = $node->value;
} else {
if (null !== $this->node) {
$this->nodeStack->push($this->node);
}
@ -89,13 +105,32 @@ class ExecutionContext implements ExecutionContextInterface
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()
@ -141,15 +176,7 @@ class ExecutionContext implements ExecutionContextInterface
{
$propertyPath = $this->node ? $this->node->propertyPath : '';
if (strlen($subPath) > 0) {
if ('[' === $subPath{1}) {
return $propertyPath.$subPath;
}
return $propertyPath ? $propertyPath.'.'.$subPath : $subPath;
}
return $propertyPath;
return PropertyPath::append($propertyPath, $subPath);
}
/**

View File

@ -30,11 +30,11 @@ interface ExecutionContextInterface
* Adds a violation at the current node of the validation graph.
*
* @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
*/
public function addViolation($message, array $params = array());
public function addViolation($message, array $parameters = array());
public function buildViolation($message);

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Context;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Group\GroupManagerInterface;
use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\NodeVisitor\AbstractVisitor;
@ -42,9 +43,21 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
*/
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->translator = $translator;
$this->translationDomain = $translationDomain;
$this->contextStack = new \SplStack();
}
@ -53,13 +66,23 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
$this->validator = $validator;
}
public function startContext()
public function startContext($root)
{
if (null === $this->validator) {
// TODO error, call initialize() first
}
if (null !== $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;
}
@ -100,7 +123,7 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
public function enterNode(Node $node)
{
if (null === $this->currentContext) {
// error no context started
// TODO error call startContext() first
}
$this->currentContext->pushNode($node);

View File

@ -18,9 +18,11 @@ namespace Symfony\Component\Validator\Context;
interface ExecutionContextManagerInterface
{
/**
* @param mixed $root
*
* @return ExecutionContextInterface The started context
*/
public function startContext();
public function startContext($root);
/**
* @return ExecutionContextInterface The stopped context

View File

@ -11,7 +11,12 @@
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\Group\GroupManagerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface;
/**
* @since %%NextVersion%%
@ -19,23 +24,84 @@ use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionCont
*/
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)
{
// TODO handle $traverse and $deep
return $this
->getValidator()
->inContext($this)
->atPath($subPath)
->validateObject($value, $groups)
;
}
public function validateValue($value, $constraints, $subPath = '', $groups = null)
{
return $this
->getValidator()
->inContext($this)
->atPath($subPath)
->validateValue($value, $constraints, $groups)
;
}
public function getMetadataFactory()
{
return $this->getValidator()->getMetadataFactory();
}
}

View File

@ -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()
{
}
}

View File

@ -26,7 +26,7 @@ use Symfony\Component\Validator\Exception\GroupDefinitionException;
* @author Bernhard Schussek <bschussek@gmail.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
@ -63,6 +63,8 @@ class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassB
*/
public $groupSequenceProvider = false;
public $traversalStrategy = TraversalStrategy::IMPLICIT;
/**
* @var \ReflectionClass
*/
@ -423,4 +425,19 @@ class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassB
{
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;
}
}

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint;
abstract class ElementMetadata
abstract class ElementMetadata implements MetadataInterface
{
/**
* @var Constraint[]

View File

@ -12,20 +12,18 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\ValidationVisitorInterface;
use Symfony\Component\Validator\ClassBasedInterface;
use Symfony\Component\Validator\PropertyMetadataInterface;
use Symfony\Component\Validator\PropertyMetadataInterface as LegacyPropertyMetadataInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Valid;
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 $name;
public $property;
public $cascaded = false;
public $collectionCascaded = false;
public $collectionCascadedDeeply = false;
public $cascadingStrategy = CascadingStrategy::NONE;
public $traversalStrategy = TraversalStrategy::IMPLICIT;
private $reflMember = array();
/**
@ -64,10 +62,15 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
}
if ($constraint instanceof Valid) {
$this->cascaded = true;
/* @var Valid $constraint */
$this->collectionCascaded = $constraint->traverse;
$this->collectionCascadedDeeply = $constraint->deep;
$this->cascadingStrategy = CascadingStrategy::CASCADE;
if ($constraint->traverse) {
$this->traversalStrategy = TraversalStrategy::TRAVERSE;
}
if ($constraint->deep) {
$this->traversalStrategy |= TraversalStrategy::RECURSIVE;
}
} else {
parent::addConstraint($constraint);
}
@ -86,9 +89,8 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
'class',
'name',
'property',
'cascaded',
'collectionCascaded',
'collectionCascadedDeeply',
'cascadingStrategy',
'traversalStrategy',
));
}
@ -158,6 +160,16 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
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
*
@ -165,7 +177,7 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
*/
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()
{
return $this->collectionCascaded;
return $this->traversalStrategy & TraversalStrategy::TRAVERSE;
}
/**
@ -187,7 +199,7 @@ abstract class MemberMetadata extends ElementMetadata implements PropertyMetadat
*/
public function isCollectionCascadedDeeply()
{
return $this->collectionCascadedDeeply;
return $this->traversalStrategy & TraversalStrategy::RECURSIVE;
}
/**

View File

@ -53,5 +53,7 @@ interface MetadataInterface
*/
public function findConstraints($group);
public function supportsCascading();
public function getCascadingStrategy();
public function getTraversalStrategy();
}

View File

@ -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()
{
}
}

View File

@ -11,35 +11,48 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ValidatorException;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValueMetadata implements MetadataInterface
class ValueMetadata extends ElementMetadata
{
/**
* 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)
public function __construct(array $constraints)
{
foreach ($constraints as $constraint) {
if ($constraint instanceof Valid) {
// Why can't the Valid constraint be executed directly?
//
// It cannot be executed like regular other constraints, because regular
// 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 supportsIteration()
{
}
public function supportsRecursiveIteration()
public function getTraversalStrategy()
{
}

View File

@ -24,7 +24,7 @@ class ClassNode extends Node
*/
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)) {
// error
@ -34,7 +34,8 @@ class ClassNode extends Node
$value,
$metadata,
$propertyPath,
$groups
$groups,
$cascadedGroups
);
}

View File

@ -27,11 +27,14 @@ abstract class Node
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->metadata = $metadata;
$this->propertyPath = $propertyPath;
$this->groups = $groups;
$this->cascadedGroups = $cascadedGroups;
}
}

View File

@ -24,13 +24,14 @@ class PropertyNode extends Node
*/
public $metadata;
public function __construct($value, PropertyMetadataInterface $metadata, $propertyPath, array $groups)
public function __construct($value, PropertyMetadataInterface $metadata, $propertyPath, array $groups, array $cascadedGroups)
{
parent::__construct(
$value,
$metadata,
$propertyPath,
$groups
$groups,
$cascadedGroups
);
}

View File

@ -12,6 +12,9 @@
namespace Symfony\Component\Validator\NodeTraverser;
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\Node\ClassNode;
use Symfony\Component\Validator\Node\Node;
@ -98,15 +101,25 @@ class NodeTraverser implements NodeTraverserInterface
// Stop the traversal, but execute the leaveNode() methods anyway to
// perform possible cleanups
if (!$stopTraversal && is_object($node->value) && $node->metadata->supportsCascading()) {
$classMetadata = $this->metadataFactory->getMetadataFor($node->value);
if (!$stopTraversal && null !== $node->value) {
$cascadingStrategy = $node->metadata->getCascadingStrategy();
$traversalStrategy = $node->metadata->getTraversalStrategy();
$this->traverseClassNode(new ClassNode(
$node->value,
$classMetadata,
$node->propertyPath,
$node->groups
));
if (is_array($node->value)) {
$this->cascadeCollection(
$node->value,
$node->propertyPath,
$node->cascadedGroups,
$traversalStrategy
);
} elseif ($cascadingStrategy & CascadingStrategy::CASCADE) {
$this->cascadeObject(
$node->value,
$node->propertyPath,
$node->cascadedGroups,
$traversalStrategy
);
}
}
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;
@ -135,14 +148,89 @@ class NodeTraverser implements NodeTraverserInterface
$node->propertyPath
? $node->propertyPath.'.'.$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) {
$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
);
}
}
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\NodeVisitor;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Node\ClassNode;
use Symfony\Component\Validator\Node\Node;
@ -31,7 +32,11 @@ class GroupSequenceResolver extends AbstractVisitor
$groupSequence = $node->metadata->getGroupSequence();
} elseif ($node->metadata->isGroupSequenceProvider()) {
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
$groupSequence = $value->getGroupSequence();
$groupSequence = $node->value->getGroupSequence();
if (!$groupSequence instanceof GroupSequence) {
$groupSequence = new GroupSequence($groupSequence);
}
} else {
return;
}
@ -43,7 +48,7 @@ class GroupSequenceResolver extends AbstractVisitor
$node->groups[$key] = $groupSequence;
// Cascade the "Default" group when validating the sequence
$node->groups[$key]->cascadedGroup = Constraint::DEFAULT_GROUP;
$groupSequence->cascadedGroup = Constraint::DEFAULT_GROUP;
}
}
}

View File

@ -75,7 +75,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
if ($node instanceof ClassNode) {
$objectHash = spl_object_hash($node->value);
$this->objectHashStack->push($objectHash);
} elseif ($node instanceof PropertyNode) {
} elseif ($node instanceof PropertyNode && count($this->objectHashStack) > 0) {
$objectHash = $this->objectHashStack->top();
} else {
$objectHash = null;
@ -112,10 +112,8 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
continue;
}
// Only traverse group sequences at class, not at property level
if (!$node instanceof ClassNode) {
continue;
}
// Skip the group sequence when validating properties
unset($node->groups[$key]);
// Traverse group sequence until a violation is generated
$this->traverseGroupSequence($node, $group);
@ -130,24 +128,29 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
return true;
}
public function leaveNode(Node $node)
{
if ($node instanceof ClassNode) {
$this->objectHashStack->pop();
}
}
public function getCurrentGroup()
{
return $this->currentGroup;
}
private function traverseGroupSequence(ClassNode $node, GroupSequence $groupSequence)
private function traverseGroupSequence(Node $node, GroupSequence $groupSequence)
{
$context = $this->contextManager->getCurrentContext();
$violationCount = count($context->getViolations());
foreach ($groupSequence->groups as $groupInSequence) {
$this->nodeTraverser->traverse(array(new ClassNode(
$node->value,
$node->metadata,
$node->propertyPath,
array($groupInSequence),
array($groupSequence->cascadedGroup ?: $groupInSequence)
)));
$node = clone $node;
$node->groups = array($groupInSequence);
$node->cascadedGroups = array($groupSequence->cascadedGroup ?: $groupInSequence);
$this->nodeTraverser->traverse(array($node));
// Abort sequence validation if a violation was generated
if (count($context->getViolations()) > $violationCount) {

View File

@ -9,7 +9,7 @@
* 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\Constraints\Callback;
@ -788,7 +788,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
$test->assertNull($context->getPropertyName());
$test->assertSame('', $context->getPropertyPath());
$test->assertSame('Group', $context->getGroup());
$test->assertNull($context->getMetadata());
$test->assertSame($test->metadataFactory, $context->getMetadataFactory());
$test->assertSame('Bernhard', $context->getRoot());
$test->assertSame('Bernhard', $context->getValue());
@ -942,8 +941,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
public function testNoDuplicateValidationIfConstraintInMultipleGroups()
{
$this->markTestSkipped('Currently not supported');
$entity = new Entity();
$callback = function ($value, ExecutionContextInterface $context) {
@ -963,8 +960,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
public function testGroupSequenceAbortsAfterFailedGroup()
{
$this->markTestSkipped('Currently not supported');
$entity = new Entity();
$callback1 = function ($value, ExecutionContextInterface $context) {
@ -997,8 +992,6 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
public function testGroupSequenceIncludesReferences()
{
$this->markTestSkipped('Currently not supported');
$entity = new Entity();
$entity->reference = new Reference();

View File

@ -9,17 +9,32 @@
* 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\Validator;
use Symfony\Component\Validator\DefaultTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory;
class ValidatorTest extends AbstractValidatorTest
class LegacyValidatorTest extends AbstractValidatorTest
{
protected function createValidator(MetadataFactoryInterface $metadataFactory)
{
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');
}
}

View File

@ -15,19 +15,20 @@ use Symfony\Component\Validator\DefaultTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Context\ExecutionContextManager;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolver;
use Symfony\Component\Validator\NodeVisitor\NodeValidator;
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
use Symfony\Component\Validator\Tests\AbstractValidatorTest;
use Symfony\Component\Validator\Validator\Validator;
use Symfony\Component\Validator\Validator\LegacyValidator;
class TraversingValidatorTest extends AbstractValidatorTest
class ValidatorTest extends AbstractValidatorTest
{
protected function createValidator(MetadataFactoryInterface $metadataFactory)
{
$nodeTraverser = new NodeTraverser($metadataFactory);
$nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory());
$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
// contexts
@ -37,6 +38,7 @@ class TraversingValidatorTest extends AbstractValidatorTest
// context to the constraint validators
$nodeValidator->initialize($contextManager);
$nodeTraverser->addVisitor($groupSequenceResolver);
$nodeTraverser->addVisitor($contextManager);
$nodeTraverser->addVisitor($nodeValidator);

View 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()
{
}
}

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\Constraint;
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\ValueMetadata;
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\ValueNode;
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
use Symfony\Component\Validator\Util\PropertyPath;
/**
* @since %%NextVersion%%
@ -75,15 +78,22 @@ abstract class AbstractValidator implements ValidatorInterface
$classMetadata = $this->metadataFactory->getMetadataFor($object);
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(
$object,
$classMetadata,
$this->defaultPropertyPath,
// TODO use cascade group here
$groups ? $this->normalizeGroups($groups) : $this->defaultGroups
$groups,
$groups
)));
}
@ -92,7 +102,12 @@ abstract class AbstractValidator implements ValidatorInterface
$classMetadata = $this->metadataFactory->getMetadataFor($object);
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);
@ -105,7 +120,8 @@ abstract class AbstractValidator implements ValidatorInterface
$nodes[] = new PropertyNode(
$propertyValue,
$propertyMetadata,
$this->defaultPropertyPath,
PropertyPath::append($this->defaultPropertyPath, $propertyName),
$groups,
$groups
);
}
@ -118,7 +134,12 @@ abstract class AbstractValidator implements ValidatorInterface
$classMetadata = $this->metadataFactory->getMetadataFor($object);
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);
@ -129,7 +150,8 @@ abstract class AbstractValidator implements ValidatorInterface
$nodes[] = new PropertyNode(
$value,
$propertyMetadata,
$this->defaultPropertyPath,
PropertyPath::append($this->defaultPropertyPath, $propertyName),
$groups,
$groups
);
}
@ -139,13 +161,19 @@ abstract class AbstractValidator implements ValidatorInterface
protected function traverseValue($value, $constraints, $groups = null)
{
if (!is_array($constraints)) {
$constraints = array($constraints);
}
$metadata = new ValueMetadata($constraints);
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
$this->nodeTraverser->traverse(array(new ValueNode(
$value,
$metadata,
$this->defaultPropertyPath,
$groups ? $this->normalizeGroups($groups) : $this->defaultGroups
$groups,
$groups
)));
}

View File

@ -35,7 +35,7 @@ class Validator extends AbstractValidator
public function validateObject($object, $groups = null)
{
$this->contextManager->startContext();
$this->contextManager->startContext($object);
$this->traverseObject($object, $groups);
@ -44,7 +44,7 @@ class Validator extends AbstractValidator
public function validateProperty($object, $propertyName, $groups = null)
{
$this->contextManager->startContext();
$this->contextManager->startContext($object);
$this->traverseProperty($object, $propertyName, $groups);
@ -53,7 +53,7 @@ class Validator extends AbstractValidator
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
{
$this->contextManager->startContext();
$this->contextManager->startContext($object);
$this->traversePropertyValue($object, $propertyName, $value, $groups);
@ -62,7 +62,7 @@ class Validator extends AbstractValidator
public function validateValue($value, $constraints, $groups = null)
{
$this->contextManager->startContext();
$this->contextManager->startContext($value);
$this->traverseValue($value, $constraints, $groups);

View File

@ -35,6 +35,8 @@ interface ValidatorInterface
*/
public function validateObject($object, $groups = null);
// public function validateCollection($collection, $groups = null);
/**
* Validates a property of a value against its current value.
*

View File

@ -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
));
}
}

View File

@ -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();
}