Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[Validator] Refactored the GraphWalker into an implementation of the …

…Visitor design pattern.

With this refactoring comes a decoupling of the validator from the structure of
the underlying metadata. This way it is possible for Drupal to use the validator
for validating their Entity API by using their own metadata layer, which is not
modeled as classes and properties/getter methods.
  • Loading branch information...
commit a7a50fcb4fc144985d916767f88707ac4d7f5f0f 1 parent 2f2b0d6
@webmozart webmozart authored
Showing with 2,711 additions and 569 deletions.
  1. +21 −0 CHANGELOG.md
  2. +27 −0 ClassBasedInterface.php
  3. +2 −2 ConstraintValidator.php
  4. +2 −2 ConstraintValidatorInterface.php
  5. +73 −23 ConstraintViolation.php
  6. +136 −0 ConstraintViolationInterface.php
  7. +24 −61 ConstraintViolationList.php
  8. +83 −0 ConstraintViolationListInterface.php
  9. +19 −0 Exception/NoSuchMetadataException.php
  10. +231 −43 ExecutionContext.php
  11. +304 −0 ExecutionContextInterface.php
  12. +0 −76 GlobalExecutionContext.php
  13. +68 −0 GlobalExecutionContextInterface.php
  14. +112 −91 GraphWalker.php
  15. +48 −2 Mapping/ClassMetadata.php
  16. +46 −4 Mapping/ClassMetadataFactory.php
  17. +59 −0 Mapping/ClassMetadataFactoryAdapter.php
  18. +13 −0 Mapping/ClassMetadataFactoryInterface.php
  19. +1 −1  Mapping/GetterMetadata.php
  20. +20 −2 Mapping/MemberMetadata.php
  21. +1 −1  Mapping/PropertyMetadata.php
  22. +40 −0 MetadataFactoryInterface.php
  23. +68 −0 MetadataInterface.php
  24. +4 −3 ObjectInitializerInterface.php
  25. +33 −0 PropertyMetadataContainerInterface.php
  26. +44 −0 PropertyMetadataInterface.php
  27. +79 −24 Tests/ExecutionContextTest.php
  28. +2 −2 Tests/Fixtures/ConstraintAValidator.php
  29. +0 −34 Tests/Fixtures/FakeClassMetadataFactory.php
  30. +56 −0 Tests/Fixtures/FakeMetadataFactory.php
  31. +0 −63 Tests/GlobalExecutionContextTest.php
  32. +49 −18 Tests/GraphWalkerTest.php
  33. +1 −1  Tests/Mapping/MemberMetadataTest.php
  34. +508 −0 Tests/ValidationVisitorTest.php
  35. +2 −1  Tests/ValidatorContextTest.php
  36. +2 −1  Tests/ValidatorFactoryTest.php
  37. +56 −15 Tests/ValidatorTest.php
  38. +204 −0 ValidationVisitor.php
  39. +90 −0 ValidationVisitorInterface.php
  40. +114 −66 Validator.php
  41. +8 −2 ValidatorBuilder.php
  42. +5 −3 ValidatorBuilderInterface.php
  43. +13 −1 ValidatorContext.php
  44. +43 −27 ValidatorInterface.php
View
21 CHANGELOG.md
@@ -6,6 +6,27 @@ CHANGELOG
* added a CardScheme validator
* added a Luhn validator
+ * moved @api-tags from `Validator` to `ValidatorInterface`
+ * moved @api-tags from `ConstraintViolation` to the new `ConstraintViolationInterface`
+ * moved @api-tags from `ConstraintViolationList` to the new `ConstraintViolationListInterface`
+ * moved @api-tags from `ExecutionContext` to the new `ExecutionContextInterface`
+ * [BC BREAK] `ConstraintValidatorInterface::initialize` is now type hinted against `ExecutionContextInterface` instead of `ExecutionContext`
+ * [BC BREAK] changed the visibility of the properties in `Validator` from protected to private
+ * deprecated `ClassMetadataFactoryInterface` in favor of the new `MetadataFactoryInterface`
+ * deprecated `ClassMetadataFactory::getClassMetadata` in favor of `getMetadataFor`
+ * created `MetadataInterface`, `PropertyMetadataInterface`, `ClassBasedInterface` and `PropertyMetadataContainerInterface`
+ * deprecated `GraphWalker` in favor of the new `ValidationVisitorInterface`
+ * deprecated `ExecutionContext::addViolationAtPath`
+ * deprecated `ExecutionContext::addViolationAtSubPath` in favor of `ExecutionContextInterface::addViolationAt`
+ * deprecated `ExecutionContext::getCurrentClass` in favor of `ExecutionContextInterface::getClassName`
+ * deprecated `ExecutionContext::getCurrentProperty` in favor of `ExecutionContextInterface::getPropertyName`
+ * deprecated `ExecutionContext::getCurrentValue` in favor of `ExecutionContextInterface::getValue`
+ * deprecated `ExecutionContext::getGraphWalker` in favor of `ExecutionContextInterface::validate` and `ExecutionContextInterface::validateValue`
+ * deprecated `ExecutionContext::getMetadataFactory` in favor of `ExecutionContextInterface::getMetadataFor`
+ * improved `ValidatorInterface::validateValue` to accept arrays of constraints
+ * changed `ValidatorInterface::getMetadataFactory` to return a `MetadataFactoryInterface` instead of a `ClassMetadataFactoryInterface`
+ * removed `ClassMetadataFactoryInterface` type hint from `ValidatorBuilderInterface::setMetadataFactory`.
+ As of Symfony 2.3, this method will be typed against `MetadataFactoryInterface` instead.
2.1.0
-----
View
27 ClassBasedInterface.php
@@ -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;
+
+/**
+ * An object backed by a PHP class.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface ClassBasedInterface
+{
+ /**
+ * Returns the name of the backing PHP class.
+ *
+ * @return string The name of the backing class.
+ */
+ public function getClassName();
+}
View
4 ConstraintValidator.php
@@ -23,7 +23,7 @@
abstract class ConstraintValidator implements ConstraintValidatorInterface
{
/**
- * @var ExecutionContext
+ * @var ExecutionContextInterface
*/
protected $context;
@@ -44,7 +44,7 @@
/**
* {@inheritDoc}
*/
- public function initialize(ExecutionContext $context)
+ public function initialize(ExecutionContextInterface $context)
{
$this->context = $context;
$this->messageTemplate = '';
View
4 ConstraintValidatorInterface.php
@@ -21,9 +21,9 @@
/**
* Initializes the constraint validator.
*
- * @param ExecutionContext $context The current validation context
+ * @param ExecutionContextInterface $context The current validation context
*/
- public function initialize(ExecutionContext $context);
+ public function initialize(ExecutionContextInterface $context);
/**
* Checks if the passed value is valid.
View
96 ConstraintViolation.php
@@ -12,20 +12,64 @@
namespace Symfony\Component\Validator;
/**
- * Represents a single violation of a constraint.
+ * Default implementation of {@ConstraintViolationInterface}.
*
- * @api
+ * @author Bernhard Schussek <bschussek@gmail.com>
*/
-class ConstraintViolation
+class ConstraintViolation implements ConstraintViolationInterface
{
- protected $messageTemplate;
- protected $messageParameters;
- protected $messagePluralization;
- protected $root;
- protected $propertyPath;
- protected $invalidValue;
- protected $code;
+ /**
+ * @var string
+ */
+ private $messageTemplate;
+
+ /**
+ * @var array
+ */
+ private $messageParameters;
+
+ /**
+ * @var integer|null
+ */
+ private $messagePluralization;
+
+ /**
+ * @var mixed
+ */
+ private $root;
+
+ /**
+ * @var string
+ */
+ private $propertyPath;
+
+ /**
+ * @var mixed
+ */
+ private $invalidValue;
+
+ /**
+ * @var mixed
+ */
+ private $code;
+ /**
+ * Creates a new constraint violation.
+ *
+ * @param string $messageTemplate The raw violation message.
+ * @param array $messageParameters The parameters to substitute
+ * in the raw message.
+ * @param mixed $root The value originally passed
+ * to the validator.
+ * @param string $propertyPath The property path from the
+ * root value to the invalid
+ * value.
+ * @param mixed $invalidValue The invalid value causing the
+ * violation.
+ * @param integer|null $messagePluralization The pluralization parameter.
+ * @param mixed $code The error code of the
+ * violation, if any.
+ */
public function __construct($messageTemplate, array $messageParameters, $root, $propertyPath, $invalidValue, $messagePluralization = null, $code = null)
{
$this->messageTemplate = $messageTemplate;
@@ -38,7 +82,9 @@ public function __construct($messageTemplate, array $messageParameters, $root, $
}
/**
- * @return string
+ * Converts the violation into a string for debugging purposes.
+ *
+ * @return string The violation as string.
*/
public function __toString()
{
@@ -58,9 +104,7 @@ public function __toString()
}
/**
- * @return string
- *
- * @api
+ * {@inheritDoc}
*/
public function getMessageTemplate()
{
@@ -68,9 +112,7 @@ public function getMessageTemplate()
}
/**
- * @return array
- *
- * @api
+ * {@inheritDoc}
*/
public function getMessageParameters()
{
@@ -78,7 +120,7 @@ public function getMessageParameters()
}
/**
- * @return integer|null
+ * {@inheritDoc}
*/
public function getMessagePluralization()
{
@@ -86,11 +128,7 @@ public function getMessagePluralization()
}
/**
- * Returns the violation message.
- *
- * @return string
- *
- * @api
+ * {@inheritDoc}
*/
public function getMessage()
{
@@ -105,21 +143,33 @@ public function getMessage()
return strtr($this->messageTemplate, $parameters);
}
+ /**
+ * {@inheritDoc}
+ */
public function getRoot()
{
return $this->root;
}
+ /**
+ * {@inheritDoc}
+ */
public function getPropertyPath()
{
return $this->propertyPath;
}
+ /**
+ * {@inheritDoc}
+ */
public function getInvalidValue()
{
return $this->invalidValue;
}
+ /**
+ * {@inheritDoc}
+ */
public function getCode()
{
return $this->code;
View
136 ConstraintViolationInterface.php
@@ -0,0 +1,136 @@
+<?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;
+
+/**
+ * A violation of a constraint that happened during validation.
+ *
+ * For each constraint that fails during validation one or more violations are
+ * created. The violations store the violation message, the path to the failing
+ * element in the validation graph and the root element that was originally
+ * passed to the validator. For example, take the following graph:
+ *
+ * <pre>
+ * (Person)---(firstName: string)
+ * \
+ * (address: Address)---(street: string)
+ * </pre>
+ *
+ * If the <tt>Person</tt> object is validated and validation fails for the
+ * "firstName" property, the generated violation has the <tt>Person</tt>
+ * instance as root and the property path "firstName". If validation fails
+ * for the "street" property of the related <tt>Address</tt> instance, the root
+ * element is still the person, but the property path is "address.street".
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @api
+ */
+interface ConstraintViolationInterface
+{
+ /**
+ * Returns the violation message.
+ *
+ * @return string The violation message.
+ *
+ * @api
+ */
+ public function getMessage();
+
+ /**
+ * Returns the raw violation message.
+ *
+ * The raw violation message contains placeholders for the parameters
+ * returned by {@link getMessageParameters}. Typically you'll pass the
+ * message template and parameters to a translation engine.
+ *
+ * @return string The raw violation message.
+ *
+ * @api
+ */
+ public function getMessageTemplate();
+
+ /**
+ * Returns the parameters to be inserted into the raw violation message.
+ *
+ * @return array A possibly empty list of parameters indexed by the names
+ * that appear in the message template.
+ *
+ * @see getMessageTemplate
+ *
+ * @api
+ */
+ public function getMessageParameters();
+
+ /**
+ * Returns a number for pluralizing the violation message.
+ *
+ * For example, the message template could have different translation based
+ * on a parameter "choices":
+ *
+ * <ul>
+ * <li>Please select exactly one entry. (choices=1)</li>
+ * <li>Please select two entries. (choices=2)</li>
+ * </ul>
+ *
+ * This method returns the value of the parameter for choosing the right
+ * pluralization form (in this case "choices").
+ *
+ * @return integer|null The number to use to pluralize of the message.
+ */
+ public function getMessagePluralization();
+
+ /**
+ * Returns the root element of the validation.
+ *
+ * @return mixed The value that was passed originally to the validator when
+ * the validation was started. Because the validator traverses
+ * the object graph, the value at which the violation occurs
+ * is not necessarily the value that was originally validated.
+ *
+ * @api
+ */
+ public function getRoot();
+
+ /**
+ * Returns the property path from the root element to the violation.
+ *
+ * @return string The property path indicates how the validator reached
+ * the invalid value from the root element. If the root
+ * element is a <tt>Person</tt> instance with a property
+ * "address" that contains an <tt>Address</tt> instance
+ * with an invalid property "street", the generated property
+ * path is "address.street". Property access is denoted by
+ * dots, while array access is denoted by square brackets,
+ * for example "addresses[1].street".
+ *
+ * @api
+ */
+ public function getPropertyPath();
+
+ /**
+ * Returns the value that caused the violation.
+ *
+ * @return mixed The invalid value that caused the validated constraint to
+ * fail.
+ *
+ * @api
+ */
+ public function getInvalidValue();
+
+ /**
+ * Returns a machine-digestible error code for the violation.
+ *
+ * @return mixed The error code.
+ */
+ public function getCode();
+}
View
85 ConstraintViolationList.php
@@ -12,25 +12,21 @@
namespace Symfony\Component\Validator;
/**
- * A list of ConstrainViolation objects.
+ * Default implementation of {@ConstraintViolationListInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
- *
- * @api
*/
-class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayAccess
+class ConstraintViolationList implements \IteratorAggregate, ConstraintViolationListInterface
{
/**
- * The constraint violations
- *
- * @var array
+ * @var ConstraintViolationInterface[]
*/
- protected $violations = array();
+ private $violations = array();
/**
* Creates a new constraint violation list.
*
- * @param array $violations The constraint violations to add to the list
+ * @param ConstraintViolationInterface[] $violations The constraint violations to add to the list
*/
public function __construct(array $violations = array())
{
@@ -40,7 +36,9 @@ public function __construct(array $violations = array())
}
/**
- * @return string
+ * Converts the violation into a string for debugging purposes.
+ *
+ * @return string The violation as string.
*/
public function __toString()
{
@@ -54,39 +52,25 @@ public function __toString()
}
/**
- * Add a ConstraintViolation to this list.
- *
- * @param ConstraintViolation $violation
- *
- * @api
+ * {@inheritDoc}
*/
- public function add(ConstraintViolation $violation)
+ public function add(ConstraintViolationInterface $violation)
{
$this->violations[] = $violation;
}
/**
- * Merge an existing ConstraintViolationList into this list.
- *
- * @param ConstraintViolationList $otherList
- *
- * @api
+ * {@inheritDoc}
*/
- public function addAll(ConstraintViolationList $otherList)
+ public function addAll(ConstraintViolationListInterface $otherList)
{
- foreach ($otherList->violations as $violation) {
+ foreach ($otherList as $violation) {
$this->violations[] = $violation;
}
}
/**
- * Returns the violation at a given offset.
- *
- * @param integer $offset The offset of the violation.
- *
- * @return ConstraintViolation The violation.
- *
- * @throws \OutOfBoundsException If the offset does not exist.
+ * {@inheritDoc}
*/
public function get($offset)
{
@@ -98,11 +82,7 @@ public function get($offset)
}
/**
- * Returns whether the given offset exists.
- *
- * @param integer $offset The violation offset.
- *
- * @return Boolean Whether the offset exists.
+ * {@inheritDoc}
*/
public function has($offset)
{
@@ -110,20 +90,15 @@ public function has($offset)
}
/**
- * Sets a violation at a given offset.
- *
- * @param integer $offset The violation offset.
- * @param ConstraintViolation $violation The violation.
+ * {@inheritDoc}
*/
- public function set($offset, ConstraintViolation $violation)
+ public function set($offset, ConstraintViolationInterface $violation)
{
$this->violations[$offset] = $violation;
}
/**
- * Removes a violation at a given offset.
- *
- * @param integer $offset The offset to remove.
+ * {@inheritDoc}
*/
public function remove($offset)
{
@@ -131,9 +106,7 @@ public function remove($offset)
}
/**
- * @see IteratorAggregate
- *
- * @api
+ * {@inheritDoc}
*/
public function getIterator()
{
@@ -141,9 +114,7 @@ public function getIterator()
}
/**
- * @see Countable
- *
- * @api
+ * {@inheritDoc}
*/
public function count()
{
@@ -151,9 +122,7 @@ public function count()
}
/**
- * @see ArrayAccess
- *
- * @api
+ * {@inheritDoc}
*/
public function offsetExists($offset)
{
@@ -161,9 +130,7 @@ public function offsetExists($offset)
}
/**
- * @see ArrayAccess
- *
- * @api
+ * {@inheritDoc}
*/
public function offsetGet($offset)
{
@@ -171,9 +138,7 @@ public function offsetGet($offset)
}
/**
- * @see ArrayAccess
- *
- * @api
+ * {@inheritDoc}
*/
public function offsetSet($offset, $violation)
{
@@ -185,9 +150,7 @@ public function offsetSet($offset, $violation)
}
/**
- * @see ArrayAccess
- *
- * @api
+ * {@inheritDoc}
*/
public function offsetUnset($offset)
{
View
83 ConstraintViolationListInterface.php
@@ -0,0 +1,83 @@
+<?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;
+
+/**
+ * A list of constraint violations.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @api
+ */
+interface ConstraintViolationListInterface extends \Traversable, \Countable, \ArrayAccess
+{
+ /**
+ * Adds a constraint violation to this list.
+ *
+ * @param ConstraintViolationInterface $violation The violation to add.
+ *
+ * @api
+ */
+ public function add(ConstraintViolationInterface $violation);
+
+ /**
+ * Merges an existing violation list into this list.
+ *
+ * @param ConstraintViolationListInterface $otherList The list to merge.
+ *
+ * @api
+ */
+ public function addAll(ConstraintViolationListInterface $otherList);
+
+ /**
+ * Returns the violation at a given offset.
+ *
+ * @param integer $offset The offset of the violation.
+ *
+ * @return ConstraintViolationInterface The violation.
+ *
+ * @throws \OutOfBoundsException If the offset does not exist.
+ *
+ * @api
+ */
+ public function get($offset);
+
+ /**
+ * Returns whether the given offset exists.
+ *
+ * @param integer $offset The violation offset.
+ *
+ * @return Boolean Whether the offset exists.
+ *
+ * @api
+ */
+ public function has($offset);
+
+ /**
+ * Sets a violation at a given offset.
+ *
+ * @param integer $offset The violation offset.
+ * @param ConstraintViolationInterface $violation The violation.
+ *
+ * @api
+ */
+ public function set($offset, ConstraintViolationInterface $violation);
+
+ /**
+ * Removes a violation at a given offset.
+ *
+ * @param integer $offset The offset to remove.
+ *
+ * @api
+ */
+ public function remove($offset);
+}
View
19 Exception/NoSuchMetadataException.php
@@ -0,0 +1,19 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Exception;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class NoSuchMetadataException extends ValidatorException
+{
+}
View
274 ExecutionContext.php
@@ -11,59 +11,69 @@
namespace Symfony\Component\Validator;
-use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
-
/**
- * Stores the state of the current node in the validation graph.
+ * Default implementation of {@link ExecutionContextInterface}.
*
* This class is immutable by design.
*
- * It is used by the GraphWalker to initialize validation of different items
- * and keep track of the violations.
- *
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
- *
- * @api
*/
-class ExecutionContext
+class ExecutionContext implements ExecutionContextInterface
{
+ /**
+ * @var GlobalExecutionContextInterface
+ */
private $globalContext;
- private $propertyPath;
+
+ /**
+ * @var MetadataInterface
+ */
+ private $metadata;
+
+ /**
+ * @var mixed
+ */
private $value;
+
+ /**
+ * @var string
+ */
private $group;
- private $class;
- private $property;
- public function __construct(GlobalExecutionContext $globalContext, $value, $propertyPath, $group, $class = null, $property = null)
+ /**
+ * @var string
+ */
+ private $propertyPath;
+
+ /**
+ * Creates a new execution context.
+ *
+ * @param GlobalExecutionContextInterface $globalContext The global context storing node-independent state.
+ * @param MetadataInterface $metadata The metadata of the validated node.
+ * @param mixed $value The value of the validated node.
+ * @param string $group The current validation group.
+ * @param string $propertyPath The property path to the current node.
+ */
+ public function __construct(GlobalExecutionContextInterface $globalContext, MetadataInterface $metadata = null, $value = null, $group = null, $propertyPath = '')
{
+ if (null === $group) {
+ $group = Constraint::DEFAULT_GROUP;
+ }
+
$this->globalContext = $globalContext;
+ $this->metadata = $metadata;
$this->value = $value;
$this->propertyPath = $propertyPath;
$this->group = $group;
- $this->class = $class;
- $this->property = $property;
- }
-
- public function __clone()
- {
- $this->globalContext = clone $this->globalContext;
}
/**
- * Adds a violation at the current node of the validation graph.
- *
- * @param string $message The error message.
- * @param array $params The parameters parsed into the error message.
- * @param mixed $invalidValue The invalid, validated value.
- * @param integer|null $pluralization The number to use to pluralize of the message.
- * @param integer|null $code The violation code.
- *
- * @api
+ * {@inheritdoc}
*/
public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
{
- $this->globalContext->addViolation(new ConstraintViolation(
+ $this->globalContext->getViolations()->add(new ConstraintViolation(
$message,
$params,
$this->globalContext->getRoot(),
@@ -85,10 +95,12 @@ public function addViolation($message, array $params = array(), $invalidValue =
* @param mixed $invalidValue The invalid, validated value.
* @param integer|null $pluralization The number to use to pluralize of the message.
* @param integer|null $code The violation code.
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
public function addViolationAtPath($propertyPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
{
- $this->globalContext->addViolation(new ConstraintViolation(
+ $this->globalContext->getViolations()->add(new ConstraintViolation(
$message,
$params,
$this->globalContext->getRoot(),
@@ -110,10 +122,26 @@ public function addViolationAtPath($propertyPath, $message, array $params = arra
* @param mixed $invalidValue The invalid, validated value.
* @param integer|null $pluralization The number to use to pluralize of the message.
* @param integer|null $code The violation code.
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use the
+ * method {@link atViolationAt} instead.
*/
public function addViolationAtSubPath($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
{
- $this->globalContext->addViolation(new ConstraintViolation(
+ if (func_num_args() >= 4) {
+ $this->addViolationAt($subPath, $message, $params, $invalidValue, $pluralization, $code);
+ } else {
+ // Needed in order to make the check for func_num_args() inside work
+ $this->addViolationAt($subPath, $message, $params);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
+ {
+ $this->globalContext->getViolations()->add(new ConstraintViolation(
$message,
$params,
$this->globalContext->getRoot(),
@@ -126,20 +154,24 @@ public function addViolationAtSubPath($subPath, $message, array $params = array(
}
/**
- * @return ConstraintViolationList
- *
- * @api
+ * {@inheritdoc}
*/
public function getViolations()
{
return $this->globalContext->getViolations();
}
+ /**
+ * {@inheritdoc}
+ */
public function getRoot()
{
return $this->globalContext->getRoot();
}
+ /**
+ * {@inheritdoc}
+ */
public function getPropertyPath($subPath = null)
{
if (null !== $subPath && '' !== $this->propertyPath && '' !== $subPath && '[' !== $subPath[0]) {
@@ -149,39 +181,195 @@ public function getPropertyPath($subPath = null)
return $this->propertyPath . $subPath;
}
- public function getCurrentClass()
+ /**
+ * {@inheritdoc}
+ */
+ public function getClassName()
{
- return $this->class;
+ if ($this->metadata instanceof ClassBasedInterface) {
+ return $this->metadata->getClassName();
+ }
+
+ return null;
}
- public function getCurrentProperty()
+ /**
+ * {@inheritdoc}
+ */
+ public function getPropertyName()
{
- return $this->property;
+ if ($this->metadata instanceof PropertyMetadataInterface) {
+ return $this->metadata->getPropertyName();
+ }
+
+ return null;
}
- public function getCurrentValue()
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue()
{
return $this->value;
}
+ /**
+ * {@inheritdoc}
+ */
public function getGroup()
{
return $this->group;
}
/**
- * @return GraphWalker
+ * {@inheritdoc}
+ */
+ public function getMetadata()
+ {
+ return $this->metadata;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFor($value)
+ {
+ return $this->globalContext->getMetadataFactory()->getMetadataFor($value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate($value, $groups = null, $subPath = '', $traverse = false, $deep = false)
+ {
+ $propertyPath = $this->getPropertyPath($subPath);
+
+ foreach ($this->resolveGroups($groups) as $group) {
+ $this->globalContext->getVisitor()->validate($value, $group, $propertyPath, $traverse, $deep);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateValue($value, $constraints, $groups = null, $subPath = '')
+ {
+ $constraints = is_array($constraints) ? $constraints : array($constraints);
+
+ if (null === $groups && '' === $subPath) {
+ $context = clone $this;
+ $context->value = $value;
+ $context->executeConstraintValidators($value, $constraints);
+
+ return;
+ }
+
+ $propertyPath = $this->getPropertyPath($subPath);
+
+ foreach ($this->resolveGroups($groups) as $group) {
+ $context = clone $this;
+ $context->value = $value;
+ $context->group = $group;
+ $context->propertyPath = $propertyPath;
+ $context->executeConstraintValidators($value, $constraints);
+ }
+ }
+
+ /**
+ * Returns the class name of the current node.
+ *
+ * @return string|null The class name or null, if the current node does not
+ * hold information about a class.
+ *
+ * @see getClassName
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
+ * {@link getClassName} instead.
+ */
+ public function getCurrentClass()
+ {
+ return $this->getClassName();
+ }
+
+ /**
+ * Returns the property name of the current node.
+ *
+ * @return string|null The property name or null, if the current node does
+ * not hold information about a property.
+ *
+ * @see getPropertyName
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
+ * {@link getClassName} instead.
+ */
+ public function getCurrentProperty()
+ {
+ return $this->getPropertyName();
+ }
+
+ /**
+ * Returns the currently validated value.
+ *
+ * @return mixed The current value.
+ *
+ * @see getValue
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
+ * {@link getValue} instead.
+ */
+ public function getCurrentValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the graph walker instance.
+ *
+ * @return GraphWalker The graph walker.
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
+ * {@link validate} and {@link validateValue} instead.
*/
public function getGraphWalker()
{
- return $this->globalContext->getGraphWalker();
+ return $this->globalContext->getVisitor()->getGraphWalker();
}
/**
- * @return ClassMetadataFactoryInterface
+ * {@inheritdoc}
*/
public function getMetadataFactory()
{
return $this->globalContext->getMetadataFactory();
}
+
+ /**
+ * Executes the validators of the given constraints for the given value.
+ *
+ * @param mixed $value The value to validate.
+ * @param Constraint[] $constraints The constraints to match against.
+ */
+ private function executeConstraintValidators($value, array $constraints)
+ {
+ foreach ($constraints as $constraint) {
+ $validator = $this->globalContext->getValidatorFactory()->getInstance($constraint);
+ $validator->initialize($this);
+ $validator->validate($value, $constraint);
+ }
+ }
+
+ /**
+ * Returns an array of group names.
+ *
+ * @param null|string|string[] $groups The groups to resolve. If a single string is
+ * passed, it is converted to an array. If null
+ * is passed, an array containing the current
+ * group of the context is returned.
+ *
+ * @return array An array of validation groups.
+ */
+ private function resolveGroups($groups)
+ {
+ return $groups ? (array) $groups : (array) $this->group;
+ }
}
View
304 ExecutionContextInterface.php
@@ -0,0 +1,304 @@
+<?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;
+
+/**
+ * Stores the validator's state during validation.
+ *
+ * For example, let's validate the following object graph:
+ *
+ * <pre>
+ * (Person)---($firstName: string)
+ * \
+ * ($address: Address)---($street: string)
+ * </pre>
+ *
+ * We validate the <tt>Person</tt> instance, which becomes the "root" of the
+ * validation run (see {@link getRoot}). The state of the context after the
+ * first step will be like this:
+ *
+ * <pre>
+ * (Person)---($firstName: string)
+ * ^ \
+ * ($address: Address)---($street: string)
+ * </pre>
+ *
+ * The validator is stopped at the <tt>Person</tt> node, both the root and the
+ * value (see {@link getValue}) of the context point to the <tt>Person</tt>
+ * instance. The property path is empty at this point (see {@link getPropertyPath}).
+ * The metadata of the context is the metadata of the <tt>Person</tt> node
+ * (see {@link getMetadata}).
+ *
+ * After advancing to the property <tt>$firstName</tt> of the <tt>Person</tt>
+ * instance, the state of the context looks like this:
+ *
+ * <pre>
+ * (Person)---($firstName: string)
+ * \ ^
+ * ($address: Address)---($street: string)
+ * </pre>
+ *
+ * The validator is stopped at the property <tt>$firstName</tt>. The root still
+ * points to the <tt>Person</tt> instance, because this is where the validation
+ * started. The property path is now "firstName" and the current value is the
+ * value of that property.
+ *
+ * After advancing to the <tt>$address</tt> property and then to the
+ * <tt>$street</tt> property of the <tt>Address</tt> instance, the context state
+ * looks like this:
+ *
+ * <pre>
+ * (Person)---($firstName: string)
+ * \
+ * ($address: Address)---($street: string)
+ * ^
+ * </pre>
+ *
+ * The validator is stopped at the property <tt>$street</tt>. The root still
+ * points to the <tt>Person</tt> instance, but the property path is now
+ * "address.street" and the validated value is the value of that property.
+ *
+ * Apart from the root, the property path and the currently validated value,
+ * the execution context also knows the metadata of the current node (see
+ * {@link getMetadata}) which for example returns a {@link Mapping\PropertyMetadata}
+ * or a {@link Mapping\ClassMetadata} object. he context also contains the
+ * validation group that is currently being validated (see {@link getGroup}) and
+ * the violations that happened up until now (see {@link getViolations}).
+ *
+ * Apart from reading the execution context, you can also use
+ * {@link addViolation} or {@link addViolationAt} to add new violations and
+ * {@link validate} or {@link validateValue} to validate values that the
+ * validator otherwise would not reach.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @api
+ */
+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 mixed $invalidValue The invalid, validated value.
+ * @param integer|null $pluralization The number to use to pluralize of the message.
+ * @param integer|null $code The violation code.
+ *
+ * @api
+ */
+ public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null);
+
+ /**
+ * Adds a violation at the validation graph node with the given property
+ * path relative to the current property path.
+ *
+ * @param string $subPath The relative property path for the violation.
+ * @param string $message The error message.
+ * @param array $params The parameters substituted in the error message.
+ * @param mixed $invalidValue The invalid, validated value.
+ * @param integer|null $pluralization The number to use to pluralize of the message.
+ * @param integer|null $code The violation code.
+ *
+ * @api
+ */
+ public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null);
+
+ /**
+ * Validates the given value within the scope of the current validation.
+ *
+ * The value may be any value recognized by the used metadata factory
+ * (see {@link MetadataFactoryInterface::getMetadata}), or an array or a
+ * traversable object of such values.
+ *
+ * Usually you validate a value that is not the current node of the
+ * execution context. For this case, you can pass the {@link $subPath}
+ * argument which is appended to the current property path when a violation
+ * is created. For example, take the following object graph:
+ *
+ * <pre>
+ * (Person)---($address: Address)---($phoneNumber: PhoneNumber)
+ * ^
+ * </pre>
+ *
+ * When the execution context stops at the <tt>Person</tt> instance, the
+ * property path is "address". When you validate the <tt>PhoneNumber</tt>
+ * instance now, pass "phoneNumber" as sub path to correct the property path
+ * to "address.phoneNumber":
+ *
+ * <pre>
+ * $context->validate($address->phoneNumber, 'phoneNumber');
+ * </pre>
+ *
+ * Any violations generated during the validation will be added to the
+ * violation list that you can access with {@link getViolations}.
+ *
+ * @param mixed $value The value to validate.
+ * @param string $subPath The path to append to the context's property path.
+ * @param null $groups The groups to validate in. If you don't pass any
+ * groups here, the current group of the context
+ * will be used.
+ * @param bool $traverse Whether to traverse the value if it is an array
+ * or an instance of <tt>\Traversable</tt>.
+ * @param bool $deep Whether to traverse the value recursively if
+ * it is a collection of collections.
+ */
+ public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false);
+
+ /**
+ * Validates a value against a constraint.
+ *
+ * Use the parameter <tt>$subPath</tt> to adapt the property path for the
+ * validated value. For example, take the following object graph:
+ *
+ * <pre>
+ * (Person)---($address: Address)---($street: string)
+ * ^
+ * </pre>
+ *
+ * When the validator validates the <tt>Address</tt> instance, the
+ * property path stored in the execution context is "address". When you
+ * manually validate the property <tt>$street</tt> now, pass the sub path
+ * "street" to adapt the full property path to "address.street":
+ *
+ * <pre>
+ * $context->validate($address->street, new NotNull(), 'street');
+ * </pre>
+ *
+ * @param mixed $value The value to validate.
+ * @param Constraint|Constraint[] $constraints The constraint(s) to validate against.
+ * @param string $subPath The path to append to the context's property path.
+ * @param null $groups The groups to validate in. If you don't pass any
+ * groups here, the current group of the context
+ * will be used.
+ */
+ public function validateValue($value, $constraints, $subPath = '', $groups = null);
+
+ /**
+ * 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();
+
+ /**
+ * Returns the used metadata factory.
+ *
+ * @return MetadataFactoryInterface The metadata factory.
+ */
+ public function getMetadataFactory();
+
+ /**
+ * 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:
+ *
+ * <pre>
+ * (Person)---($address: Address)---($street: string)
+ * </pre>
+ *
+ * When the <tt>Person</tt> instance is passed to the validator, the
+ * property path is initially empty. When the <tt>$address</tt> property
+ * of that person is validated, the property path is "address". When
+ * the <tt>$street</tt> property of the related <tt>Address</tt> 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 <tt>$addresses</tt> and contains an array
+ * of <tt>Address</tt> instance, the property path generated for the
+ * <tt>$street</tt> 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 = null);
+}
View
76 GlobalExecutionContext.php
@@ -1,76 +0,0 @@
-<?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;
-
-use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
-
-/**
- * Stores the node-independent information of a validation run.
- *
- * This class is immutable by design, except for violation tracking.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- */
-class GlobalExecutionContext
-{
- private $root;
- private $graphWalker;
- private $metadataFactory;
- private $violations;
-
- public function __construct($root, GraphWalker $graphWalker, ClassMetadataFactoryInterface $metadataFactory)
- {
- $this->root = $root;
- $this->graphWalker = $graphWalker;
- $this->metadataFactory = $metadataFactory;
- $this->violations = new ConstraintViolationList();
- }
-
- public function __clone()
- {
- $this->violations = clone $this->violations;
- }
-
- public function addViolation(ConstraintViolation $violation)
- {
- $this->violations->add($violation);
- }
-
- /**
- * @return ConstraintViolationList
- */
- public function getViolations()
- {
- return $this->violations;
- }
-
- public function getRoot()
- {
- return $this->root;
- }
-
- /**
- * @return GraphWalker
- */
- public function getGraphWalker()
- {
- return $this->graphWalker;
- }
-
- /**
- * @return ClassMetadataFactoryInterface
- */
- public function getMetadataFactory()
- {
- return $this->metadataFactory;
- }
-}
View
68 GlobalExecutionContextInterface.php
@@ -0,0 +1,68 @@
+<?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;
+
+/**
+ * Stores the node-independent state of a validation run.
+ *
+ * When the validator validates a graph of objects, it uses two classes to
+ * store the state during the validation:
+ *
+ * <ul>
+ * <li>For each node in the validation graph (objects, properties, getters) the
+ * validator creates an instance of {@link ExecutionContextInterface} that
+ * stores the information about that node.</li>
+ * <li>One single <tt>GlobalExecutionContextInterface</tt> stores the state
+ * that is independent of the current node.</li>
+ * </ul>
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface GlobalExecutionContextInterface
+{
+ /**
+ * Returns the violations generated by the validator so far.
+ *
+ * @return ConstraintViolationListInterface A list of constraint violations.
+ */
+ public function getViolations();
+
+ /**
+ * Returns the value at which validation was started in the object graph.
+ *
+ * @return mixed The root value.
+ *
+ * @see ExecutionContextInterface::getRoot
+ */
+ public function getRoot();
+
+ /**
+ * Returns the visitor instance used to validate the object graph nodes.
+ *
+ * @return ValidationVisitorInterface The validation visitor.
+ */
+ public function getVisitor();
+
+ /**
+ * Returns the factory for constraint validators.
+ *
+ * @return ConstraintValidatorFactoryInterface The constraint validator factory.
+ */
+ public function getValidatorFactory();
+
+ /**
+ * Returns the factory for validation metadata objects.
+ *
+ * @return MetadataFactoryInterface The metadata factory.
+ */
+ public function getMetadataFactory();
+}
View
203 GraphWalker.php
@@ -11,10 +11,8 @@
namespace Symfony\Component\Validator;
-use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
-use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\MemberMetadata;
@@ -24,29 +22,52 @@
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. This class
+ * has been replaced by {@link ValidationVisitorInterface} and
+ * {@link MetadataInterface}.
*/
class GraphWalker
{
- private $globalContext;
- private $validatorFactory;
+ /**
+ * @var ValidationVisitor
+ */
+ private $visitor;
+
+ /**
+ * @var MetadataFactoryInterface
+ */
private $metadataFactory;
- private $validatorInitializers = array();
- private $validatedObjects = array();
- public function __construct($root, ClassMetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $factory, array $validatorInitializers = array())
+ /**
+ * @var array
+ */
+ private $validatedObjects;
+
+ /**
+ * Creates a new graph walker.
+ *
+ * @param ValidationVisitor $visitor
+ * @param MetadataFactoryInterface $metadataFactory
+ * @param array $validatedObjects
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
+ */
+ public function __construct(ValidationVisitor $visitor, MetadataFactoryInterface $metadataFactory, array &$validatedObjects = array())
{
- $this->globalContext = new GlobalExecutionContext($root, $this, $metadataFactory);
- $this->validatorFactory = $factory;
+ $this->visitor = $visitor;
$this->metadataFactory = $metadataFactory;
- $this->validatorInitializers = $validatorInitializers;
+ $this->validatedObjects = &$validatedObjects;
}
/**
* @return ConstraintViolationList
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
public function getViolations()
{
- return $this->globalContext->getViolations();
+ return $this->visitor->getViolations();
}
/**
@@ -57,128 +78,128 @@ public function getViolations()
* @param object $object The object to validate
* @param string $group The validator group to use for validation
* @param string $propertyPath
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
public function walkObject(ClassMetadata $metadata, $object, $group, $propertyPath)
{
- foreach ($this->validatorInitializers as $initializer) {
- if (!$initializer instanceof ObjectInitializerInterface) {
- throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.');
- }
- $initializer->initialize($object);
- }
-
- if ($group === Constraint::DEFAULT_GROUP && ($metadata->hasGroupSequence() || $metadata->isGroupSequenceProvider())) {
- if ($metadata->hasGroupSequence()) {
- $groups = $metadata->getGroupSequence();
- } else {
- $groups = $object->getGroupSequence();
- }
-
- foreach ($groups as $group) {
- $this->walkObjectForGroup($metadata, $object, $group, $propertyPath, Constraint::DEFAULT_GROUP);
-
- if (count($this->getViolations()) > 0) {
- break;
- }
- }
- } else {
- $this->walkObjectForGroup($metadata, $object, $group, $propertyPath);
- }
- }
-
- protected function walkObjectForGroup(ClassMetadata $metadata, $object, $group, $propertyPath, $propagatedGroup = null)
- {
$hash = spl_object_hash($object);
// Exit, if the object is already validated for the current group
if (isset($this->validatedObjects[$hash][$group])) {
- return;
+ return;
}
// Remember validating this object before starting and possibly
// traversing the object graph
$this->validatedObjects[$hash][$group] = true;
- $currentClass = $metadata->getClassName();
-
- foreach ($metadata->findConstraints($group) as $constraint) {
- $this->walkConstraint($constraint, $object, $group, $propertyPath, $currentClass);
- }
+ $metadata->accept($this->visitor, $object, $group, $propertyPath);
+ }
- if (null !== $object) {
- $pathPrefix = empty($propertyPath) ? '' : $propertyPath.'.';
- foreach ($metadata->getConstrainedProperties() as $property) {
- $this->walkProperty($metadata, $property, $object, $group, $pathPrefix.$property, $propagatedGroup);
- }
- }
+ protected function walkObjectForGroup(ClassMetadata $metadata, $object, $group, $propertyPath, $propagatedGroup = null)
+ {
+ $metadata->accept($this->visitor, $object, $group, $propertyPath, $propagatedGroup);
}
+ /**
+ * Validates a property of a class.
+ *
+ * @param Mapping\ClassMetadata $metadata
+ * @param $property
+ * @param $object
+ * @param $group
+ * @param $propertyPath
+ * @param null $propagatedGroup
+ *
+ * @throws Exception\UnexpectedTypeException
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
+ */
public function walkProperty(ClassMetadata $metadata, $property, $object, $group, $propertyPath, $propagatedGroup = null)
{
+ if (!is_object($object)) {
+ throw new UnexpectedTypeException($object, 'object');
+ }
+
foreach ($metadata->getMemberMetadatas($property) as $member) {
- $this->walkMember($member, $member->getValue($object), $group, $propertyPath, $propagatedGroup);
+ $member->accept($this->visitor, $member->getValue($object), $group, $propertyPath, $propagatedGroup);
}
}
+ /**
+ * Validates a property of a class against a potential value.
+ *
+ * @param Mapping\ClassMetadata $metadata
+ * @param $property
+ * @param $value
+ * @param $group
+ * @param $propertyPath
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
+ */
public function walkPropertyValue(ClassMetadata $metadata, $property, $value, $group, $propertyPath)
{
foreach ($metadata->getMemberMetadatas($property) as $member) {
- $this->walkMember($member, $value, $group, $propertyPath);
+ $member->accept($this->visitor, $value, $group, $propertyPath);
}
}
protected function walkMember(MemberMetadata $metadata, $value, $group, $propertyPath, $propagatedGroup = null)
{
- $currentClass = $metadata->getClassName();
- $currentProperty = $metadata->getPropertyName();
-
- foreach ($metadata->findConstraints($group) as $constraint) {
- $this->walkConstraint($constraint, $value, $group, $propertyPath, $currentClass, $currentProperty);
- }
-
- if ($metadata->isCascaded()) {
- $this->walkReference($value, $propagatedGroup ?: $group, $propertyPath, $metadata->isCollectionCascaded(), $metadata->isCollectionCascadedDeeply());
- }
+ $metadata->accept($this->visitor, $value, $group, $propertyPath, $propagatedGroup);
}
+ /**
+ * Validates an object or an array.
+ *
+ * @param $value
+ * @param $group
+ * @param $propertyPath
+ * @param $traverse
+ * @param bool $deep
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
+ */
public function walkReference($value, $group, $propertyPath, $traverse, $deep = false)
{
- if (null !== $value) {
- if (!is_object($value) && !is_array($value)) {
- throw new UnexpectedTypeException($value, 'object or array');
- }
-
- if ($traverse && (is_array($value) || $value instanceof \Traversable)) {
- foreach ($value as $key => $element) {
- // Ignore any scalar values in the collection
- if (is_object($element) || is_array($element)) {
- // Only repeat the traversal if $deep is set
- $this->walkReference($element, $group, $propertyPath.'['.$key.']', $deep, $deep);
- }
- }
- }
-
- if (is_object($value)) {
- $metadata = $this->metadataFactory->getClassMetadata(get_class($value));
- $this->walkObject($metadata, $value, $group, $propertyPath);
- }
- }
+ $this->visitor->validate($value, $group, $propertyPath, $traverse, $deep);
}
+ /**
+ * Validates a value against a constraint.
+ *
+ * @param Constraint $constraint
+ * @param $value
+ * @param $group
+ * @param $propertyPath
+ * @param null $currentClass
+ * @param null $currentProperty
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
+ */
public function walkConstraint(Constraint $constraint, $value, $group, $propertyPath, $currentClass = null, $currentProperty = null)
{
- $validator = $this->validatorFactory->getInstance($constraint);
+ $metadata = null;
+
+ // BC code to make getCurrentClass() and getCurrentProperty() work when
+ // called from within this method
+ if (null !== $currentClass) {
+ $metadata = $this->metadataFactory->getMetadataFor($currentClass);
+
+ if (null !== $currentProperty && $metadata instanceof PropertyMetadataContainerInterface) {
+ $metadata = current($metadata->getPropertyMetadata($currentProperty));
+ }
+ }
- $localContext = new ExecutionContext(
- $this->globalContext,
+ $context = new ExecutionContext(
+ $this->visitor,
+ $metadata,
$value,
- $propertyPath,
$group,
- $currentClass,
- $currentProperty
+ $propertyPath
);
- $validator->initialize($localContext);
- $validator->validate($value, $constraint);
+ $context->validateValue($value, $constraint);
}
}
View
50 Mapping/ClassMetadata.php
@@ -11,6 +11,10 @@
namespace Symfony\Component\Validator\Mapping;
+use Symfony\Component\Validator\ValidationVisitorInterface;
+use Symfony\Component\Validator\PropertyMetadataContainerInterface;
+use Symfony\Component\Validator\ClassBasedInterface;
+use Symfony\Component\Validator\MetadataInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\GroupDefinitionException;
@@ -21,7 +25,7 @@
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
-class ClassMetadata extends ElementMetadata
+class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassBasedInterface, PropertyMetadataContainerInterface
{
public $name;
public $defaultGroup;
@@ -47,6 +51,40 @@ public function __construct($class)
$this->defaultGroup = $class;
}
}
+
+ public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath, $propagatedGroup = null)
+ {
+ if (null === $propagatedGroup && Constraint::DEFAULT_GROUP === $group
+ && ($this->hasGroupSequence() || $this->isGroupSequenceProvider())) {
+ if ($this->hasGroupSequence()) {
+ $groups = $this->getGroupSequence();
+ } else {
+ $groups = $value->getGroupSequence();
+ }
+
+ foreach ($groups as $group) {
+ $this->accept($visitor, $value, $group, $propertyPath, Constraint::DEFAULT_GROUP);
+
+ if (count($visitor->getViolations()) > 0) {
+ break;
+ }
+ }
+
+ return;
+ }
+
+ $visitor->visit($this, $value, $group, $propertyPath);
+
+ if (null !== $value) {
+ $pathPrefix = empty($propertyPath) ? '' : $propertyPath.'.';
+
+ foreach ($this->getConstrainedProperties() as $property) {
+ foreach ($this->getMemberMetadatas($property) as $member) {
+ $member->accept($visitor, $member->getValue($value), $group, $pathPrefix.$property, $propagatedGroup);
+ }
+ }
+ }
+ }
/**
* Returns the properties to be serialized
@@ -225,7 +263,7 @@ public function hasMemberMetadatas($property)
*
* @param string $property The name of the property
*
- * @return array An array of MemberMetadata
+ * @return MemberMetadata[] An array of MemberMetadata
*/
public function getMemberMetadatas($property)
{
@@ -233,6 +271,14 @@ public function getMemberMetadatas($property)
}
/**
+ * {@inheritdoc}
+ */
+ public function getPropertyMetadata($property)
+ {
+ return $this->members[$property];
+ }
+
+ /**
* Returns all properties for which constraints are defined.
*
* @return array An array of property names
View
50 Mapping/ClassMetadataFactory.php
@@ -11,15 +11,17 @@
namespace Symfony\Component\Validator\Mapping;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Exception\NoSuchMetadataException;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
/**
- * Implementation of ClassMetadataFactoryInterface
+ * A factory for creating metadata for PHP classes.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
-class ClassMetadataFactory implements ClassMetadataFactoryInterface
+class ClassMetadataFactory implements ClassMetadataFactoryInterface, MetadataFactoryInterface
{
/**
* The loader for loading the class metadata
@@ -41,9 +43,16 @@ public function __construct(LoaderInterface $loader = null, CacheInterface $cach
$this->cache = $cache;
}
- public function getClassMetadata($class)
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFor($value)
{
- $class = ltrim($class, '\\');
+ if (!is_object($value) && !is_string($value)) {
+ throw new NoSuchMetadataException('Cannot create metadata for non-objects. Got: ' . gettype($value));
+ }
+
+ $class = ltrim(is_object($value) ? get_class($value) : $value, '\\');
if (isset($this->loadedClasses[$class])) {
return $this->loadedClasses[$class];
@@ -53,6 +62,10 @@ public function getClassMetadata($class)
return $this->loadedClasses[$class];
}
+ if (!class_exists($class) && !interface_exists($class)) {
+ throw new NoSuchMetadataException('The class or interface "' . $class . '" does not exist.');
+ }
+
$metadata = new ClassMetadata($class);
// Include constraints from the parent class
@@ -78,4 +91,33 @@ public function getClassMetadata($class)
return $this->loadedClasses[$class] = $metadata;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMetadataFor($value)
+ {
+ if (!is_object($value) && !is_string($value)) {
+ return false;
+ }
+
+ $class = ltrim(is_object($value) ? get_class($value) : $value, '\\');
+
+ if (class_exists($class) || interface_exists($class)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
+ * {@link getMetadataFor} instead.
+ */
+ public function getClassMetadata($class)
+ {
+ return $this->getMetadataFor($class);
+ }
}
View
59 Mapping/ClassMetadataFactoryAdapter.php
@@ -0,0 +1,59 @@
+<?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;
+
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Exception\NoSuchMetadataException;
+
+/**
+ * An adapter for exposing {@link ClassMetadataFactoryInterface} implementations
+ * under the new {@link MetadataFactoryInterface}.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class ClassMetadataFactoryAdapter implements MetadataFactoryInterface
+{
+ /**
+ * @var ClassMetadataFactoryInterface
+ */
+ private $innerFactory;
+
+ public function __construct(ClassMetadataFactoryInterface $innerFactory)
+ {
+ $this->innerFactory = $innerFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFor($value)
+ {
+ $class = is_object($value) ? get_class($value) : $value;
+ $metadata = $this->innerFactory->getClassMetadata($class);
+
+ if (null === $metadata) {
+ throw new NoSuchMetadataException('No metadata exists for class '. $class);
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMetadataFor($value)
+ {
+ $class = is_object($value) ? get_class($value) : $value;
+
+ return null !== $this->innerFactory->getClassMetadata($class);
+ }
+}
View
13 Mapping/ClassMetadataFactoryInterface.php
@@ -11,7 +11,20 @@
namespace Symfony\Component\Validator\Mapping;
+/**
+ * A factory for {@link ClassMetadata} objects.
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3. Implement
+ * {@link \Symfony\Component\Validator\MetadataFactoryInterface} instead.
+ */
interface ClassMetadataFactoryInterface
{
+ /**
+ * Returns metadata for a given class.
+ *
+ * @param string $class The class name.
+ *
+ * @return ClassMetadata The class metadata instance.
+ */
public function getClassMetadata($class);
}
View
2  Mapping/GetterMetadata.php
@@ -40,7 +40,7 @@ public function __construct($class, $property)
/**
* {@inheritDoc}
*/
- public function getValue($object)
+ public function getPropertyValue($object)
{
return $this->getReflectionMember()->invoke($object);
}
View
22 Mapping/MemberMetadata.php
@@ -11,11 +11,14 @@
namespace Symfony\Component\Validator\Mapping;
+use Symfony\Component\Validator\ValidationVisitorInterface;
+use Symfony\Component\Validator\ClassBasedInterface;
+use Symfony\Component\Validator\PropertyMetadataInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
-abstract class MemberMetadata extends ElementMetadata
+abstract class MemberMetadata extends ElementMetadata implements PropertyMetadataInterface, ClassBasedInterface
{
public $class;
public $name;
@@ -39,6 +42,15 @@ public function __construct($class, $name, $property)
$this->property = $property;
}
+ public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath, $propagatedGroup = null)
+ {
+ $visitor->visit($this, $value, $group, $propertyPath);
+
+ if ($this->isCascaded()) {
+ $visitor->validate($value, $propagatedGroup ?: $group</