From a6ed4cae5daeebf9dec20bba359f0f1c9be9c178 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 17 Feb 2014 14:12:00 +0100 Subject: [PATCH] [Validator] Prototype of the traverser implementation --- .../Validator/Constraints/GroupSequence.php | 18 ++ .../Validator/Context/ExecutionContext.php | 174 ++++++++++++++++++ .../Context/ExecutionContextInterface.php | 154 ++++++++++++++++ .../Context/ExecutionContextManager.php | 130 +++++++++++++ .../ExecutionContextManagerInterface.php | 34 ++++ .../Context/LegacyExecutionContext.php | 41 +++++ .../Validator/Group/GroupManagerInterface.php | 21 +++ .../Mapping/ClassMetadataInterface.php | 44 +++++ .../Validator/Mapping/MetadataInterface.php | 57 ++++++ .../Mapping/PropertyMetadataInterface.php | 46 +++++ .../Validator/Mapping/ValueMetadata.php | 46 +++++ .../Component/Validator/Node/ClassNode.php | 41 +++++ src/Symfony/Component/Validator/Node/Node.php | 37 ++++ .../Component/Validator/Node/PropertyNode.php | 37 ++++ .../Component/Validator/Node/ValueNode.php | 20 ++ .../NodeTraverser/AbstractVisitor.php | 37 ++++ .../Validator/NodeTraverser/NodeTraverser.php | 166 +++++++++++++++++ .../NodeTraverser/NodeTraverserInterface.php | 32 ++++ .../NodeTraverser/NodeVisitorInterface.php | 29 +++ .../Validator/TraversingValidatorTest.php | 41 +++++ .../Validator/Validator/AbstractValidator.php | 155 ++++++++++++++++ .../Validator/ContextualValidator.php | 123 +++++++++++++ .../ContextualValidatorInterface.php | 26 +++ .../Validator/Validator/LegacyValidator.php | 27 +++ .../Validator/Validator/NodeValidator.php | 155 ++++++++++++++++ .../Validator/Validator/Validator.php | 72 ++++++++ .../Validator/ValidatorInterface.php | 88 +++++++++ 27 files changed, 1851 insertions(+) create mode 100644 src/Symfony/Component/Validator/Context/ExecutionContext.php create mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextInterface.php create mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextManager.php create mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php create mode 100644 src/Symfony/Component/Validator/Context/LegacyExecutionContext.php create mode 100644 src/Symfony/Component/Validator/Group/GroupManagerInterface.php create mode 100644 src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php create mode 100644 src/Symfony/Component/Validator/Mapping/MetadataInterface.php create mode 100644 src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php create mode 100644 src/Symfony/Component/Validator/Mapping/ValueMetadata.php create mode 100644 src/Symfony/Component/Validator/Node/ClassNode.php create mode 100644 src/Symfony/Component/Validator/Node/Node.php create mode 100644 src/Symfony/Component/Validator/Node/PropertyNode.php create mode 100644 src/Symfony/Component/Validator/Node/ValueNode.php create mode 100644 src/Symfony/Component/Validator/NodeTraverser/AbstractVisitor.php create mode 100644 src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php create mode 100644 src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php create mode 100644 src/Symfony/Component/Validator/NodeTraverser/NodeVisitorInterface.php create mode 100644 src/Symfony/Component/Validator/Tests/Validator/TraversingValidatorTest.php create mode 100644 src/Symfony/Component/Validator/Validator/AbstractValidator.php create mode 100644 src/Symfony/Component/Validator/Validator/ContextualValidator.php create mode 100644 src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php create mode 100644 src/Symfony/Component/Validator/Validator/LegacyValidator.php create mode 100644 src/Symfony/Component/Validator/Validator/NodeValidator.php create mode 100644 src/Symfony/Component/Validator/Validator/Validator.php create mode 100644 src/Symfony/Component/Validator/Validator/ValidatorInterface.php diff --git a/src/Symfony/Component/Validator/Constraints/GroupSequence.php b/src/Symfony/Component/Validator/Constraints/GroupSequence.php index 61f72c6231..7985b6cc97 100644 --- a/src/Symfony/Component/Validator/Constraints/GroupSequence.php +++ b/src/Symfony/Component/Validator/Constraints/GroupSequence.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Constraints; +use Traversable; + /** * Annotation for group sequences * @@ -28,6 +30,22 @@ class GroupSequence implements \ArrayAccess, \IteratorAggregate, \Countable */ public $groups; + /** + * The group under which cascaded objects are validated when validating + * this sequence. + * + * By default, cascaded objects are validated in each of the groups of + * the sequence. + * + * If a class has a group sequence attached, that sequence replaces the + * "Default" group. When validating that class in the "Default" group, the + * group sequence is used instead, but still the "Default" group should be + * cascaded to other objects. + * + * @var string|GroupSequence + */ + public $cascadedGroup; + public function __construct(array $groups) { // Support for Doctrine annotations diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php new file mode 100644 index 0000000000..09dc2644e2 --- /dev/null +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\ClassBasedInterface; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Group\GroupManagerInterface; +use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; +use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Node\Node; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class ExecutionContext implements ExecutionContextInterface +{ + private $root; + + private $violations; + + /** + * @var Node + */ + private $node; + + /** + * @var \SplStack + */ + private $nodeStack; + + /** + * @var MetadataFactoryInterface + */ + private $metadataFactory; + + /** + * @var ValidatorInterface + */ + private $validator; + + /** + * @var GroupManagerInterface + */ + private $groupManager; + + public function __construct(MetadataFactoryInterface $metadataFactory, ValidatorInterface $validator, GroupManagerInterface $groupManager) + { + $this->metadataFactory = $metadataFactory; + $this->validator = $validator; + $this->groupManager = $groupManager; + $this->violations = new ConstraintViolationList(); + } + + public function pushNode(Node $node) + { + if (null === $this->node) { + $this->root = $node->value; + } else { + $this->nodeStack->push($this->node); + } + + $this->node = $node; + } + + public function popNode() + { + $poppedNode = $this->node; + + if (0 === count($this->nodeStack)) { + $this->node = null; + + return $poppedNode; + } + + if (1 === count($this->nodeStack)) { + $this->nodeStack->pop(); + $this->node = null; + + return $poppedNode; + } + + $this->nodeStack->pop(); + $this->node = $this->nodeStack->top(); + + return $poppedNode; + } + + public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null) + { + } + + public function buildViolation($message) + { + + } + + public function getMetadataFor($object) + { + + } + + public function getViolations() + { + return $this->violations; + } + + public function getRoot() + { + return $this->root; + } + + public function getValue() + { + return $this->node ? $this->node->value : null; + } + + public function getMetadata() + { + return $this->node ? $this->node->metadata : null; + } + + public function getGroup() + { + return $this->groupManager->getCurrentGroup(); + } + + public function getClassName() + { + $metadata = $this->getMetadata(); + + return $metadata instanceof ClassBasedInterface ? $metadata->getClassName() : null; + } + + public function getPropertyName() + { + $metadata = $this->getMetadata(); + + return $metadata instanceof PropertyMetadataInterface ? $metadata->getPropertyName() : null; + } + + public function getPropertyPath($subPath = '') + { + $propertyPath = $this->node ? $this->node->propertyPath : ''; + + if (strlen($subPath) > 0) { + if ('[' === $subPath{1}) { + return $propertyPath.$subPath; + } + + return $propertyPath ? $propertyPath.'.'.$subPath : $subPath; + } + + return $propertyPath; + } + + /** + * @return ValidatorInterface + */ + public function getValidator() + { + return $this->validator; + } +} diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php new file mode 100644 index 0000000000..c7ee62dc23 --- /dev/null +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Mapping\MetadataInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface ExecutionContextInterface +{ + /** + * @return ValidatorInterface + */ + public function getValidator(); + + /** + * 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. + * + * @api + */ + public function addViolation($message, array $params = array()); + + public function buildViolation($message); + + /** + * Returns the violations generated by the validator so far. + * + * @return ConstraintViolationListInterface The constraint violation list. + * + * @api + */ + public function getViolations(); + + /** + * Returns the value at which validation was started in the object graph. + * + * The validator, when given an object, traverses the properties and + * related objects and their properties. The root of the validation is the + * object from which the traversal started. + * + * The current value is returned by {@link getValue}. + * + * @return mixed The root value of the validation. + */ + public function getRoot(); + + /** + * Returns the value that the validator is currently validating. + * + * If you want to retrieve the object that was originally passed to the + * validator, use {@link getRoot}. + * + * @return mixed The currently validated value. + */ + public function getValue(); + + /** + * Returns the metadata for the currently validated value. + * + * With the core implementation, this method returns a + * {@link Mapping\ClassMetadata} instance if the current value is an object, + * a {@link Mapping\PropertyMetadata} instance if the current value is + * the value of a property and a {@link Mapping\GetterMetadata} instance if + * the validated value is the result of a getter method. + * + * If the validated value is neither of these, for example if the validator + * has been called with a plain value and constraint, this method returns + * null. + * + * @return MetadataInterface|null The metadata of the currently validated + * value. + */ + public function getMetadata(); + + public function getMetadataFor($object); + + /** + * Returns the validation group that is currently being validated. + * + * @return string The current validation group. + */ + public function getGroup(); + + /** + * Returns the class name of the current node. + * + * If the metadata of the current node does not implement + * {@link ClassBasedInterface} or if no metadata is available for the + * current node, this method returns null. + * + * @return string|null The class name or null, if no class name could be found. + */ + public function getClassName(); + + /** + * Returns the property name of the current node. + * + * If the metadata of the current node does not implement + * {@link PropertyMetadataInterface} or if no metadata is available for the + * current node, this method returns null. + * + * @return string|null The property name or null, if no property name could be found. + */ + public function getPropertyName(); + + /** + * Returns the property path to the value that the validator is currently + * validating. + * + * For example, take the following object graph: + * + *
+     * (Person)---($address: Address)---($street: string)
+     * 
+ * + * When the Person instance is passed to the validator, the + * property path is initially empty. When the $address property + * of that person is validated, the property path is "address". When + * the $street property of the related Address instance + * is validated, the property path is "address.street". + * + * Properties of objects are prefixed with a dot in the property path. + * Indices of arrays or objects implementing the {@link \ArrayAccess} + * interface are enclosed in brackets. For example, if the property in + * the previous example is $addresses and contains an array + * of Address instance, the property path generated for the + * $street property of one of these addresses is for example + * "addresses[0].street". + * + * @param string $subPath Optional. The suffix appended to the current + * property path. + * + * @return string The current property path. The result may be an empty + * string if the validator is currently validating the + * root value of the validation graph. + */ + public function getPropertyPath($subPath = ''); +} diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextManager.php b/src/Symfony/Component/Validator/Context/ExecutionContextManager.php new file mode 100644 index 0000000000..08442b94e8 --- /dev/null +++ b/src/Symfony/Component/Validator/Context/ExecutionContextManager.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\Group\GroupManagerInterface; +use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Node\Node; +use Symfony\Component\Validator\NodeTraverser\AbstractVisitor; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class ExecutionContextManager extends AbstractVisitor implements ExecutionContextManagerInterface +{ + /** + * @var MetadataFactoryInterface + */ + private $metadataFactory; + + /** + * @var GroupManagerInterface + */ + private $groupManager; + + /** + * @var ValidatorInterface + */ + private $validator; + + /** + * @var ExecutionContext + */ + private $currentContext; + + /** + * @var \SplStack|ExecutionContext[] + */ + private $contextStack; + + public function __construct(MetadataFactoryInterface $metadataFactory, GroupManagerInterface $groupManager) + { + $this->metadataFactory = $metadataFactory; + $this->groupManager = $groupManager; + + $this->reset(); + } + + public function initialize(ValidatorInterface $validator) + { + $this->validator = $validator; + } + + public function startContext() + { + if (null !== $this->currentContext) { + $this->contextStack->push($this->currentContext); + } + + $this->currentContext = new ExecutionContext($this->metadataFactory, $this->validator, $this->groupManager); + + return $this->currentContext; + } + + public function stopContext() + { + $stoppedContext = $this->currentContext; + + if (0 === count($this->contextStack)) { + $this->currentContext = null; + + return $stoppedContext; + } + + if (1 === count($this->contextStack)) { + $this->contextStack->pop(); + $this->currentContext = null; + + return $stoppedContext; + } + + $this->contextStack->pop(); + $this->currentContext = $this->contextStack->top(); + + return $stoppedContext; + } + + public function getCurrentContext() + { + return $this->currentContext; + } + + public function afterTraversal(array $nodes) + { + $this->reset(); + } + + public function enterNode(Node $node) + { + if (null === $this->currentContext) { + // error no context started + } + + $this->currentContext->pushNode($node); + } + + public function leaveNode(Node $node) + { + if (null === $this->currentContext) { + // error no context started + } + + $this->currentContext->popNode(); + } + + private function reset() + { + $this->contextStack = new \SplStack(); + } +} diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php new file mode 100644 index 0000000000..0d79eb43bb --- /dev/null +++ b/src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface ExecutionContextManagerInterface +{ + /** + * @return ExecutionContextInterface The started context + */ + public function startContext(); + + /** + * @return ExecutionContextInterface The stopped context + */ + public function stopContext(); + + /** + * @return ExecutionContextInterface The current context + */ + public function getCurrentContext(); +} diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php new file mode 100644 index 0000000000..1981e0f00e --- /dev/null +++ b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class LegacyExecutionContext extends ExecutionContext implements LegacyExecutionContextInterface +{ + public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null) + { + + } + + public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false) + { + + } + + public function validateValue($value, $constraints, $subPath = '', $groups = null) + { + + } + + public function getMetadataFactory() + { + + } +} diff --git a/src/Symfony/Component/Validator/Group/GroupManagerInterface.php b/src/Symfony/Component/Validator/Group/GroupManagerInterface.php new file mode 100644 index 0000000000..94a0ab9544 --- /dev/null +++ b/src/Symfony/Component/Validator/Group/GroupManagerInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Group; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface GroupManagerInterface +{ + public function getCurrentGroup(); +} diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php b/src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php new file mode 100644 index 0000000000..fea3f7f1d7 --- /dev/null +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\ClassBasedInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface ClassMetadataInterface extends MetadataInterface, ClassBasedInterface +{ + public function getConstrainedProperties(); + + public function hasPropertyMetadata($property); + + /** + * Returns all metadata instances for the given named property. + * + * If your implementation does not support properties, simply throw an + * exception in this method (for example a BadMethodCallException). + * + * @param string $property The property name. + * + * @return PropertyMetadataInterface[] A list of metadata instances. Empty if + * no metadata exists for the property. + */ + public function getPropertyMetadata($property); + + public function hasGroupSequence(); + + public function getGroupSequence(); + + public function isGroupSequenceProvider(); +} diff --git a/src/Symfony/Component/Validator/Mapping/MetadataInterface.php b/src/Symfony/Component/Validator/Mapping/MetadataInterface.php new file mode 100644 index 0000000000..3df0d9bc0d --- /dev/null +++ b/src/Symfony/Component/Validator/Mapping/MetadataInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +/** + * A container for validation metadata. + * + * The container contains constraints that may belong to different validation + * groups. Constraints for a specific group can be fetched by calling + * {@link findConstraints}. + * + * Implement this interface to add validation metadata to your own metadata + * layer. Each metadata may have named properties. Each property can be + * represented by one or more {@link PropertyMetadataInterface} instances that + * are returned by {@link getPropertyMetadata}. Since + * PropertyMetadataInterface inherits from MetadataInterface, + * each property may be divided into further properties. + * + * The {@link accept} method of each metadata implements the Visitor pattern. + * The method should forward the call to the visitor's + * {@link ValidationVisitorInterface::visit} method and additionally call + * accept() on all structurally related metadata instances. + * + * For example, to store constraints for PHP classes and their properties, + * create a class ClassMetadata (implementing MetadataInterface) + * and a class PropertyMetadata (implementing PropertyMetadataInterface). + * ClassMetadata::getPropertyMetadata($property) returns all + * PropertyMetadata instances for a property of that class. Its + * accept()-method simply forwards to ValidationVisitorInterface::visit() + * and calls accept() on all contained PropertyMetadata + * instances, which themselves call ValidationVisitorInterface::visit() + * again. + * + * @author Bernhard Schussek + */ +interface MetadataInterface +{ + /** + * 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 supportsCascading(); +} diff --git a/src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php b/src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php new file mode 100644 index 0000000000..78da11b907 --- /dev/null +++ b/src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping; + +use Symfony\Component\Validator\ClassBasedInterface; + +/** + * A container for validation metadata of a property. + * + * What exactly you define as "property" is up to you. The validator expects + * implementations of {@link MetadataInterface} that contain constraints and + * optionally a list of named properties that also have constraints (and may + * have further sub properties). Such properties are mapped by implementations + * of this interface. + * + * @author Bernhard Schussek + * + * @see MetadataInterface + */ +interface PropertyMetadataInterface extends MetadataInterface, ClassBasedInterface +{ + /** + * Returns the name of the property. + * + * @return string The property name. + */ + public function getPropertyName(); + + /** + * Extracts the value of the property from the given object. + * + * @param mixed $object The object to extract the property value from. + * + * @return mixed The value of the property. + */ + public function getPropertyValue($object); +} diff --git a/src/Symfony/Component/Validator/Mapping/ValueMetadata.php b/src/Symfony/Component/Validator/Mapping/ValueMetadata.php new file mode 100644 index 0000000000..c51a6fa575 --- /dev/null +++ b/src/Symfony/Component/Validator/Mapping/ValueMetadata.php @@ -0,0 +1,46 @@ + + * + * 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 + */ +class ValueMetadata implements MetadataInterface +{ + /** + * 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 supportsCascading() + { + + } + + public function supportsIteration() + { + + } + + public function supportsRecursiveIteration() + { + + } +} diff --git a/src/Symfony/Component/Validator/Node/ClassNode.php b/src/Symfony/Component/Validator/Node/ClassNode.php new file mode 100644 index 0000000000..dfb06dbc03 --- /dev/null +++ b/src/Symfony/Component/Validator/Node/ClassNode.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Node; + +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class ClassNode extends Node +{ + /** + * @var ClassMetadataInterface + */ + public $metadata; + + public function __construct($value, ClassMetadataInterface $metadata, $propertyPath, array $groups) + { + if (!is_object($value)) { + // error + } + + parent::__construct( + $value, + $metadata, + $propertyPath, + $groups + ); + } + +} diff --git a/src/Symfony/Component/Validator/Node/Node.php b/src/Symfony/Component/Validator/Node/Node.php new file mode 100644 index 0000000000..3dead5623d --- /dev/null +++ b/src/Symfony/Component/Validator/Node/Node.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Node; + +use Symfony\Component\Validator\Mapping\MetadataInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +abstract class Node +{ + public $value; + + public $metadata; + + public $propertyPath; + + public $groups; + + public function __construct($value, MetadataInterface $metadata, $propertyPath, array $groups) + { + $this->value = $value; + $this->metadata = $metadata; + $this->propertyPath = $propertyPath; + $this->groups = $groups; + } +} diff --git a/src/Symfony/Component/Validator/Node/PropertyNode.php b/src/Symfony/Component/Validator/Node/PropertyNode.php new file mode 100644 index 0000000000..9424acb59f --- /dev/null +++ b/src/Symfony/Component/Validator/Node/PropertyNode.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Node; + +use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class PropertyNode extends Node +{ + /** + * @var PropertyMetadataInterface + */ + public $metadata; + + public function __construct($value, PropertyMetadataInterface $metadata, $propertyPath, array $groups) + { + parent::__construct( + $value, + $metadata, + $propertyPath, + $groups + ); + } + +} diff --git a/src/Symfony/Component/Validator/Node/ValueNode.php b/src/Symfony/Component/Validator/Node/ValueNode.php new file mode 100644 index 0000000000..e0f77e41f1 --- /dev/null +++ b/src/Symfony/Component/Validator/Node/ValueNode.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Node; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class ValueNode extends Node +{ +} diff --git a/src/Symfony/Component/Validator/NodeTraverser/AbstractVisitor.php b/src/Symfony/Component/Validator/NodeTraverser/AbstractVisitor.php new file mode 100644 index 0000000000..c03e87c18d --- /dev/null +++ b/src/Symfony/Component/Validator/NodeTraverser/AbstractVisitor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\NodeTraverser; + +use Symfony\Component\Validator\Node\Node; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +abstract class AbstractVisitor implements NodeVisitorInterface +{ + public function beforeTraversal(array $nodes) + { + } + + public function afterTraversal(array $nodes) + { + } + + public function enterNode(Node $node) + { + } + + public function leaveNode(Node $node) + { + } +} diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php new file mode 100644 index 0000000000..3d3c7bfe03 --- /dev/null +++ b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\NodeTraverser; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\MetadataFactoryInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class NodeTraverser implements NodeTraverserInterface +{ + /** + * @var NodeVisitorInterface[] + */ + private $visitors; + + /** + * @var MetadataFactoryInterface + */ + private $metadataFactory; + + private $traversalStarted = false; + + public function __construct(MetadataFactoryInterface $metadataFactory) + { + $this->visitors = new \SplObjectStorage(); + $this->metadataFactory = $metadataFactory; + } + + public function addVisitor(NodeVisitorInterface $visitor) + { + $this->visitors->attach($visitor); + } + + public function removeVisitor(NodeVisitorInterface $visitor) + { + $this->visitors->detach($visitor); + } + + /** + * {@inheritdoc} + */ + public function traverse(array $nodes) + { + $isTopLevelCall = !$this->traversalStarted; + + if ($isTopLevelCall) { + $this->traversalStarted = true; + + foreach ($this->visitors as $visitor) { + /** @var NodeVisitorInterface $visitor */ + $visitor->beforeTraversal($nodes); + } + } + + foreach ($nodes as $node) { + if ($node instanceof ClassNode) { + $this->traverseClassNode($node); + } else { + $this->traverseNode($node); + } + } + + if ($isTopLevelCall) { + $this->traversalStarted = false; + + foreach ($this->visitors as $visitor) { + /** @var NodeVisitorInterface $visitor */ + $visitor->afterTraversal($nodes); + } + } + } + + private function traverseNode(Node $node) + { + $stopTraversal = false; + + foreach ($this->visitors as $visitor) { + if (false === $visitor->enterNode($node)) { + $stopTraversal = true; + } + } + + // 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); + + $this->traverseClassNode(new ClassNode( + $node->value, + $classMetadata, + $node->propertyPath, + $node->groups + )); + } + + foreach ($this->visitors as $visitor) { + $visitor->leaveNode($node); + } + } + + private function traverseClassNode(ClassNode $node) + { + // Replace "Default" group by the group sequence attached to the class + // (if any) + foreach ($node->groups as $key => $group) { + if (Constraint::DEFAULT_GROUP !== $group) { + continue; + } + + if ($node->metadata->hasGroupSequence()) { + $node->groups[$key] = $node->metadata->getGroupSequence(); + } elseif ($node->metadata->isGroupSequenceProvider()) { + /** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */ + $node->groups[$key] = $value->getGroupSequence(); + } + + // Cascade the "Default" group when validating the sequence + $node->groups[$key]->cascadedGroup = Constraint::DEFAULT_GROUP; + + // "Default" group found, abort + break; + } + + $stopTraversal = false; + + foreach ($this->visitors as $visitor) { + if (false === $visitor->enterNode($node)) { + $stopTraversal = true; + } + } + + // Stop the traversal, but execute the leaveNode() methods anyway to + // perform possible cleanups + if (!$stopTraversal) { + foreach ($node->metadata->getConstrainedProperties() as $propertyName) { + foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { + $this->traverseNode(new PropertyNode( + $propertyMetadata->getPropertyValue($node->value), + $propertyMetadata, + $node->propertyPath + ? $node->propertyPath.'.'.$propertyName + : $propertyName, + $node->groups + )); + } + } + } + + foreach ($this->visitors as $visitor) { + $visitor->leaveNode($node); + } + } +} diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php new file mode 100644 index 0000000000..048a1458b4 --- /dev/null +++ b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\NodeTraverser; + +use Symfony\Component\Validator\Node\Node; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface NodeTraverserInterface +{ + public function addVisitor(NodeVisitorInterface $visitor); + + public function removeVisitor(NodeVisitorInterface $visitor); + + /** + * @param Node[] $nodes + * + * @return mixed + */ + public function traverse(array $nodes); +} diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeVisitorInterface.php b/src/Symfony/Component/Validator/NodeTraverser/NodeVisitorInterface.php new file mode 100644 index 0000000000..0a70cc13fe --- /dev/null +++ b/src/Symfony/Component/Validator/NodeTraverser/NodeVisitorInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\NodeTraverser; + +use Symfony\Component\Validator\Node\Node; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface NodeVisitorInterface +{ + public function beforeTraversal(array $nodes); + + public function afterTraversal(array $nodes); + + public function enterNode(Node $node); + + public function leaveNode(Node $node); +} diff --git a/src/Symfony/Component/Validator/Tests/Validator/TraversingValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/TraversingValidatorTest.php new file mode 100644 index 0000000000..4f3a212aa1 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Validator/TraversingValidatorTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Validator; + +use Symfony\Component\Validator\Context\ExecutionContextManager; +use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Tests\AbstractValidatorTest; +use Symfony\Component\Validator\NodeTraverser\NodeTraverser; +use Symfony\Component\Validator\NodeTraverser\NodeVisitor\NodeValidator; +use Symfony\Component\Validator\DefaultTranslator; +use Symfony\Component\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\Validator\Validator; + +class TraversingValidatorTest extends AbstractValidatorTest +{ + protected function createValidator(MetadataFactoryInterface $metadataFactory) + { + $validatorFactory = new ConstraintValidatorFactory(); + $nodeTraverser = new NodeTraverser($metadataFactory); + $nodeValidator = new NodeValidator($validatorFactory, $nodeTraverser); + $contextManager = new ExecutionContextManager($metadataFactory, $nodeValidator, new DefaultTranslator()); + $validator = new Validator($nodeTraverser, $metadataFactory, $contextManager); + + $contextManager->initialize($validator); + $nodeValidator->setContextManager($contextManager); + + $nodeTraverser->addVisitor($contextManager); + $nodeTraverser->addVisitor($nodeValidator); + + return $validator; + } +} diff --git a/src/Symfony/Component/Validator/Validator/AbstractValidator.php b/src/Symfony/Component/Validator/Validator/AbstractValidator.php new file mode 100644 index 0000000000..c0d9f54b52 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/AbstractValidator.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\ValueMetadata; +use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\NodeTraverser\ClassNode; +use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; +use Symfony\Component\Validator\NodeTraverser\PropertyNode; +use Symfony\Component\Validator\NodeTraverser\ValueNode; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +abstract class AbstractValidator implements ValidatorInterface +{ + /** + * @var NodeTraverserInterface + */ + protected $nodeTraverser; + + /** + * @var MetadataFactoryInterface + */ + protected $metadataFactory; + + /** + * @var string + */ + protected $defaultPropertyPath = ''; + + protected $defaultGroups = array(Constraint::DEFAULT_GROUP); + + public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory) + { + $this->nodeTraverser = $nodeTraverser; + $this->metadataFactory = $metadataFactory; + } + + /** + * @param ExecutionContextInterface $context + * + * @return ContextualValidatorInterface + */ + public function inContext(ExecutionContextInterface $context) + { + return new ContextualValidator($this->nodeTraverser, $this->metadataFactory, $context); + } + + public function getMetadataFactory() + { + return $this->metadataFactory; + } + + protected function traverseObject($object, $groups = null) + { + $classMetadata = $this->metadataFactory->getMetadataFor($object); + + if (!$classMetadata instanceof ClassMetadataInterface) { + // error + } + + $this->nodeTraverser->traverse(array(new ClassNode( + $object, + $classMetadata, + $this->defaultPropertyPath, + // TODO use cascade group here + $groups ? $this->normalizeGroups($groups) : $this->defaultGroups + ))); + } + + protected function traverseProperty($object, $propertyName, $groups = null) + { + $classMetadata = $this->metadataFactory->getMetadataFor($object); + + if (!$classMetadata instanceof ClassMetadataInterface) { + // error + } + + $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + $nodes = array(); + + foreach ($propertyMetadatas as $propertyMetadata) { + $propertyValue = $propertyMetadata->getPropertyValue($object); + + $nodes[] = new PropertyNode( + $propertyValue, + $propertyMetadata, + $this->defaultPropertyPath, + $groups + ); + } + + $this->nodeTraverser->traverse($nodes); + } + + protected function traversePropertyValue($object, $propertyName, $value, $groups = null) + { + $classMetadata = $this->metadataFactory->getMetadataFor($object); + + if (!$classMetadata instanceof ClassMetadataInterface) { + // error + } + + $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + $nodes = array(); + + foreach ($propertyMetadatas as $propertyMetadata) { + $nodes[] = new PropertyNode( + $value, + $propertyMetadata, + $this->defaultPropertyPath, + $groups + ); + } + + $this->nodeTraverser->traverse($nodes); + } + + protected function traverseValue($value, $constraints, $groups = null) + { + $metadata = new ValueMetadata($constraints); + + $this->nodeTraverser->traverse(array(new ValueNode( + $value, + $metadata, + $this->defaultPropertyPath, + $groups ? $this->normalizeGroups($groups) : $this->defaultGroups + ))); + } + + protected function normalizeGroups($groups) + { + if (is_array($groups)) { + return $groups; + } + + return array($groups); + } +} diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidator.php b/src/Symfony/Component/Validator/Validator/ContextualValidator.php new file mode 100644 index 0000000000..560f088522 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/ContextualValidator.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class ContextualValidator extends AbstractValidator implements ContextualValidatorInterface +{ + /** + * @var ExecutionContextManagerInterface + */ + private $context; + + public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextInterface $context) + { + parent::__construct($nodeTraverser, $metadataFactory); + + $this->context = $context; + $this->defaultPropertyPath = $context->getPropertyPath(); + $this->defaultGroups = array($context->getGroup()); + } + + public function atPath($subPath) + { + $this->defaultPropertyPath = $this->context->getPropertyPath($subPath); + } + + /** + * Validates a value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param mixed $object The value to validate + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validateObject($object, $groups = null) + { + $this->traverseObject($object, $groups); + + return $this->context->getViolations(); + } + + /** + * Validates a property of a value against its current value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param mixed $object The value containing the property. + * @param string $propertyName The name of the property to validate. + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validateProperty($object, $propertyName, $groups = null) + { + $this->traverseProperty($object, $propertyName, $groups); + + return $this->context->getViolations(); + } + + /** + * Validate a property of a value against a potential value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param string $object The value containing the property. + * @param string $propertyName The name of the property to validate + * @param string $value The value to validate against the + * constraints of the property. + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validatePropertyValue($object, $propertyName, $value, $groups = null) + { + $this->traversePropertyValue($object, $propertyName, $value, $groups); + + return $this->context->getViolations(); + } + + /** + * Validates a value against a constraint or a list of constraints. + * + * @param mixed $value The value to validate. + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against. + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validateValue($value, $constraints, $groups = null) + { + $this->traverseValue($value, $constraints, $groups); + + return $this->context->getViolations(); + } +} diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php new file mode 100644 index 0000000000..61de8900f1 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface ContextualValidatorInterface extends ValidatorInterface +{ + /** + * @param $subPath + * + * @return ContextualValidatorInterface + */ + public function atPath($subPath); +} diff --git a/src/Symfony/Component/Validator/Validator/LegacyValidator.php b/src/Symfony/Component/Validator/Validator/LegacyValidator.php new file mode 100644 index 0000000000..2227318289 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/LegacyValidator.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class LegacyValidator extends Validator implements LegacyValidatorInterface +{ + public function validate($value, $groups = null, $traverse = false, $deep = false) + { + // TODO what about $traverse and $deep? + return $this->validateObject($value, $groups); + } +} diff --git a/src/Symfony/Component/Validator/Validator/NodeValidator.php b/src/Symfony/Component/Validator/Validator/NodeValidator.php new file mode 100644 index 0000000000..20177341a8 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/NodeValidator.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; +use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\Group\GroupManagerInterface; +use Symfony\Component\Validator\Node\ClassNode; +use Symfony\Component\Validator\Node\Node; +use Symfony\Component\Validator\NodeTraverser\AbstractVisitor; +use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class NodeValidator extends AbstractVisitor implements GroupManagerInterface +{ + private $validatedNodes = array(); + + /** + * @var ConstraintValidatorFactoryInterface + */ + private $validatorFactory; + + /** + * @var ExecutionContextManagerInterface + */ + private $contextManager; + + /** + * @var NodeTraverserInterface + */ + private $nodeTraverser; + + private $currentGroup; + + public function __construct(ConstraintValidatorFactoryInterface $validatorFactory, NodeTraverserInterface $nodeTraverser) + { + $this->validatorFactory = $validatorFactory; + $this->nodeTraverser = $nodeTraverser; + } + + public function setContextManager(ExecutionContextManagerInterface $contextManager) + { + $this->contextManager = $contextManager; + } + + public function afterTraversal(array $nodes) + { + $this->validatedNodes = array(); + } + + public function enterNode(Node $node) + { + $cacheKey = $node instanceof ClassNode + ? spl_object_hash($node->value) + : null; + + // if group (=[,G3,G4]) contains group sequence (=) + // then call traverse() with each entry of the group sequence and abort + // if necessary (G1, G2) + // finally call traverse() with remaining entries ([G3,G4]) or + // simply continue traversal (if possible) + + foreach ($node->groups as $group) { + // Validate object nodes only once per group + if (null !== $cacheKey) { + // Use the object hash for group sequences + $groupKey = is_object($group) ? spl_object_hash($group) : $group; + + // Exit, if the object is already validated for the current group + if (isset($this->validatedNodes[$cacheKey][$groupKey])) { + return false; + } + + // Remember validating this object before starting and possibly + // traversing the object graph + $this->validatedNodes[$cacheKey][$groupKey] = true; + } + + // Validate group sequence until a violation is generated + if ($group instanceof GroupSequence) { + // Rename for clarity + $groupSequence = $group; + + // Only evaluate group sequences at class, not at property level + if (!$node instanceof ClassNode) { + continue; + } + + $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) + ))); + + // Abort sequence validation if a violation was generated + if (count($context->getViolations()) > $violationCount) { + break; + } + } + + // Optimization: If the groups only contain the group sequence, + // we can skip the traversal for the properties of the object + if (1 === count($node->groups)) { + return false; + } + + // We're done for the current loop execution. + continue; + } + + // Validate normal group (non group sequences) + try { + $this->currentGroup = $group; + + foreach ($node->metadata->findConstraints($group) as $constraint) { + $validator = $this->validatorFactory->getInstance($constraint); + $validator->initialize($this->contextManager->getCurrentContext()); + $validator->validate($node->value, $constraint); + } + + $this->currentGroup = null; + } catch (\Exception $e) { + $this->currentGroup = null; + + throw $e; + } + } + + return true; + } + + public function getCurrentGroup() + { + return $this->currentGroup; + } +} diff --git a/src/Symfony/Component/Validator/Validator/Validator.php b/src/Symfony/Component/Validator/Validator/Validator.php new file mode 100644 index 0000000000..ed222d57c8 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/Validator.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; +use Symfony\Component\Validator\MetadataFactoryInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +class Validator extends AbstractValidator +{ + /** + * @var ExecutionContextManagerInterface + */ + private $contextManager; + + public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextManagerInterface $contextManager) + { + parent::__construct($nodeTraverser, $metadataFactory); + + $this->contextManager = $contextManager; + } + + public function validateObject($object, $groups = null) + { + $this->contextManager->startContext(); + + $this->traverseObject($object, $groups); + + return $this->contextManager->stopContext()->getViolations(); + } + + public function validateProperty($object, $propertyName, $groups = null) + { + $this->contextManager->startContext(); + + $this->traverseProperty($object, $propertyName, $groups); + + return $this->contextManager->stopContext()->getViolations(); + } + + public function validatePropertyValue($object, $propertyName, $value, $groups = null) + { + $this->contextManager->startContext(); + + $this->traversePropertyValue($object, $propertyName, $value, $groups); + + return $this->contextManager->stopContext()->getViolations(); + } + + public function validateValue($value, $constraints, $groups = null) + { + $this->contextManager->startContext(); + + $this->traverseValue($value, $constraints, $groups); + + return $this->contextManager->stopContext()->getViolations(); + } + +} diff --git a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php new file mode 100644 index 0000000000..4f557710c4 --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintViolationListInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; + +/** + * @since %%NextVersion%% + * @author Bernhard Schussek + */ +interface ValidatorInterface +{ + /** + * Validates a value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param mixed $object The value to validate + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validateObject($object, $groups = null); + + /** + * Validates a property of a value against its current value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param mixed $object The value containing the property. + * @param string $propertyName The name of the property to validate. + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validateProperty($object, $propertyName, $groups = null); + + /** + * Validate a property of a value against a potential value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param string $object The value containing the property. + * @param string $propertyName The name of the property to validate + * @param string $value The value to validate against the + * constraints of the property. + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validatePropertyValue($object, $propertyName, $value, $groups = null); + + /** + * Validates a value against a constraint or a list of constraints. + * + * @param mixed $value The value to validate. + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against. + * @param array|null $groups The validation groups to validate. + * + * @return ConstraintViolationListInterface A list of constraint violations. If the + * list is empty, validation succeeded. + */ + public function validateValue($value, $constraints, $groups = null); + + /** + * @param ExecutionContextInterface $context + * + * @return ContextualValidatorInterface + */ + public function inContext(ExecutionContextInterface $context); +}