diff -r 000000000000 -r 7f95f8617b0b vendor/symfony/src/Symfony/Component/Validator/GraphWalker.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/symfony/src/Symfony/Component/Validator/GraphWalker.php Sat Sep 24 15:40:41 2011 +0200 @@ -0,0 +1,190 @@ + + * + * 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\ConstraintValidatorFactoryInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\MemberMetadata; + +/** + * Responsible for walking over and initializing validation on different + * types of items. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +class GraphWalker +{ + protected $context; + protected $validatorFactory; + protected $metadataFactory; + protected $validatorInitializers = array(); + protected $validatedObjects = array(); + + public function __construct($root, ClassMetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $factory, array $validatorInitializers = array()) + { + $this->context = new ExecutionContext($root, $this, $metadataFactory); + $this->validatorFactory = $factory; + $this->metadataFactory = $metadataFactory; + $this->validatorInitializers = $validatorInitializers; + } + + /** + * @return ConstraintViolationList + */ + public function getViolations() + { + return $this->context->getViolations(); + } + + /** + * Initialize validation on the given object using the given metadata + * instance and validation group. + * + * @param ClassMetadata $metadata + * @param object $object The object to validate + * @param string $group The validator group to use for validation + * @param string $propertyPath + */ + 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); + } + + $this->context->setCurrentClass($metadata->getClassName()); + + if ($group === Constraint::DEFAULT_GROUP && $metadata->hasGroupSequence()) { + $groups = $metadata->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])) { + if (isset($this->validatedObjects[$hash][$group])) { + return; + } + } else { + $this->validatedObjects[$hash] = array(); + } + + // Remember validating this object before starting and possibly + // traversing the object graph + $this->validatedObjects[$hash][$group] = true; + + foreach ($metadata->findConstraints($group) as $constraint) { + $this->walkConstraint($constraint, $object, $group, $propertyPath); + } + + if (null !== $object) { + foreach ($metadata->getConstrainedProperties() as $property) { + $localPropertyPath = empty($propertyPath) ? $property : $propertyPath.'.'.$property; + + $this->walkProperty($metadata, $property, $object, $group, $localPropertyPath, $propagatedGroup); + } + } + } + + public function walkProperty(ClassMetadata $metadata, $property, $object, $group, $propertyPath, $propagatedGroup = null) + { + foreach ($metadata->getMemberMetadatas($property) as $member) { + $this->walkMember($member, $member->getValue($object), $group, $propertyPath, $propagatedGroup); + } + } + + public function walkPropertyValue(ClassMetadata $metadata, $property, $value, $group, $propertyPath) + { + foreach ($metadata->getMemberMetadatas($property) as $member) { + $this->walkMember($member, $value, $group, $propertyPath); + } + } + + protected function walkMember(MemberMetadata $metadata, $value, $group, $propertyPath, $propagatedGroup = null) + { + $this->context->setCurrentProperty($metadata->getPropertyName()); + + foreach ($metadata->findConstraints($group) as $constraint) { + $this->walkConstraint($constraint, $value, $group, $propertyPath); + } + + if ($metadata->isCascaded()) { + $this->walkReference($value, $propagatedGroup ?: $group, $propertyPath, $metadata->isCollectionCascaded()); + } + } + + public function walkReference($value, $group, $propertyPath, $traverse) + { + 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)) { + $this->walkReference($element, $group, $propertyPath.'['.$key.']', $traverse); + } + } + } + + if (is_object($value)) { + $metadata = $this->metadataFactory->getClassMetadata(get_class($value)); + $this->walkObject($metadata, $value, $group, $propertyPath); + } + } + } + + public function walkConstraint(Constraint $constraint, $value, $group, $propertyPath) + { + $validator = $this->validatorFactory->getInstance($constraint); + + $this->context->setPropertyPath($propertyPath); + $this->context->setGroup($group); + + $validator->initialize($this->context); + + if (!$validator->isValid($value, $constraint)) { + // Resetting the property path. This is needed because some + // validators, like CollectionValidator, use the walker internally + // and so change the context. + $this->context->setPropertyPath($propertyPath); + + $this->context->addViolation( + $validator->getMessageTemplate(), + $validator->getMessageParameters(), + $value + ); + } + } +}