[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.
This commit is contained in:
Bernhard Schussek 2012-11-22 15:58:46 +01:00
parent 225e3e53d4
commit efe42cbb1f
44 changed files with 2733 additions and 591 deletions

View File

@ -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 File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator;
/**
* 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 File

@ -23,7 +23,7 @@ use Symfony\Component\Validator\Exception\ValidatorException;
abstract class ConstraintValidator implements ConstraintValidatorInterface
{
/**
* @var ExecutionContext
* @var ExecutionContextInterface
*/
protected $context;
@ -44,7 +44,7 @@ abstract class ConstraintValidator implements ConstraintValidatorInterface
/**
* {@inheritDoc}
*/
public function initialize(ExecutionContext $context)
public function initialize(ExecutionContextInterface $context)
{
$this->context = $context;
$this->messageTemplate = '';

View File

@ -21,9 +21,9 @@ interface ConstraintValidatorInterface
/**
* 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 File

@ -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 @@ class ConstraintViolation
}
/**
* @return string
* Converts the violation into a string for debugging purposes.
*
* @return string The violation as string.
*/
public function __toString()
{
@ -58,9 +104,7 @@ class ConstraintViolation
}
/**
* @return string
*
* @api
* {@inheritDoc}
*/
public function getMessageTemplate()
{
@ -68,9 +112,7 @@ class ConstraintViolation
}
/**
* @return array
*
* @api
* {@inheritDoc}
*/
public function getMessageParameters()
{
@ -78,7 +120,7 @@ class ConstraintViolation
}
/**
* @return integer|null
* {@inheritDoc}
*/
public function getMessagePluralization()
{
@ -86,11 +128,7 @@ class ConstraintViolation
}
/**
* Returns the violation message.
*
* @return string
*
* @api
* {@inheritDoc}
*/
public function getMessage()
{
@ -105,21 +143,33 @@ class ConstraintViolation
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 File

@ -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 File

@ -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 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @return string
* Converts the violation into a string for debugging purposes.
*
* @return string The violation as string.
*/
public function __toString()
{
@ -54,39 +52,25 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* 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 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* 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 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* 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 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @see IteratorAggregate
*
* @api
* {@inheritDoc}
*/
public function getIterator()
{
@ -141,9 +114,7 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @see Countable
*
* @api
* {@inheritDoc}
*/
public function count()
{
@ -151,9 +122,7 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @see ArrayAccess
*
* @api
* {@inheritDoc}
*/
public function offsetExists($offset)
{
@ -161,9 +130,7 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @see ArrayAccess
*
* @api
* {@inheritDoc}
*/
public function offsetGet($offset)
{
@ -171,9 +138,7 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @see ArrayAccess
*
* @api
* {@inheritDoc}
*/
public function offsetSet($offset, $violation)
{
@ -185,9 +150,7 @@ class ConstraintViolationList implements \IteratorAggregate, \Countable, \ArrayA
}
/**
* @see ArrayAccess
*
* @api
* {@inheritDoc}
*/
public function offsetUnset($offset)
{

View File

@ -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 File

@ -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 File

@ -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;
private $value;
private $group;
private $class;
private $property;
public function __construct(GlobalExecutionContext $globalContext, $value, $propertyPath, $group, $class = null, $property = null)
/**
* @var MetadataInterface
*/
private $metadata;
/**
* @var mixed
*/
private $value;
/**
* @var string
*/
private $group;
/**
* @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 @@ class ExecutionContext
* @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 @@ class ExecutionContext
* @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 @@ class ExecutionContext
}
/**
* @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 @@ class ExecutionContext
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 getGraphWalker()
public function getMetadata()
{
return $this->globalContext->getGraphWalker();
return $this->metadata;
}
/**
* @return ClassMetadataFactoryInterface
* {@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->getVisitor()->getGraphWalker();
}
/**
* {@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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 @@ use Symfony\Component\Validator\Mapping\MemberMetadata;
*
* @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;
private $metadataFactory;
private $validatorInitializers = array();
private $validatedObjects = array();
/**
* @var ValidationVisitor
*/
private $visitor;
public function __construct($root, ClassMetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $factory, array $validatorInitializers = array())
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/**
* @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 @@ class GraphWalker
* @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);
}
if (null !== $object) {
$pathPrefix = empty($propertyPath) ? '' : $propertyPath.'.';
foreach ($metadata->getConstrainedProperties() as $property) {
$this->walkProperty($metadata, $property, $object, $group, $pathPrefix.$property, $propagatedGroup);
}
}
$metadata->accept($this->visitor, $object, $group, $propertyPath);
}
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;
$localContext = new ExecutionContext(
$this->globalContext,
// 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));
}
}
$context = new ExecutionContext(
$this->visitor,
$metadata,
$value,
$propertyPath,
$group,
$currentClass,
$currentProperty
$propertyPath
);
$validator->initialize($localContext);
$validator->validate($value, $constraint);
$context->validateValue($value, $constraint);
}
}

View File

@ -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 @@ use Symfony\Component\Validator\Exception\GroupDefinitionException;
* @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 @@ class ClassMetadata extends ElementMetadata
$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,13 +263,21 @@ class ClassMetadata extends ElementMetadata
*
* @param string $property The name of the property
*
* @return array An array of MemberMetadata
* @return MemberMetadata[] An array of MemberMetadata
*/
public function getMemberMetadatas($property)
{
return $this->members[$property];
}
/**
* {@inheritdoc}
*/
public function getPropertyMetadata($property)
{
return $this->members[$property];
}
/**
* Returns all properties for which constraints are defined.
*

View File

@ -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 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
$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 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
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 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface
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 File

@ -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 File

@ -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 File

@ -40,7 +40,7 @@ class GetterMetadata extends MemberMetadata
/**
* {@inheritDoc}
*/
public function getValue($object)
public function getPropertyValue($object)
{
return $this->getReflectionMember()->invoke($object);
}

View File

@ -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 @@ abstract class MemberMetadata extends ElementMetadata
$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, $propertyPath, $this->isCollectionCascaded(), $this->isCollectionCascadedDeeply());
}
}
/**
* {@inheritDoc}
*/
@ -176,8 +188,14 @@ abstract class MemberMetadata extends ElementMetadata
* @param object $object The object
*
* @return mixed The property value
*
* @deprecated Deprecated since version 2.2, to be removed in 2.3. Use the
* method {@link getPropertyValue} instead.
*/
abstract public function getValue($object);
public function getValue($object)
{
return $this->getPropertyValue($object);
}
/**
* Returns the Reflection instance of the member

View File

@ -33,7 +33,7 @@ class PropertyMetadata extends MemberMetadata
/**
* {@inheritDoc}
*/
public function getValue($object)
public function getPropertyValue($object)
{
return $this->getReflectionMember()->getValue($object);
}

View File

@ -0,0 +1,40 @@
<?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;
/**
* Returns {@link MetadataInterface} instances for values.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface MetadataFactoryInterface
{
/**
* Returns the metadata for the given value.
*
* @param mixed $value Some value.
*
* @return MetadataInterface The metadata for the value.
*
* @throws Exception\NoSuchMetadataException If no metadata exists for the value.
*/
public function getMetadataFor($value);
/**
* Returns whether metadata exists for the given value.
*
* @param mixed $value Some value.
*
* @return Boolean Whether metadata exists for the value.
*/
public function hasMetadataFor($value);
}

View File

@ -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;
/**
* 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
* <tt>PropertyMetadataInterface</tt> inherits from <tt>MetadataInterface</tt>,
* 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
* <tt>accept()</tt> on all structurally related metadata instances.
*
* For example, to store constraints for PHP classes and their properties,
* create a class <tt>ClassMetadata</tt> (implementing <tt>MetadataInterface</tt>)
* and a class <tt>PropertyMetadata</tt> (implementing <tt>PropertyMetadataInterface</tt>).
* <tt>ClassMetadata::getPropertyMetadata($property)</tt> returns all
* <tt>PropertyMetadata</tt> instances for a property of that class. Its
* <tt>accept()</tt>-method simply forwards to <tt>ValidationVisitorInterface::visit()</tt>
* and calls <tt>accept()</tt> on all contained <tt>PropertyMetadata</tt>
* instances, which themselves call <tt>ValidationVisitorInterface::visit()</tt>
* again.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface MetadataInterface
{
/**
* Implementation of the Visitor design pattern.
*
* Calls {@link ValidationVisitorInterface::visit} and then forwards the
* <tt>accept()</tt>-call to all property metadata instances.
*
* @param ValidationVisitorInterface $visitor The visitor implementing the validation logic.
* @param mixed $value The value to validate.
* @param string|string[] $group The validation group to validate in.
* @param string $propertyPath The current property path in the validation graph.
*/
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath);
/**
* Returns all constraints for a given validation group.
*
* @param string $group The validation group.
*
* @return Constraint[] A list of constraint instances.
*/
public function findConstraints($group);
}

View File

@ -12,12 +12,13 @@
namespace Symfony\Component\Validator;
/**
* Interface for object initializers.
* Prepares an object for validation.
*
* Concrete implementations of this interface are used by the GraphWalker
* to initialize objects just before validating them/
* Concrete implementations of this interface are used by {@link ValidationVisitorInterface}
* to initialize objects just before validating them.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/

View File

@ -0,0 +1,33 @@
<?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 container for {@link PropertyMetadataInterface} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface PropertyMetadataContainerInterface
{
/**
* 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 <tt>BadMethodCallException</tt>).
*
* @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);
}

View File

@ -0,0 +1,44 @@
<?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 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 <bschussek@gmail.com>
*
* @see MetadataInterface
*/
interface PropertyMetadataInterface extends MetadataInterface
{
/**
* Returns the name of the property.
*
* @return string The property name.
*/
public function getPropertyName();
/**
* Extracts the value of the property from the given container.
*
* @param mixed $containingValue The container to extract the property value from.
*
* @return mixed The value of the property.
*/
public function getPropertyValue($containingValue);
}

View File

@ -11,27 +11,47 @@
namespace Symfony\Component\Validator\Tests;
use Symfony\Component\Validator\GlobalExecutionContext;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ExecutionContext;
class ExecutionContextTest extends \PHPUnit_Framework_TestCase
{
protected $walker;
protected $metadataFactory;
protected $globalContext;
protected $context;
private $visitor;
private $violations;
private $metadata;
private $metadataFactory;
private $globalContext;
/**
* @var ExecutionContext
*/
private $context;
protected function setUp()
{
$this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false);
$this->metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
$this->globalContext = new GlobalExecutionContext('Root', $this->walker, $this->metadataFactory);
$this->context = new ExecutionContext($this->globalContext, 'currentValue', 'foo.bar', 'Group', 'ClassName', 'propertyName');
$this->visitor = $this->getMockBuilder('Symfony\Component\Validator\ValidationVisitor')
->disableOriginalConstructor()
->getMock();
$this->violations = new ConstraintViolationList();
$this->metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
$this->metadataFactory = $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface');
$this->globalContext = $this->getMock('Symfony\Component\Validator\GlobalExecutionContextInterface');
$this->globalContext->expects($this->any())
->method('getRoot')
->will($this->returnValue('Root'));
$this->globalContext->expects($this->any())
->method('getViolations')
->will($this->returnValue($this->violations));
$this->globalContext->expects($this->any())
->method('getVisitor')
->will($this->returnValue($this->visitor));
$this->globalContext->expects($this->any())
->method('getMetadataFactory')
->will($this->returnValue($this->metadataFactory));
$this->context = new ExecutionContext($this->globalContext, $this->metadata, 'currentValue', 'Group', 'foo.bar');
}
protected function tearDown()
@ -45,18 +65,48 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase
$this->assertCount(0, $this->context->getViolations());
$this->assertSame('Root', $this->context->getRoot());
$this->assertSame('foo.bar', $this->context->getPropertyPath());
$this->assertSame('ClassName', $this->context->getCurrentClass());
$this->assertSame('propertyName', $this->context->getCurrentProperty());
$this->assertSame('Group', $this->context->getGroup());
$this->assertSame($this->walker, $this->context->getGraphWalker());
$this->visitor->expects($this->once())
->method('getGraphWalker')
->will($this->returnValue('GRAPHWALKER'));
// BC
$this->assertNull($this->context->getCurrentClass());
$this->assertNull($this->context->getCurrentProperty());
$this->assertSame('GRAPHWALKER', $this->context->getGraphWalker());
$this->assertSame($this->metadataFactory, $this->context->getMetadataFactory());
}
public function testInitWithClassMetadata()
{
// BC
$this->metadata = new ClassMetadata(__NAMESPACE__ . '\ExecutionContextTest_TestClass');
$this->context = new ExecutionContext($this->globalContext, $this->metadata, 'currentValue', 'Group', 'foo.bar');
$this->assertSame(__NAMESPACE__ . '\ExecutionContextTest_TestClass', $this->context->getCurrentClass());
$this->assertNull($this->context->getCurrentProperty());
}
public function testInitWithPropertyMetadata()
{
// BC
$this->metadata = new PropertyMetadata(__NAMESPACE__ . '\ExecutionContextTest_TestClass', 'myProperty');
$this->context = new ExecutionContext($this->globalContext, $this->metadata, 'currentValue', 'Group', 'foo.bar');
$this->assertSame(__NAMESPACE__ . '\ExecutionContextTest_TestClass', $this->context->getCurrentClass());
$this->assertSame('myProperty', $this->context->getCurrentProperty());
}
public function testClone()
{
$clone = clone $this->context;
$this->assertNotSame($this->context->getViolations(), $clone->getViolations());
// Cloning the context keeps the reference to the original violation
// list. This way we can efficiently duplicate context instances during
// the validation run and only modify the properties that need to be
// changed.
$this->assertSame($this->context->getViolations(), $clone->getViolations());
}
public function testAddViolation()
@ -170,10 +220,10 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase
)), $this->context->getViolations());
}
public function testAddViolationAtSubPath()
public function testAddViolationAt()
{
// override preconfigured property path
$this->context->addViolationAtSubPath('bam.baz', 'Error', array('foo' => 'bar'), 'invalid');
$this->context->addViolationAt('bam.baz', 'Error', array('foo' => 'bar'), 'invalid');
$this->assertEquals(new ConstraintViolationList(array(
new ConstraintViolation(
@ -186,9 +236,9 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase
)), $this->context->getViolations());
}
public function testAddViolationAtSubPathUsesPreconfiguredValueIfNotPassed()
public function testAddViolationAtUsesPreconfiguredValueIfNotPassed()
{
$this->context->addViolationAtSubPath('bam.baz', 'Error');
$this->context->addViolationAt('bam.baz', 'Error');
$this->assertEquals(new ConstraintViolationList(array(
new ConstraintViolation(
@ -201,11 +251,11 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase
)), $this->context->getViolations());
}
public function testAddViolationAtSubPathUsesPassedNullValue()
public function testAddViolationAtUsesPassedNullValue()
{
// passed null value should override preconfigured value "invalid"
$this->context->addViolationAtSubPath('bam.baz', 'Error', array('foo' => 'bar'), null);
$this->context->addViolationAtSubPath('bam.baz', 'Error', array('foo' => 'bar'), null, 1);
$this->context->addViolationAt('bam.baz', 'Error', array('foo' => 'bar'), null);
$this->context->addViolationAt('bam.baz', 'Error', array('foo' => 'bar'), null, 1);
$this->assertEquals(new ConstraintViolationList(array(
new ConstraintViolation(
@ -243,8 +293,13 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase
public function testGetPropertyPathWithEmptyCurrentPropertyPath()
{
$this->context = new ExecutionContext($this->globalContext, 'currentValue', '', 'Group', 'ClassName', 'propertyName');
$this->context = new ExecutionContext($this->globalContext, $this->metadata, 'currentValue', 'Group', '');
$this->assertEquals('bam.baz', $this->context->getPropertyPath('bam.baz'));
}
}
class ExecutionContextTest_TestClass
{
public $myProperty;
}

View File

@ -13,13 +13,13 @@ namespace Symfony\Component\Validator\Tests\Fixtures;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\ExecutionContext;
use Symfony\Component\Validator\ExecutionContextInterface;
class ConstraintAValidator extends ConstraintValidator
{
public static $passedContext;
public function initialize(ExecutionContext $context)
public function initialize(ExecutionContextInterface $context)
{
parent::initialize($context);

View File

@ -1,34 +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\Tests\Fixtures;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
class FakeClassMetadataFactory implements ClassMetadataFactoryInterface
{
protected $metadatas = array();
public function getClassMetadata($class)
{
if (!isset($this->metadatas[$class])) {
throw new \RuntimeException('No metadata for class ' . $class);
}
return $this->metadatas[$class];
}
public function addClassMetadata(ClassMetadata $metadata)
{
$this->metadatas[$metadata->getClassName()] = $metadata;
}
}

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Tests\Fixtures;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class FakeMetadataFactory implements MetadataFactoryInterface
{
protected $metadatas = array();
public function getMetadataFor($class)
{
if (is_object($class)) {
$class = get_class($class);
}
if (!is_string($class)) {
throw new NoSuchMetadataException('No metadata for type ' . gettype($class));
}
if (!isset($this->metadatas[$class])) {
throw new NoSuchMetadataException('No metadata for "' . $class . '"');
}
return $this->metadatas[$class];
}
public function hasMetadataFor($class)
{
if (is_object($class)) {
$class = get_class($class);
}
if (!is_string($class)) {
return false;
}
return isset($this->metadatas[$class]);
}
public function addMetadata(ClassMetadata $metadata)
{
$this->metadatas[$metadata->getClassName()] = $metadata;
}
}

View File

@ -1,63 +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\Tests;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\GlobalExecutionContext;
class GlobalExecutionContextTest extends \PHPUnit_Framework_TestCase
{
protected $walker;
protected $metadataFactory;
protected $context;
protected function setUp()
{
$this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false);
$this->metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
$this->context = new GlobalExecutionContext('Root', $this->walker, $this->metadataFactory);
}
protected function tearDown()
{
$this->walker = null;
$this->metadataFactory = null;
$this->context = null;
}
public function testInit()
{
$this->assertCount(0, $this->context->getViolations());
$this->assertSame('Root', $this->context->getRoot());
$this->assertSame($this->walker, $this->context->getGraphWalker());
$this->assertSame($this->metadataFactory, $this->context->getMetadataFactory());
}
public function testClone()
{
$clone = clone $this->context;
$this->assertNotSame($this->context->getViolations(), $clone->getViolations());
}
public function testAddViolation()
{
$violation = new ConstraintViolation('Error', array(), 'Root', 'foo.bar', 'invalid');
$this->context->addViolation($violation);
$this->assertEquals(new ConstraintViolationList(array($violation)), $this->context->getViolations());
}
}

View File

@ -12,10 +12,10 @@
namespace Symfony\Component\Validator\Tests;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintAValidator;
use Symfony\Component\Validator\ValidationVisitor;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\Reference;
use Symfony\Component\Validator\Tests\Fixtures\FakeClassMetadataFactory;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
use Symfony\Component\Validator\GraphWalker;
@ -30,20 +30,39 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
protected $factory;
/**
* @var ValidationVisitor
*/
private $visitor;
/**
* @var FakeMetadataFactory
*/
protected $metadataFactory;
/**
* @var GraphWalker
*/
protected $walker;
/**
* @var ClassMetadata
*/
protected $metadata;
protected function setUp()
{
$this->factory = new FakeClassMetadataFactory();
$this->walker = new GraphWalker('Root', $this->factory, new ConstraintValidatorFactory());
$this->metadataFactory = new FakeMetadataFactory();
$this->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory());
$this->walker = $this->visitor->getGraphWalker();
$this->metadata = new ClassMetadata(self::CLASSNAME);
$this->metadataFactory->addMetadata($this->metadata);
}
protected function tearDown()
{
$this->factory = null;
$this->metadataFactory = null;
$this->visitor = null;
$this->walker = null;
$this->metadata = null;
}
@ -82,6 +101,18 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
$this->assertCount(1, $this->walker->getViolations());
}
public function testWalkObjectOnceInVisitorAndOnceInWalkerValidatesConstraintsOnce()
{
$this->metadata->addConstraint(new ConstraintA());
$entity = new Entity();
$this->visitor->validate($entity, 'Default', '');
$this->walker->walkObject($this->metadata, $entity, 'Default', '');
$this->assertCount(1, $this->walker->getViolations());
}
public function testWalkDifferentObjectsValidatesTwice()
{
$this->metadata->addConstraint(new ConstraintA());
@ -165,7 +196,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
// propagated to the reference
'groups' => 'Default',
)));
$this->factory->addClassMetadata($referenceMetadata);
$this->metadataFactory->addMetadata($referenceMetadata);
$this->walker->walkObject($this->metadata, $entity, 'Default', '');
@ -235,7 +266,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
$this->metadataFactory->addMetadata($entityMetadata);
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
@ -268,7 +299,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
$this->metadataFactory->addMetadata($entityMetadata);
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
@ -300,8 +331,8 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
$this->factory->addClassMetadata(new ClassMetadata('ArrayIterator'));
$this->metadataFactory->addMetadata($entityMetadata);
$this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
@ -333,8 +364,8 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
$this->factory->addClassMetadata(new ClassMetadata('ArrayIterator'));
$this->metadataFactory->addMetadata($entityMetadata);
$this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
@ -361,8 +392,8 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
$this->factory->addClassMetadata(new ClassMetadata('ArrayIterator'));
$this->metadataFactory->addMetadata($entityMetadata);
$this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
@ -392,8 +423,8 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
$this->factory->addClassMetadata(new ClassMetadata('ArrayIterator'));
$this->metadataFactory->addMetadata($entityMetadata);
$this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
@ -465,7 +496,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
{
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
$this->setExpectedException('Symfony\Component\Validator\Exception\NoSuchMetadataException');
$this->walker->walkPropertyValue(
$this->metadata,

View File

@ -73,7 +73,7 @@ class MemberMetadataTest extends \PHPUnit_Framework_TestCase
class TestMemberMetadata extends MemberMetadata
{
public function getValue($object)
public function getPropertyValue($object)
{
}

View File

@ -0,0 +1,508 @@
<?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\Tests;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Tests\Fixtures\Reference;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintAValidator;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\ValidationVisitor;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValidationVisitorTest extends \PHPUnit_Framework_TestCase
{
const CLASS_NAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
/**
* @var ValidationVisitor
*/
private $visitor;
/**
* @var FakeMetadataFactory
*/
private $metadataFactory;
/**
* @var ClassMetadata
*/
private $metadata;
protected function setUp()
{
$this->metadataFactory = new FakeMetadataFactory();
$this->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory());
$this->metadata = new ClassMetadata(self::CLASS_NAME);
$this->metadataFactory->addMetadata($this->metadata);
}
protected function tearDown()
{
$this->metadataFactory = null;
$this->visitor = null;
$this->metadata = null;
}
public function testValidatePassesCorrectClassAndProperty()
{
$this->metadata->addConstraint(new ConstraintA());
$entity = new Entity();
$this->visitor->validate($entity, 'Default', '');
$context = ConstraintAValidator::$passedContext;
$this->assertEquals('Symfony\Component\Validator\Tests\Fixtures\Entity', $context->getClassName());
$this->assertNull($context->getPropertyName());
}
public function testValidateConstraints()
{
$this->metadata->addConstraint(new ConstraintA());
$this->visitor->validate(new Entity(), 'Default', '');
$this->assertCount(1, $this->visitor->getViolations());
}
public function testValidateTwiceValidatesConstraintsOnce()
{
$this->metadata->addConstraint(new ConstraintA());
$entity = new Entity();
$this->visitor->validate($entity, 'Default', '');
$this->visitor->validate($entity, 'Default', '');
$this->assertCount(1, $this->visitor->getViolations());
}
public function testValidateDifferentObjectsValidatesTwice()
{
$this->metadata->addConstraint(new ConstraintA());
$this->visitor->validate(new Entity(), 'Default', '');
$this->visitor->validate(new Entity(), 'Default', '');
$this->assertCount(2, $this->visitor->getViolations());
}
public function testValidateTwiceInDifferentGroupsValidatesTwice()
{
$this->metadata->addConstraint(new ConstraintA());
$this->metadata->addConstraint(new ConstraintA(array('groups' => 'Custom')));
$entity = new Entity();
$this->visitor->validate($entity, 'Default', '');
$this->visitor->validate($entity, 'Custom', '');
$this->assertCount(2, $this->visitor->getViolations());
}
public function testValidatePropertyConstraints()
{
$this->metadata->addPropertyConstraint('firstName', new ConstraintA());
$this->visitor->validate(new Entity(), 'Default', '');
$this->assertCount(1, $this->visitor->getViolations());
}
public function testValidateGetterConstraints()
{
$this->metadata->addGetterConstraint('lastName', new ConstraintA());
$this->visitor->validate(new Entity(), 'Default', '');
$this->assertCount(1, $this->visitor->getViolations());
}
public function testValidateInDefaultGroupTraversesGroupSequence()
{
$entity = new Entity();
$this->metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
'groups' => 'First',
)));
$this->metadata->addGetterConstraint('lastName', new FailingConstraint(array(
'groups' => 'Default',
)));
$this->metadata->setGroupSequence(array('First', $this->metadata->getDefaultGroup()));
$this->visitor->validate($entity, 'Default', '');
// After validation of group "First" failed, no more group was
// validated
$violations = new ConstraintViolationList(array(
new ConstraintViolation(
'Failed',
array(),
'Root',
'firstName',
''
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateInGroupSequencePropagatesDefaultGroup()
{
$entity = new Entity();
$entity->reference = new Reference();
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->metadata->setGroupSequence(array($this->metadata->getDefaultGroup()));
$referenceMetadata = new ClassMetadata(get_class($entity->reference));
$referenceMetadata->addConstraint(new FailingConstraint(array(
// this constraint is only evaluated if group "Default" is
// propagated to the reference
'groups' => 'Default',
)));
$this->metadataFactory->addMetadata($referenceMetadata);
$this->visitor->validate($entity, 'Default', '');
// The validation of the reference's FailingConstraint in group
// "Default" was launched
$violations = new ConstraintViolationList(array(
new ConstraintViolation(
'Failed',
array(),
'Root',
'reference',
$entity->reference
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateInOtherGroupTraversesNoGroupSequence()
{
$entity = new Entity();
$this->metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
'groups' => 'First',
)));
$this->metadata->addGetterConstraint('lastName', new FailingConstraint(array(
'groups' => $this->metadata->getDefaultGroup(),
)));
$this->metadata->setGroupSequence(array('First', $this->metadata->getDefaultGroup()));
$this->visitor->validate($entity, $this->metadata->getDefaultGroup(), '');
// Only group "Second" was validated
$violations = new ConstraintViolationList(array(
new ConstraintViolation(
'Failed',
array(),
'Root',
'lastName',
''
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyValidatesReferences()
{
$entity = new Entity();
$entity->reference = new Entity();
// add a constraint for the entity that always fails
$this->metadata->addConstraint(new FailingConstraint());
// validate entity when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
// invoke validation on an object
$this->visitor->validate($entity, 'Default', '');
$violations = new ConstraintViolationList(array(
// generated by the root object
new ConstraintViolation(
'Failed',
array(),
'Root',
'',
$entity
),
// generated by the reference
new ConstraintViolation(
'Failed',
array(),
'Root',
'reference',
$entity->reference
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyValidatesArraysByDefault()
{
$entity = new Entity();
$entity->reference = array('key' => new Entity());
// add a constraint for the entity that always fails
$this->metadata->addConstraint(new FailingConstraint());
// validate array when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->visitor->validate($entity, 'Default', '');
$violations = new ConstraintViolationList(array(
// generated by the root object
new ConstraintViolation(
'Failed',
array(),
'Root',
'',
$entity
),
// generated by the reference
new ConstraintViolation(
'Failed',
array(),
'Root',
'reference[key]',
$entity->reference['key']
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyValidatesTraversableByDefault()
{
$entity = new Entity();
$entity->reference = new \ArrayIterator(array('key' => new Entity()));
// add a constraint for the entity that always fails
$this->metadata->addConstraint(new FailingConstraint());
// validate array when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->visitor->validate($entity, 'Default', '');
$violations = new ConstraintViolationList(array(
// generated by the root object
new ConstraintViolation(
'Failed',
array(),
'Root',
'',
$entity
),
// generated by the reference
new ConstraintViolation(
'Failed',
array(),
'Root',
'reference[key]',
$entity->reference['key']
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyDoesNotValidateTraversableIfDisabled()
{
$entity = new Entity();
$entity->reference = new \ArrayIterator(array('key' => new Entity()));
$this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
// add a constraint for the entity that always fails
$this->metadata->addConstraint(new FailingConstraint());
// validate array when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid(array(
'traverse' => false,
)));
$this->visitor->validate($entity, 'Default', '');
$violations = new ConstraintViolationList(array(
// generated by the root object
new ConstraintViolation(
'Failed',
array(),
'Root',
'',
$entity
),
// nothing generated by the reference!
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testMetadataMayNotExistIfTraversalIsEnabled()
{
$entity = new Entity();
$entity->reference = new \ArrayIterator();
$this->metadata->addPropertyConstraint('reference', new Valid(array(
'traverse' => true,
)));
$this->visitor->validate($entity, 'Default', '');
}
/**
* @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
*/
public function testMetadataMustExistIfTraversalIsDisabled()
{
$entity = new Entity();
$entity->reference = new \ArrayIterator();
$this->metadata->addPropertyConstraint('reference', new Valid(array(
'traverse' => false,
)));
$this->visitor->validate($entity, 'Default', '');
}
public function testValidateCascadedPropertyDoesNotRecurseByDefault()
{
$entity = new Entity();
$entity->reference = new \ArrayIterator(array(
// The inner iterator should not be traversed by default
'key' => new \ArrayIterator(array(
'nested' => new Entity(),
)),
));
$this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
// add a constraint for the entity that always fails
$this->metadata->addConstraint(new FailingConstraint());
// validate iterator when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->visitor->validate($entity, 'Default', '');
$violations = new ConstraintViolationList(array(
// generated by the root object
new ConstraintViolation(
'Failed',
array(),
'Root',
'',
$entity
),
// nothing generated by the reference!
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyRecursesIfDeepIsSet()
{
$entity = new Entity();
$entity->reference = new \ArrayIterator(array(
// The inner iterator should now be traversed
'key' => new \ArrayIterator(array(
'nested' => new Entity(),
)),
));
// add a constraint for the entity that always fails
$this->metadata->addConstraint(new FailingConstraint());
// validate iterator when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid(array(
'deep' => true,
)));
$this->visitor->validate($entity, 'Default', '');
$violations = new ConstraintViolationList(array(
// generated by the root object
new ConstraintViolation(
'Failed',
array(),
'Root',
'',
$entity
),
// nothing generated by the reference!
new ConstraintViolation(
'Failed',
array(),
'Root',
'reference[key][nested]',
$entity->reference['key']['nested']
),
));
$this->assertEquals($violations, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyDoesNotValidateNestedScalarValues()
{
$entity = new Entity();
$entity->reference = array('scalar', 'values');
// validate array when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->visitor->validate($entity, 'Default', '');
$this->assertCount(0, $this->visitor->getViolations());
}
public function testValidateCascadedPropertyDoesNotValidateNullValues()
{
$entity = new Entity();
$entity->reference = null;
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->visitor->validate($entity, 'Default', '');
$this->assertCount(0, $this->visitor->getViolations());
}
/**
* @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
*/
public function testValidateCascadedPropertyRequiresObjectOrArray()
{
$entity = new Entity();
$entity->reference = 'no object';
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->visitor->validate($entity, 'Default', '');
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Tests;
use Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryAdapter;
use Symfony\Component\Validator\ValidatorContext;
class ValidatorContextTest extends \PHPUnit_Framework_TestCase
@ -56,6 +57,6 @@ class ValidatorContextTest extends \PHPUnit_Framework_TestCase
->setConstraintValidatorFactory($validatorFactory)
->getValidator();
$this->assertEquals(new Validator($metadataFactory, $validatorFactory), $validator);
$this->assertEquals(new Validator(new ClassMetadataFactoryAdapter($metadataFactory), $validatorFactory), $validator);
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Tests;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryAdapter;
use Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\ValidatorContext;
use Symfony\Component\Validator\ValidatorFactory;
@ -77,7 +78,7 @@ class ValidatorFactoryTest extends \PHPUnit_Framework_TestCase
$validator = $this->factory->getValidator();
$this->assertEquals(new Validator($metadataFactory, $validatorFactory), $validator);
$this->assertEquals(new Validator(new ClassMetadataFactoryAdapter($metadataFactory), $validatorFactory), $validator);
}
public function testBuildDefaultFromAnnotationsWithCustomNamespaces()

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\Validator\Tests;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity;
use Symfony\Component\Validator\Tests\Fixtures\FakeClassMetadataFactory;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
use Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\ConstraintViolation;
@ -24,18 +24,25 @@ use Symfony\Component\Validator\Mapping\ClassMetadata;
class ValidatorTest extends \PHPUnit_Framework_TestCase
{
protected $factory;
protected $validator;
/**
* @var FakeMetadataFactory
*/
private $metadataFactory;
/**
* @var Validator
*/
private $validator;
protected function setUp()
{
$this->factory = new FakeClassMetadataFactory();
$this->validator = new Validator($this->factory, new ConstraintValidatorFactory());
$this->metadataFactory = new FakeMetadataFactory();
$this->validator = new Validator($this->metadataFactory, new ConstraintValidatorFactory());
}
protected function tearDown()
{
$this->factory = null;
$this->metadataFactory = null;
$this->validator = null;
}
@ -47,7 +54,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
'groups' => 'Custom',
)));
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
// Only the constraint of group "Default" failed
$violations = new ConstraintViolationList();
@ -70,7 +77,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
'groups' => 'Custom',
)));
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
// Only the constraint of group "Custom" failed
$violations = new ConstraintViolationList();
@ -95,7 +102,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
'groups' => 'Second',
)));
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
// The constraints of both groups failed
$violations = new ConstraintViolationList();
@ -130,7 +137,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
'groups' => 'Second',
)));
$metadata->setGroupSequenceProvider(true);
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
@ -168,7 +175,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint());
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
$result = $this->validator->validateProperty($entity, 'firstName');
@ -180,7 +187,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint());
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
$result = $this->validator->validatePropertyValue(get_class($entity), 'firstName', 'Bernhard');
@ -202,21 +209,55 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException Symfony\Component\Validator\Exception\ValidatorException
* @expectedException \Symfony\Component\Validator\Exception\ValidatorException
*/
public function testValidateValueRejectsValid()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($metadata);
$this->metadataFactory->addMetadata($metadata);
$this->validator->validateValue($entity, new Valid());
}
/**
* @expectedException \Symfony\Component\Validator\Exception\ValidatorException
*/
public function testValidatePropertyFailsIfPropertiesNotSupported()
{
// $metadata does not implement PropertyMetadataContainerInterface
$metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
$this->metadataFactory = $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface');
$this->metadataFactory->expects($this->any())
->method('getMetadataFor')
->with('VALUE')
->will($this->returnValue($metadata));
$this->validator = new Validator($this->metadataFactory, new ConstraintValidatorFactory());
$this->validator->validateProperty('VALUE', 'someProperty');
}
/**
* @expectedException \Symfony\Component\Validator\Exception\ValidatorException
*/
public function testValidatePropertyValueFailsIfPropertiesNotSupported()
{
// $metadata does not implement PropertyMetadataContainerInterface
$metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
$this->metadataFactory = $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface');
$this->metadataFactory->expects($this->any())
->method('getMetadataFor')
->with('VALUE')
->will($this->returnValue($metadata));
$this->validator = new Validator($this->metadataFactory, new ConstraintValidatorFactory());
$this->validator->validatePropertyValue('VALUE', 'someProperty', 'propertyValue');
}
public function testGetMetadataFactory()
{
$this->assertInstanceOf(
'Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface',
'Symfony\Component\Validator\MetadataFactoryInterface',
$this->validator->getMetadataFactory()
);
}

View File

@ -0,0 +1,204 @@
<?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\Exception\NoSuchMetadataException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Default implementation of {@link ValidationVisitorInterface} and
* {@link GlobalExecutionContextInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionContextInterface
{
/**
* @var mixed
*/
private $root;
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/**
* @var ConstraintValidatorFactoryInterface
*/
private $validatorFactory;
/**
* @var array
*/
private $objectInitializers;
/**
* @var ConstraintViolationList
*/
private $violations;
/**
* @var array
*/
private $validatedObjects = array();
/**
* @var GraphWalker
*
* @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
private $graphWalker;
/**
* Creates a new validation visitor.
*
* @param mixed $root The value passed to the validator.
* @param MetadataFactoryInterface $metadataFactory The factory for obtaining metadata instances.
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating constraint validators.
* @param ObjectInitializerInterface[] $objectInitializers The initializers for preparing objects before validation.
*
* @throws UnexpectedTypeException If any of the object initializers is not an instance of ObjectInitializerInterface
*/
public function __construct($root, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
{
foreach ($objectInitializers as $initializer) {
if (!$initializer instanceof ObjectInitializerInterface) {
throw new UnexpectedTypeException($initializer, 'Symfony\Component\Validator\ObjectInitializerInterface');
}
}
$this->root = $root;
$this->metadataFactory = $metadataFactory;
$this->validatorFactory = $validatorFactory;
$this->objectInitializers = $objectInitializers;
$this->violations = new ConstraintViolationList();
}
/**
* {@inheritdoc}
*/
public function visit(MetadataInterface $metadata, $value, $group, $propertyPath)
{
$context = new ExecutionContext(
$this,
$metadata,
$value,
$group,
$propertyPath
);
$context->validateValue($value, $metadata->findConstraints($group));
}
/**
* {@inheritdoc}
*/
public function validate($value, $group, $propertyPath, $traverse = false, $deep = false)
{
if (null === $value) {
return;
}
if (is_object($value)) {
$hash = spl_object_hash($value);
// Exit, if the object is already validated for the current group
if (isset($this->validatedObjects[$hash][$group])) {
return;
}
// Remember validating this object before starting and possibly
// traversing the object graph
$this->validatedObjects[$hash][$group] = true;
foreach ($this->objectInitializers as $initializer) {
if (!$initializer instanceof ObjectInitializerInterface) {
throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.');
}
$initializer->initialize($value);
}
}
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->validate($element, $group, $propertyPath.'['.$key.']', $deep, $deep);
}
}
try {
$this->metadataFactory->getMetadataFor($value)->accept($this, $value, $group, $propertyPath);
} catch (NoSuchMetadataException $e) {
// Metadata doesn't necessarily have to exist for
// traversable objects, because we know how to validate
// them anyway. Optionally, additional metadata is supported.
}
} else {
$this->metadataFactory->getMetadataFor($value)->accept($this, $value, $group, $propertyPath);
}
}
/**
* {@inheritdoc}
*/
public function getGraphWalker()
{
if (null === $this->graphWalker) {
$this->graphWalker = new GraphWalker($this, $this->metadataFactory, $this->validatedObjects);
}
return $this->graphWalker;
}
/**
* {@inheritdoc}
*/
public function getViolations()
{
return $this->violations;
}
/**
* {@inheritdoc}
*/
public function getRoot()
{
return $this->root;
}
/**
* {@inheritdoc}
*/
public function getVisitor()
{
return $this;
}
/**
* {@inheritdoc}
*/
public function getValidatorFactory()
{
return $this->validatorFactory;
}
/**
* {@inheritdoc}
*/
public function getMetadataFactory()
{
return $this->metadataFactory;
}
}

View File

@ -0,0 +1,90 @@
<?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;
/**
* Validates values against constraints defined in {@link MetadataInterface}
* instances.
*
* This interface is an implementation of the Visitor design pattern. A value
* is validated by first passing it to the {@link validate} method. That method
* will determine the matching {@link MetadataInterface} for validating the
* value. It then calls the {@link MetadataInterface::accept} method of that
* metadata. <tt>accept()</tt> does two things:
*
* <ol>
* <li>It calls {@link visit} to validate the value against the constraints of
* the metadata.</li>
* <li>It calls <tt>accept()</tt> on all nested metadata instances with the
* corresponding values extracted from the current value. For example, if the
* current metadata represents a class and the current value is an object of
* that class, the metadata contains nested instances for each property of that
* class. It forwards the call to these nested metadata with the values of the
* corresponding properties in the original object.</li>
* </ol>
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ValidationVisitorInterface
{
/**
* Validates a value.
*
* If the value is an array or a traversable object, you can set the
* parameter <tt>$traverse</tt> to <tt>true</tt> in order to run through
* the collection and validate each element. If these elements can be
* collections again and you want to traverse them recursively, set the
* parameter <tt>$deep</tt> to <tt>true</tt> as well.
*
* If you set <tt>$traversable</tt> to <tt>true</tt>, the visitor will
* nevertheless try to find metadata for the collection and validate its
* constraints. If no such metadata is found, the visitor ignores that and
* only iterates the collection.
*
* If you don't set <tt>$traversable</tt> to <tt>true</tt> and the visitor
* does not find metadata for the given value, it will fail with an
* exception.
*
* @param mixed $value The value to validate.
* @param string $group The validation group to validate.
* @param string $propertyPath The current property path in the validation graph.
* @param Boolean $traverse Whether to traverse the value if it is traversable.
* @param Boolean $deep Whether to traverse nested traversable values recursively.
*
* @throws Exception\NoSuchMetadataException If no metadata can be found for
* the given value.
*/
public function validate($value, $group, $propertyPath, $traverse = false, $deep = false);
/**
* Validates a value against the constraints defined in some metadata.
*
* This method implements the Visitor design pattern. See also
* {@link ValidationVisitorInterface}.
*
* @param MetadataInterface $metadata The metadata holding the constraints.
* @param mixed $value The value to validate.
* @param string $group The validation group to validate.
* @param string $propertyPath The current property path in the validation graph.
*/
public function visit(MetadataInterface $metadata, $value, $group, $propertyPath);
/**
* Returns a graph walker with an alternative, deprecated API of the
* visitor.
*
* @return GraphWalker The graph walker.
*
* @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
public function getGraphWalker();
}

View File

@ -12,39 +12,44 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
use Symfony\Component\Validator\Exception\ValidatorException;
/**
* The default implementation of the ValidatorInterface.
*
* This service can be used to validate objects, properties and raw values
* against constraints.
* Default implementation of {@link ValidatorInterface}.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
class Validator implements ValidatorInterface
{
protected $metadataFactory;
protected $validatorFactory;
protected $validatorInitializers;
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/**
* @var ConstraintValidatorFactoryInterface
*/
private $validatorFactory;
/**
* @var array
*/
private $objectInitializers;
public function __construct(
ClassMetadataFactoryInterface $metadataFactory,
MetadataFactoryInterface $metadataFactory,
ConstraintValidatorFactoryInterface $validatorFactory,
array $validatorInitializers = array()
array $objectInitializers = array()
)
{
$this->metadataFactory = $metadataFactory;
$this->validatorFactory = $validatorFactory;
$this->validatorInitializers = $validatorInitializers;
$this->objectInitializers = $objectInitializers;
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function getMetadataFactory()
{
@ -53,91 +58,134 @@ class Validator implements ValidatorInterface
/**
* {@inheritDoc}
*
* @api
*/
public function validate($object, $groups = null)
public function getMetadataFor($value)
{
$metadata = $this->metadataFactory->getClassMetadata(get_class($object));
$walk = function(GraphWalker $walker, $group) use ($metadata, $object) {
return $walker->walkObject($metadata, $object, $group, '');
};
return $this->validateGraph($object, $walk, $groups);
return $this->metadataFactory->getMetadataFor($value);
}
/**
* {@inheritDoc}
*
* @api
*/
public function validateProperty($object, $property, $groups = null)
public function validate($value, $groups = null, $traverse = false, $deep = false)
{
$metadata = $this->metadataFactory->getClassMetadata(get_class($object));
$visitor = $this->createVisitor($value);
$walk = function(GraphWalker $walker, $group) use ($metadata, $property, $object) {
return $walker->walkProperty($metadata, $property, $object, $group, '');
};
return $this->validateGraph($object, $walk, $groups);
}
/**
* {@inheritDoc}
*
* @api
*/
public function validatePropertyValue($class, $property, $value, $groups = null)
{
$metadata = $this->metadataFactory->getClassMetadata($class);
$walk = function(GraphWalker $walker, $group) use ($metadata, $property, $value) {
return $walker->walkPropertyValue($metadata, $property, $value, $group, '');
};
return $this->validateGraph($class, $walk, $groups);
}
/**
* {@inheritDoc}
*
* @api
*/
public function validateValue($value, Constraint $constraint, $groups = null)
{
if ($constraint instanceof Valid) {
// Why can't the Valid constraint be executed directly?
//
// It cannot be executed like regular other constraints, because regular
// constraints are only executed *if they belong to the validated group*.
// The Valid constraint, on the other hand, is always executed and propagates
// the group to the cascaded object. The propagated group depends on
//
// * Whether a group sequence is currently being executed. Then the default
// group is propagated.
//
// * Otherwise the validated group is propagated.
throw new ValidatorException('The constraint ' . get_class($constraint) . ' cannot be validated. Use the method validate() instead.');
foreach ($this->resolveGroups($groups) as $group) {
$visitor->validate($value, $group, '');
}
$walk = function(GraphWalker $walker, $group) use ($constraint, $value) {
return $walker->walkConstraint($constraint, $value, $group, '');
};
return $this->validateGraph('', $walk, $groups);
return $visitor->getViolations();
}
protected function validateGraph($root, \Closure $walk, $groups = null)
/**
* {@inheritDoc}
*
* @throws ValidatorException If the metadata for the value does not support properties.
*/
public function validateProperty($containingValue, $property, $groups = null)
{
$walker = new GraphWalker($root, $this->metadataFactory, $this->validatorFactory, $this->validatorInitializers);
$groups = $groups ? (array) $groups : array(Constraint::DEFAULT_GROUP);
$visitor = $this->createVisitor($containingValue);
$metadata = $this->metadataFactory->getMetadataFor($containingValue);
foreach ($groups as $group) {
$walk($walker, $group);
if (!$metadata instanceof PropertyMetadataContainerInterface) {
$valueAsString = is_scalar($containingValue)
? '"' . $containingValue . '"'
: 'the value of type ' . gettype($containingValue);
throw new ValidatorException(sprintf('The metadata for ' . $valueAsString . ' does not support properties.'));
}
return $walker->getViolations();
foreach ($this->resolveGroups($groups) as $group) {
foreach ($metadata->getPropertyMetadata($property) as $propMeta) {
$propMeta->accept($visitor, $propMeta->getPropertyValue($containingValue), $group, $property);
}
}
return $visitor->getViolations();
}
/**
* {@inheritDoc}
*
* @throws ValidatorException If the metadata for the value does not support properties.
*/
public function validatePropertyValue($containingValue, $property, $value, $groups = null)
{
$visitor = $this->createVisitor($containingValue);
$metadata = $this->metadataFactory->getMetadataFor($containingValue);
if (!$metadata instanceof PropertyMetadataContainerInterface) {
$valueAsString = is_scalar($containingValue)
? '"' . $containingValue . '"'
: 'the value of type ' . gettype($containingValue);
throw new ValidatorException(sprintf('The metadata for ' . $valueAsString . ' does not support properties.'));
}
foreach ($this->resolveGroups($groups) as $group) {
foreach ($metadata->getPropertyMetadata($property) as $propMeta) {
$propMeta->accept($visitor, $value, $group, $property);
}
}
return $visitor->getViolations();
}
/**
* {@inheritDoc}
*/
public function validateValue($value, $constraints, $groups = null)
{
$context = new ExecutionContext($this->createVisitor(null));
$constraints = is_array($constraints) ? $constraints : array($constraints);
foreach ($constraints as $constraint) {
if ($constraint instanceof Valid) {
// Why can't the Valid constraint be executed directly?
//
// It cannot be executed like regular other constraints, because regular
// constraints are only executed *if they belong to the validated group*.
// The Valid constraint, on the other hand, is always executed and propagates
// the group to the cascaded object. The propagated group depends on
//
// * Whether a group sequence is currently being executed. Then the default
// group is propagated.
//
// * Otherwise the validated group is propagated.
throw new ValidatorException(
sprintf(
'The constraint %s cannot be validated. Use the method validate() instead.',
get_class($constraint)
)
);
}
$context->validateValue($value, $constraint, $groups);
}
return $context->getViolations();
}
/**
* @param mixed $root
*
* @return ValidationVisitor
*/
private function createVisitor($root)
{
return new ValidationVisitor($root, $this->metadataFactory, $this->validatorFactory, $this->objectInitializers);
}
/**
* @param null|string|string[] $groups
*
* @return string[]
*/
private function resolveGroups($groups)
{
return $groups ? (array) $groups : array(Constraint::DEFAULT_GROUP);
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Mapping\ClassMetadataFactory;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryAdapter;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
@ -60,7 +61,7 @@ class ValidatorBuilder implements ValidatorBuilderInterface
private $annotationReader = null;
/**
* @var ClassMetadataFactoryInterface
* @var MetadataFactoryInterface
*/
private $metadataFactory;
@ -213,12 +214,17 @@ class ValidatorBuilder implements ValidatorBuilderInterface
/**
* {@inheritdoc}
*/
public function setMetadataFactory(ClassMetadataFactoryInterface $metadataFactory)
public function setMetadataFactory($metadataFactory)
{
if (count($this->xmlMappings) > 0 || count($this->yamlMappings) > 0 || count($this->methodMappings) > 0 || null !== $this->annotationReader) {
throw new ValidatorException('You cannot set a custom metadata factory after adding custom mappings. You should do either of both.');
}
if ($metadataFactory instanceof ClassMetadataFactoryInterface
&& !$metadataFactory instanceof MetadataFactoryInterface) {
$metadataFactory = new ClassMetadataFactoryAdapter($metadataFactory);
}
$this->metadataFactory = $metadataFactory;
return $this;

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
use Doctrine\Common\Annotations\Reader;
@ -113,11 +112,14 @@ interface ValidatorBuilderInterface
/**
* Sets the class metadata factory used by the validator.
*
* @param ClassMetadataFactoryInterface $metadataFactory The metadata factory.
* As of Symfony 2.3, the first parameter of this method will be typed
* against {@link MetadataFactoryInterface}.
*
* @param MetadataFactoryInterface|Mapping\ClassMetadataFactoryInterface $metadataFactory The metadata factory.
*
* @return ValidatorBuilderInterface The builder object.
*/
public function setMetadataFactory(ClassMetadataFactoryInterface $metadataFactory);
public function setMetadataFactory($metadataFactory);
/**
* Sets the cache for caching class metadata.

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryAdapter;
/**
* Default implementation of ValidatorContextInterface
@ -23,6 +24,11 @@ use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
*/
class ValidatorContext implements ValidatorContextInterface
{
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/**
* The class metadata factory used in the new validator
* @var ClassMetadataFactoryInterface
@ -43,6 +49,12 @@ class ValidatorContext implements ValidatorContextInterface
*/
public function setClassMetadataFactory(ClassMetadataFactoryInterface $classMetadataFactory)
{
if ($classMetadataFactory instanceof MetadataFactoryInterface) {
$this->metadataFactory = $classMetadataFactory;
} else {
$this->metadataFactory = new ClassMetadataFactoryAdapter($classMetadataFactory);
}
$this->classMetadataFactory = $classMetadataFactory;
return $this;
@ -70,7 +82,7 @@ class ValidatorContext implements ValidatorContextInterface
public function getValidator()
{
return new Validator(
$this->classMetadataFactory,
$this->metadataFactory,
$this->constraintValidatorFactory
);
}

View File

@ -14,7 +14,7 @@ namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Constraint;
/**
* Validates a given value.
* Validates values and graphs of objects and arrays.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
@ -23,61 +23,77 @@ use Symfony\Component\Validator\Constraint;
interface ValidatorInterface
{
/**
* Validate the given object.
* Validates a value.
*
* @param object $object The object to validate
* @param array|null $groups The validator groups to use for validating
* The accepted values depend on the {@link MetadataFactoryInterface}
* implementation.
*
* @return ConstraintViolationList
* @param mixed $value The value to validate
* @param array|null $groups The validation groups to validate.
* @param Boolean $traverse Whether to traverse the value if it is traversable.
* @param Boolean $deep Whether to traverse nested traversable values recursively.
*
* @return ConstraintViolationListInterface A list of constraint violations. If the
* list is empty, validation succeeded.
*
* @api
*/
public function validate($object, $groups = null);
public function validate($value, $groups = null, $traverse = false, $deep = false);
/**
* Validate a single property of an object against its current value.
* Validates a property of a value against its current value.
*
* @param object $object The object to validate
* @param string $property The name of the property to validate
* @param array|null $groups The validator groups to use for validating
* The accepted values depend on the {@link MetadataFactoryInterface}
* implementation.
*
* @return ConstraintViolationList
* @param mixed $containingValue The value containing the property.
* @param string $property 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.
*
* @api
*/
public function validateProperty($object, $property, $groups = null);
public function validateProperty($containingValue, $property, $groups = null);
/**
* Validate a single property of an object against the given value.
* Validate a property of a value against a potential value.
*
* @param string $class The class on which the property belongs
* @param string $property The name of the property to validate
* @param string $value
* @param array|null $groups The validator groups to use for validating
* The accepted values depend on the {@link MetadataFactoryInterface}
* implementation.
*
* @return ConstraintViolationList
* @param string $containingValue The value containing the property.
* @param string $property 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.
*
* @api
*/
public function validatePropertyValue($class, $property, $value, $groups = null);
public function validatePropertyValue($containingValue, $property, $value, $groups = null);
/**
* Validates a given value against a specific Constraint.
* Validates a value against a constraint or a list of constraints.
*
* @param mixed $value The value to validate
* @param Constraint $constraint The constraint to validate against
* @param array|null $groups The validator groups to use for validating
* @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 ConstraintViolationList
* @return ConstraintViolationListInterface A list of constraint violations. If the
* list is empty, validation succeeded.
*
* @api
*/
public function validateValue($value, Constraint $constraint, $groups = null);
public function validateValue($value, $constraints, $groups = null);
/**
* Returns the factory for ClassMetadata instances
* Returns the factory for metadata instances.
*
* @return Mapping\ClassMetadataFactoryInterface
* @return MetadataFactoryInterface The metadata factory.
*
* @api
*/