From 373d925dffbf91bb81c31e7416ff3e10f80c36b0 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Tue, 5 May 2015 10:03:32 -0700 Subject: [PATCH] Issue #2343035 by fago, dawehner, larowlan, ianthomas_uk, cilefen, hussainweb, Miroling, jibran, beejeebus, dashaforbes, webmozart, klausi: Upgrade validator integration for Symfony versions 2.5+ --- core/composer.json | 2 +- core/composer.lock | 34 +- .../Core/TypedData/TypedDataManager.php | 17 +- .../Validation/ConstraintViolationBuilder.php | 254 +++++++++++++ .../ContextualValidatorInterface.php | 38 ++ .../TypedData/Validation/ExecutionContext.php | 320 ++++++++++++++++ .../Validation/ExecutionContextFactory.php | 60 +++ .../Core/TypedData/Validation/Metadata.php | 115 ------ .../TypedData/Validation/MetadataFactory.php | 62 ---- .../Validation/PropertyContainerMetadata.php | 72 ---- .../RecursiveContextualValidator.php | 239 ++++++++++++ .../Validation/RecursiveValidator.php | 121 ++++++ .../TypedDataAwareValidatorTrait.php | 36 ++ .../Validation/TypedDataMetadata.php | 77 ++++ .../Core/Validation/ConstraintManager.php | 11 +- .../AllowedValuesConstraintValidator.php | 38 +- .../ComplexDataConstraintValidator.php | 34 +- .../Constraint/NotNullConstraint.php | 23 ++ .../Constraint/NotNullConstraintValidator.php | 36 ++ .../Validation/Constraint/NullConstraint.php | 23 ++ .../Constraint/NullConstraintValidator.php | 36 ++ .../PrimitiveTypeConstraintValidator.php | 5 +- .../Constraint/UniqueFieldValueValidator.php | 6 +- .../ForumLeafConstraintValidator.php | 6 +- .../quickedit/src/Form/QuickEditFieldForm.php | 8 +- .../Constraint/UserMailRequired.php | 2 +- .../RecursiveContextualValidatorTest.php | 349 ++++++++++++++++++ .../PrimitiveTypeConstraintValidatorTest.php | 63 ++-- core/vendor/composer/ClassLoader.php | 8 +- core/vendor/composer/installed.json | 156 ++++---- .../Component/Validator/Constraint.php | 2 +- .../Validator/Constraints/ChoiceValidator.php | 2 +- .../Component/Validator/DefaultTranslator.php | 2 +- .../Factory/LazyLoadingMetadataFactory.php | 4 +- .../Factory/MetadataFactoryInterface.php | 2 +- .../Mapping/Loader/AnnotationLoader.php | 4 +- .../Mapping/Loader/YamlFileLoader.php | 2 +- .../Symfony/Component/Validator/README.md | 2 +- .../Tests/ConstraintViolationTest.php | 2 +- .../Tests/Constraints/ChoiceValidatorTest.php | 17 + .../Validator/Tests/Util/PropertyPathTest.php | 1 + .../Component/Validator/Util/PropertyPath.php | 3 +- .../RecursiveContextualValidator.php | 7 +- .../Validator/ValidatorInterface.php | 2 +- .../Symfony/Component/Validator/composer.json | 4 +- 45 files changed, 1855 insertions(+), 452 deletions(-) create mode 100644 core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/ContextualValidatorInterface.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/ExecutionContext.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php delete mode 100644 core/lib/Drupal/Core/TypedData/Validation/Metadata.php delete mode 100644 core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php delete mode 100644 core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/RecursiveValidator.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php create mode 100644 core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadata.php create mode 100644 core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NotNullConstraint.php create mode 100644 core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NotNullConstraintValidator.php create mode 100644 core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NullConstraint.php create mode 100644 core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NullConstraintValidator.php create mode 100644 core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php diff --git a/core/composer.json b/core/composer.json index 4f6b2507690..5f645147ee3 100644 --- a/core/composer.json +++ b/core/composer.json @@ -14,7 +14,7 @@ "symfony/http-kernel": "2.6.*", "symfony/routing": "2.6.*", "symfony/serializer": "2.6.*", - "symfony/validator": "2.6.*", + "symfony/validator": "2.6.*@dev", "symfony/process": "2.6.*", "symfony/yaml": "2.6.*", "twig/twig": "1.18.*", diff --git a/core/composer.lock b/core/composer.lock index 5022e171069..efa5529d5fa 100644 --- a/core/composer.lock +++ b/core/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c523fe48318d98a520d2bc45286106e2", + "hash": "ba1a97bf2c0bcef4fb771231a4c1fbdb", "packages": [ { "name": "behat/mink", @@ -461,7 +461,7 @@ }, "dist": { "type": "zip", - "url": "https://github.com/doctrine/inflector/archive/v1.0.zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/54b8333d2a5682afdc690060c1cf384ba9f47f08", "reference": "v1.0", "shasum": "" }, @@ -578,7 +578,7 @@ }, "dist": { "type": "zip", - "url": "https://github.com/doctrine/lexer/archive/v1.0.zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb", "reference": "v1.0", "shasum": "" }, @@ -1530,12 +1530,12 @@ "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log", + "url": "https://github.com/php-fig/log.git", "reference": "1.0.0" }, "dist": { "type": "zip", - "url": "https://github.com/php-fig/log/archive/1.0.0.zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", "reference": "1.0.0", "shasum": "" }, @@ -2891,17 +2891,17 @@ }, { "name": "symfony/validator", - "version": "v2.6.6", + "version": "2.6.x-dev", "target-dir": "Symfony/Component/Validator", "source": { "type": "git", "url": "https://github.com/symfony/Validator.git", - "reference": "85d9b42fe71bf88e7a1e5dec2094605dc9fbff28" + "reference": "6bb1b474d25cb80617d8da6cb14c955ba914e495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Validator/zipball/85d9b42fe71bf88e7a1e5dec2094605dc9fbff28", - "reference": "85d9b42fe71bf88e7a1e5dec2094605dc9fbff28", + "url": "https://api.github.com/repos/symfony/Validator/zipball/6bb1b474d25cb80617d8da6cb14c955ba914e495", + "reference": "6bb1b474d25cb80617d8da6cb14c955ba914e495", "shasum": "" }, "require": { @@ -2948,18 +2948,18 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony Validator Component", - "homepage": "http://symfony.com", - "time": "2015-03-30 15:54:10" + "homepage": "https://symfony.com", + "time": "2015-05-05 01:29:27" }, { "name": "symfony/yaml", @@ -3221,7 +3221,9 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "symfony/validator": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index cd7ad17118f..d9545db67b4 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -13,11 +13,11 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; -use Drupal\Core\TypedData\Validation\MetadataFactory; +use Drupal\Core\TypedData\Validation\ExecutionContextFactory; +use Drupal\Core\TypedData\Validation\RecursiveValidator; use Drupal\Core\Validation\ConstraintManager; use Drupal\Core\Validation\ConstraintValidatorFactory; use Drupal\Core\Validation\DrupalTranslator; -use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validator\ValidatorInterface; /** @@ -28,7 +28,7 @@ class TypedDataManager extends DefaultPluginManager { /** * The validator used for validating typed data. * - * @var \Symfony\Component\Validator\ValidatorInterface + * @var \Symfony\Component\Validator\Validator\ValidatorInterface */ protected $validator; @@ -331,12 +331,11 @@ class TypedDataManager extends DefaultPluginManager { */ public function getValidator() { if (!isset($this->validator)) { - $this->validator = Validation::createValidatorBuilder() - ->setMetadataFactory(new MetadataFactory($this)) - ->setTranslator(new DrupalTranslator()) - ->setConstraintValidatorFactory(new ConstraintValidatorFactory($this->classResolver)) - ->setApiVersion(Validation::API_VERSION_2_4) - ->getValidator(); + $this->validator = new RecursiveValidator( + new ExecutionContextFactory(new DrupalTranslator()), + new ConstraintValidatorFactory($this->classResolver), + $this + ); } return $this->validator; } diff --git a/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php b/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php new file mode 100644 index 00000000000..eb785e5a664 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/ConstraintViolationBuilder.php @@ -0,0 +1,254 @@ +violations = $violations; + $this->message = $message; + $this->parameters = $parameters; + $this->root = $root; + $this->propertyPath = $propertyPath; + $this->invalidValue = $invalidValue; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->constraint = $constraint; + } + + /** + * {@inheritdoc} + */ + public function atPath($path) + { + $this->propertyPath = PropertyPath::append($this->propertyPath, $path); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setParameter($key, $value) + { + $this->parameters[$key] = $value; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setTranslationDomain($translationDomain) + { + $this->translationDomain = $translationDomain; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setInvalidValue($invalidValue) + { + $this->invalidValue = $invalidValue; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setPlural($number) + { + $this->plural = $number; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCause($cause) + { + $this->cause = $cause; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function addViolation() + { + if (null === $this->plural) { + $translatedMessage = $this->translator->trans( + $this->message, + $this->parameters, + $this->translationDomain + ); + } else { + try { + $translatedMessage = $this->translator->transChoice( + $this->message, + $this->plural, + $this->parameters, + $this->translationDomain# + ); + } catch (\InvalidArgumentException $e) { + $translatedMessage = $this->translator->trans( + $this->message, + $this->parameters, + $this->translationDomain + ); + } + } + + $this->violations->add(new ConstraintViolation( + $translatedMessage, + $this->message, + $this->parameters, + $this->root, + $this->propertyPath, + $this->invalidValue, + $this->plural, + $this->code, + $this->constraint, + $this->cause + )); + } +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/ContextualValidatorInterface.php b/core/lib/Drupal/Core/TypedData/Validation/ContextualValidatorInterface.php new file mode 100644 index 00000000000..53f9acf9e5c --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/ContextualValidatorInterface.php @@ -0,0 +1,38 @@ +validator = $validator; + $this->root = $root; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + $this->violations = new ConstraintViolationList(); + } + + /** + * {@inheritdoc} + */ + public function setNode($value, $object, MetadataInterface $metadata = NULL, $propertyPath) { + $this->value = $value; + $this->data = $object; + $this->metadata = $metadata; + $this->propertyPath = (string) $propertyPath; + } + + /** + * {@inheritdoc} + */ + public function setGroup($group) { + $this->group = $group; + } + + /** + * {@inheritdoc} + */ + public function setConstraint(Constraint $constraint) { + $this->constraint = $constraint; + } + + /** + * {@inheritdoc} + */ + public function addViolation($message, array $parameters = array(), $invalidValue = NULL, $plural = NULL, $code = NULL) { + // The parameters $invalidValue and following are ignored by the new + // API, as they are not present in the new interface anymore. + // You should use buildViolation() instead. + if (func_num_args() > 2) { + throw new \LogicException('Legacy validator API is unsupported.'); + } + + $this->violations->add(new ConstraintViolation($this->translator->trans($message, $parameters, $this->translationDomain), $message, $parameters, $this->root, $this->propertyPath, $this->value, NULL, NULL, $this->constraint)); + } + + /** + * {@inheritdoc} + */ + public function buildViolation($message, array $parameters = array()) { + return new ConstraintViolationBuilder($this->violations, $this->constraint, $message, $parameters, $this->root, $this->propertyPath, $this->value, $this->translator, $this->translationDomain); + } + + /** + * {@inheritdoc} + */ + public function getViolations() { + return $this->violations; + } + + /** + * {@inheritdoc} + */ + public function getValidator() { + return $this->validator; + } + + /** + * {@inheritdoc} + */ + public function getRoot() { + return $this->root; + } + + /** + * {@inheritdoc} + */ + public function getValue() { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getObject() { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function getMetadata() { + return $this->metadata; + } + + /** + * {@inheritdoc} + */ + public function getGroup() { + return Constraint::DEFAULT_GROUP; + } + + /** + * {@inheritdoc} + */ + public function getClassName() { + return get_class($this->data); + } + + /** + * {@inheritdoc} + */ + public function getPropertyName() { + return $this->data->getName(); + } + + /** + * {@inheritdoc} + */ + public function getPropertyPath($sub_path = '') { + return PropertyPath::append($this->propertyPath, $sub_path); + } + + /** + * {@inheritdoc} + */ + public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = NULL, $plural = NULL, $code = NULL) { + throw new \LogicException('Legacy validator API is unsupported.'); + } + + /** + * {@inheritdoc} + */ + public function validate($value, $subPath = '', $groups = NULL, $traverse = FALSE, $deep = FALSE) { + throw new \LogicException('Legacy validator API is unsupported.'); + } + + /** + * {@inheritdoc} + */ + public function markConstraintAsValidated($cache_key, $constraint_hash) { + $this->validatedConstraints[$cache_key . ':' . $constraint_hash] = TRUE; + } + + /** + * {@inheritdoc} + */ + public function isConstraintValidated($cache_key, $constraint_hash) { + return isset($this->validatedConstraints[$cache_key . ':' . $constraint_hash]); + } + + /** + * {@inheritdoc} + */ + public function validateValue($value, $constraints, $subPath = '', $groups = NULL) { + throw new \LogicException('Legacy validator API is unsupported.'); + } + + /** + * {@inheritdoc} + */ + public function markGroupAsValidated($cache_key, $group_hash) { + $this->validatedObjects[$cache_key][$group_hash] = TRUE; + } + + /** + * {@inheritdoc} + */ + public function isGroupValidated($cache_key, $group_hash) { + return isset($this->validatedObjects[$cache_key][$group_hash]); + } + + /** + * {@inheritdoc} + */ + public function markObjectAsInitialized($cache_key) { + // Not supported, so nothing todo. + } + + /** + * {@inheritdoc} + */ + public function isObjectInitialized($cache_key) { + // Not supported, so nothing todo. + } + + /** + * {@inheritdoc} + */ + public function getMetadataFactory() { + throw new \LogicException('Legacy validator API is unsupported.'); + } +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php b/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php new file mode 100644 index 00000000000..be7c8d4284f --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php @@ -0,0 +1,60 @@ +translator = $translator; + $this->translationDomain = $translationDomain; + } + + /** + * {@inheritdoc} + */ + public function createContext(ValidatorInterface $validator, $root) + { + return new ExecutionContext( + $validator, + $root, + $this->translator, + $this->translationDomain + ); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php deleted file mode 100644 index 6fe52558f59..00000000000 --- a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php +++ /dev/null @@ -1,115 +0,0 @@ -typedData = $typed_data; - $this->name = $name; - $this->factory = $factory; - $this->typedDataManager = $typed_data_manager; - } - - /** - * Implements MetadataInterface::accept(). - */ - public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) { - - // @todo: Do we have to care about groups? Symfony class metadata has - // $propagatedGroup. - - $visitor->visit($this, $this->typedDataManager->getCanonicalRepresentation($typed_data), $group, $propertyPath); - } - - /** - * Implements MetadataInterface::findConstraints(). - */ - public function findConstraints($group) { - return $this->typedData->getConstraints(); - } - - /** - * Returns the name of the property. - * - * @return string The property name. - */ - public function getPropertyName() { - return $this->name; - } - - /** - * Extracts the value of the property from the given container. - * - * @param mixed $container The container to extract the property value from. - * - * @return mixed The value of the property. - */ - public function getPropertyValue($container) { - return $this->typedDataManager->getCanonicalRepresentation($this->typedData); - } - - /** - * Returns the typed data object. - * - * @return \Drupal\Core\TypedData\TypedDataInterface - * The typed data object. - */ - public function getTypedData() { - return $this->typedData; - } -} diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php deleted file mode 100644 index fcd0557641a..00000000000 --- a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php +++ /dev/null @@ -1,62 +0,0 @@ -typedDataManager = $typed_data_manager; - } - - /** - * {@inheritdoc} - * - * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data - * Some typed data object containing the value to validate. - * @param $name - * (optional) The name of the property to get metadata for. Leave empty, if - * the data is the root of the typed data tree. - */ - public function getMetadataFor($typed_data, $name = '') { - if (!$typed_data instanceof TypedDataInterface) { - throw new \InvalidArgumentException('The passed value must be a typed data object.'); - } - $is_container = $typed_data instanceof ComplexDataInterface || $typed_data instanceof ListInterface; - $class = '\Drupal\Core\TypedData\Validation\\' . ($is_container ? 'PropertyContainerMetadata' : 'Metadata'); - return new $class($typed_data, $name, $this, $this->typedDataManager); - } - - /** - * Implements MetadataFactoryInterface::hasMetadataFor(). - */ - public function hasMetadataFor($value) { - return $value instanceof TypedDataInterface; - } -} diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php deleted file mode 100644 index 80d3320c3f0..00000000000 --- a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php +++ /dev/null @@ -1,72 +0,0 @@ -isEmpty()) { - $data = NULL; - } - else { - $data = $this->typedDataManager->getCanonicalRepresentation($typed_data); - } - $visitor->visit($this, $data, $group, $propertyPath); - $pathPrefix = isset($propertyPath) && $propertyPath !== '' ? $propertyPath . '.' : ''; - - // Only continue validating if the data is not empty. - if ($data) { - foreach ($typed_data as $name => $data) { - $metadata = $this->factory->getMetadataFor($data, $name); - $metadata->accept($visitor, $data, $group, $pathPrefix . $name); - } - } - } - - /** - * Implements PropertyMetadataContainerInterface::hasPropertyMetadata(). - */ - public function hasPropertyMetadata($property_name) { - try { - $exists = (bool)$this->getPropertyMetadata($property_name); - } - catch (\LogicException $e) { - $exists = FALSE; - } - return $exists; - } - - /** - * Implements PropertyMetadataContainerInterface::getPropertyMetadata(). - */ - public function getPropertyMetadata($property_name) { - if ($this->typedData instanceof ListInterface) { - return array(new Metadata($this->typedData[$property_name], $property_name, $this->factory, $this->typedDataManager)); - } - elseif ($this->typedData instanceof ComplexDataInterface) { - return array(new Metadata($this->typedData->get($property_name), $property_name, $this->factory, $this->typedDataManager)); - } - else { - throw new \LogicException("There are no known properties."); - } - } -} diff --git a/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php new file mode 100644 index 00000000000..44943e2420a --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/RecursiveContextualValidator.php @@ -0,0 +1,239 @@ +context = $context; + $this->metadataFactory = $metadata_factory; + $this->constraintValidatorFactory = $validator_factory; + $this->typedDataManager = $typed_data_manager; + } + + /** + * {@inheritdoc} + */ + public function atPath($path) { + // @todo This method is not used at the moment, see + // https://www.drupal.org/node/2482527 + return $this; + } + + /** + * {@inheritdoc} + */ + public function validate($data, $constraints = NULL, $groups = NULL, $is_root_call = TRUE) { + if (isset($groups)) { + throw new \LogicException('Passing custom groups is not supported.'); + } + + if (!$data instanceof TypedDataInterface) { + throw new \InvalidArgumentException('The passed value must be a typed data object.'); + } + + // You can pass a single constraint or an array of constraints. + // Make sure to deal with an array in the rest of the code. + if (isset($constraints) && !is_array($constraints)) { + $constraints = array($constraints); + } + + $this->validateNode($data, $constraints, $is_root_call); + return $this; + } + + /** + * Validates a Typed Data node in the validation tree. + * + * If no constraints are passed, the data is validated against the + * constraints specified in its data definition. If the data is complex or a + * list and no constraints are passed, the contained properties or list items + * are validated recursively. + * + * @param \Drupal\Core\TypedData\TypedDataInterface $data + * The data to validated. + * @param \Symfony\Component\Validator\Constraint[]|null $constraints + * (optional) If set, an array of constraints to validate. + * @param bool $is_root_call + * (optional) Whether its the most upper call in the type data tree. + * + * @return $this + */ + protected function validateNode(TypedDataInterface $data, $constraints = NULL, $is_root_call = FALSE) { + $previous_value = $this->context->getValue(); + $previous_object = $this->context->getObject(); + $previous_metadata = $this->context->getMetadata(); + $previous_path = $this->context->getPropertyPath(); + + $metadata = $this->metadataFactory->getMetadataFor($data); + $cache_key = spl_object_hash($data); + $property_path = $is_root_call ? '' : PropertyPath::append($previous_path, $data->getName()); + // Pass the canonical representation of the data as validated value to + // constraint validators, such that they do not have to care about Typed + // Data. + $value = $this->typedDataManager->getCanonicalRepresentation($data); + $this->context->setNode($value, $data, $metadata, $property_path); + + if (isset($constraints) || !$this->context->isGroupValidated($cache_key, Constraint::DEFAULT_GROUP)) { + if (!isset($constraints)) { + $this->context->markGroupAsValidated($cache_key, Constraint::DEFAULT_GROUP); + $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP); + } + $this->validateConstraints($value, $cache_key, $constraints); + } + + // If the data is a list or complex data, validate the contained list items + // or properties. However, do not recurse if the data is empty. + if (($data instanceof ListInterface || $data instanceof ComplexDataInterface) && !$data->isEmpty()) { + foreach ($data as $name => $property) { + $this->validateNode($property); + } + } + + $this->context->setNode($previous_value, $previous_object, $previous_metadata, $previous_path); + + return $this; + } + + /** + * Validates a node's value against all constraints in the given group. + * + * @param mixed $value + * The validated value. + * @param string $cache_key + * The cache key used internally to ensure we don't validate the same + * constraint twice. + * @param \Symfony\Component\Validator\Constraint[] $constraints + * The constraints which should be ensured for the given value. + */ + protected function validateConstraints($value, $cache_key, $constraints) { + foreach ($constraints as $constraint) { + // Prevent duplicate validation of constraints, in the case + // that constraints belong to multiple validated groups + if (isset($cache_key)) { + $constraint_hash = spl_object_hash($constraint); + + if ($this->context->isConstraintValidated($cache_key, $constraint_hash)) { + continue; + } + + $this->context->markConstraintAsValidated($cache_key, $constraint_hash); + } + + $this->context->setConstraint($constraint); + + $validator = $this->constraintValidatorFactory->getInstance($constraint); + $validator->initialize($this->context); + $validator->validate($value, $constraint); + } + } + + /** + * {@inheritdoc} + */ + public function getViolations() { + return $this->context->getViolations(); + } + + /** + * {@inheritdoc} + */ + public function validateProperty($object, $propertyName, $groups = NULL) { + if (isset($groups)) { + throw new \LogicException('Passing custom groups is not supported.'); + } + if (!is_object($object)) { + throw new \InvalidArgumentException('Passing class name is not supported.'); + } + elseif (!$object instanceof TypedDataInterface) { + throw new \InvalidArgumentException('The passed in object has to be typed data.'); + } + elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) { + throw new \InvalidArgumentException('Passed data does not contain properties.'); + } + return $this->validateNode($object->get($propertyName), NULL, TRUE); + } + + /** + * {@inheritdoc} + */ + public function validatePropertyValue($object, $property_name, $value, $groups = NULL) { + if (!is_object($object)) { + throw new \InvalidArgumentException('Passing class name is not supported.'); + } + elseif (!$object instanceof TypedDataInterface) { + throw new \InvalidArgumentException('The passed in object has to be typed data.'); + } + elseif (!$object instanceof ListInterface && !$object instanceof ComplexDataInterface) { + throw new \InvalidArgumentException('Passed data does not contain properties.'); + } + $data = $object->get($property_name); + $metadata = $this->metadataFactory->getMetadataFor($data); + $constraints = $metadata->findConstraints(Constraint::DEFAULT_GROUP); + return $this->validate($value, $constraints, $groups, TRUE); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/RecursiveValidator.php b/core/lib/Drupal/Core/TypedData/Validation/RecursiveValidator.php new file mode 100644 index 00000000000..c828cba51ab --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/RecursiveValidator.php @@ -0,0 +1,121 @@ +contextFactory = $context_factory; + $this->constraintValidatorFactory = $validator_factory; + $this->typedDataManager = $typed_data_manager; + } + + /** + * {@inheritdoc} + */ + public function startContext($root = NULL) { + return new RecursiveContextualValidator($this->contextFactory->createContext($this, $root), $this, $this->constraintValidatorFactory, $this->typedDataManager); + } + + /** + * {@inheritdoc} + */ + public function inContext(ExecutionContextInterface $context) { + return new RecursiveContextualValidator($context, $this, $this->constraintValidatorFactory, $this->typedDataManager); + } + + /** + * {@inheritdoc} + * + * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data + * A typed data object containing the value to validate. + */ + public function getMetadataFor($typed_data) { + if (!$typed_data instanceof TypedDataInterface) { + throw new \InvalidArgumentException('The passed value must be a typed data object.'); + } + return new TypedDataMetadata($typed_data); + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($value) { + return $value instanceof TypedDataInterface; + } + + /** + * {@inheritdoc} + */ + public function validate($value, $constraints = null, $groups = null) { + return $this->startContext($value) + ->validate($value, $constraints, $groups) + ->getViolations(); + } + + /** + * {@inheritdoc} + */ + public function validateProperty($object, $propertyName, $groups = NULL) { + return $this->startContext($object) + ->validateProperty($object, $propertyName, $groups) + ->getViolations(); + } + + /** + * {@inheritdoc} + */ + public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = NULL) { + // Just passing a class name is not supported. + if (!is_object($objectOrClass)) { + throw new \LogicException('Typed data validation does not support passing the class name only.'); + } + return $this->startContext($objectOrClass) + ->validatePropertyValue($objectOrClass, $propertyName, $value, $groups) + ->getViolations(); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php b/core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php new file mode 100644 index 00000000000..7c0509f9141 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/TypedDataAwareValidatorTrait.php @@ -0,0 +1,36 @@ +context; + /** @var \Symfony\Component\Validator\Context\ExecutionContextInterface $context */ + $data = $context->getObject(); + if (!$data instanceof TypedDataInterface) { + throw new \LogicException("There is no Typed Data object available."); + } + return $data; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadata.php new file mode 100644 index 00000000000..5b043bbc989 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/TypedDataMetadata.php @@ -0,0 +1,77 @@ +typedData = $typed_data; + } + + /** + * {@inheritdoc} + */ + public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) { + throw new BadMethodCallException('Not supported.'); + } + + /** + * {@inheritdoc} + */ + public function findConstraints($group) { + return $this->getConstraints(); + } + + /** + * {@inheritdoc} + */ + public function getConstraints() { + return $this->typedData->getConstraints(); + } + + /** + * {@inheritdoc} + */ + public function getTraversalStrategy() { + return TraversalStrategy::NONE; + } + + /** + * {@inheritdoc} + */ + public function getCascadingStrategy() { + // By default, never cascade into validating referenced data structures. + return CascadingStrategy::NONE; + } + +} diff --git a/core/lib/Drupal/Core/Validation/ConstraintManager.php b/core/lib/Drupal/Core/Validation/ConstraintManager.php index c760d1e2371..0ed8d979803 100644 --- a/core/lib/Drupal/Core/Validation/ConstraintManager.php +++ b/core/lib/Drupal/Core/Validation/ConstraintManager.php @@ -79,14 +79,9 @@ class ConstraintManager extends DefaultPluginManager { * @see ConstraintManager::__construct() */ public function registerDefinitions() { - $this->discovery->setDefinition('Null', array( - 'label' => new TranslationWrapper('Null'), - 'class' => '\Symfony\Component\Validator\Constraints\Null', - 'type' => FALSE, - )); - $this->discovery->setDefinition('NotNull', array( - 'label' => new TranslationWrapper('Not null'), - 'class' => '\Symfony\Component\Validator\Constraints\NotNull', + $this->discovery->setDefinition('Callback', array( + 'label' => new TranslationWrapper('Callback'), + 'class' => '\Symfony\Component\Validator\Constraints\Callback', 'type' => FALSE, )); $this->discovery->setDefinition('Blank', array( diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php index efc69065590..a85fbf20606 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php @@ -7,25 +7,53 @@ namespace Drupal\Core\Validation\Plugin\Validation\Constraint; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\OptionsProviderInterface; use Drupal\Core\TypedData\ComplexDataInterface; +use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\ChoiceValidator; /** * Validates the AllowedValues constraint. */ -class AllowedValuesConstraintValidator extends ChoiceValidator { +class AllowedValuesConstraintValidator extends ChoiceValidator implements ContainerInjectionInterface { + + use TypedDataAwareValidatorTrait; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('current_user')); + } + + /** + * Constructs a new AllowedValuesConstraintValidator. + * + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + */ + public function __construct(AccountInterface $current_user) { + $this->currentUser = $current_user; + } /** * {@inheritdoc} */ public function validate($value, Constraint $constraint) { - $typed_data = $this->context->getMetadata()->getTypedData(); - + $typed_data = $this->getTypedData(); if ($typed_data instanceof OptionsProviderInterface) { - $account = \Drupal::currentUser(); - $allowed_values = $typed_data->getSettableValues($account); + $allowed_values = $typed_data->getSettableValues($this->currentUser); $constraint->choices = $allowed_values; // If the data is complex, we have to validate its main property. diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php index add46ebcd7c..eacfcec171d 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php @@ -9,6 +9,7 @@ namespace Drupal\Core\Validation\Plugin\Validation\Constraint; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -18,35 +19,28 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException; */ class ComplexDataConstraintValidator extends ConstraintValidator { + use TypedDataAwareValidatorTrait; + /** * {@inheritdoc} */ - public function validate($value, Constraint $constraint) { - if (!isset($value)) { - return; - } + public function validate($data, Constraint $constraint) { // If un-wrapped data has been passed, fetch the typed data object first. - if (!$value instanceof TypedDataInterface) { - $value = $this->context->getMetadata()->getTypedData(); + if (!$data instanceof TypedDataInterface) { + $data = $this->getTypedData(); } - if (!$value instanceof ComplexDataInterface) { - throw new UnexpectedTypeException($value, 'ComplexData'); + if (!$data instanceof ComplexDataInterface) { + throw new UnexpectedTypeException($data, 'ComplexData'); } - $group = $this->context->getGroup(); - foreach ($constraint->properties as $name => $constraints) { - $property = $value->get($name); - $is_container = $property instanceof ComplexDataInterface || $property instanceof ListInterface; - if (!$is_container) { - $property = $property->getValue(); - } - elseif ($property->isEmpty()) { - // @see \Drupal\Core\TypedData\Validation\PropertyContainerMetadata::accept(); - $property = NULL; - } - $this->context->validateValue($property, $constraints, $name, $group); + $this->context->getValidator() + ->inContext($this->context) + // Specifically pass along FALSE as $root_call, as we validate the data + // as part of the typed data tree. + ->validate($data->get($name), $constraints, NULL, FALSE); } } + } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NotNullConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NotNullConstraint.php new file mode 100644 index 00000000000..9b745ae40b6 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NotNullConstraint.php @@ -0,0 +1,23 @@ +getTypedData(); + if (($typed_data instanceof ListInterface || $typed_data instanceof ComplexDataInterface) && $typed_data->isEmpty()) { + $value = NULL; + } + parent::validate($value, $constraint); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NullConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NullConstraint.php new file mode 100644 index 00000000000..e2a0a5f476f --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/NullConstraint.php @@ -0,0 +1,23 @@ +getTypedData(); + if (($typed_data instanceof ListInterface || $typed_data instanceof ComplexDataInterface) && $typed_data->isEmpty()) { + $value = NULL; + } + parent::validate($value, $constraint); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php index d1bdc0f55ed..d873820b28e 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php @@ -15,6 +15,7 @@ use Drupal\Core\TypedData\Type\FloatInterface; use Drupal\Core\TypedData\Type\IntegerInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\Type\UriInterface; +use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -23,6 +24,8 @@ use Symfony\Component\Validator\ConstraintValidator; */ class PrimitiveTypeConstraintValidator extends ConstraintValidator { + use TypedDataAwareValidatorTrait; + /** * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate(). */ @@ -32,7 +35,7 @@ class PrimitiveTypeConstraintValidator extends ConstraintValidator { return; } - $typed_data = $this->context->getMetadata()->getTypedData(); + $typed_data = $this->getTypedData(); $valid = TRUE; if ($typed_data instanceof BinaryInterface && !is_resource($value)) { $valid = FALSE; diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php index a5dde719345..d6c3910f9bd 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php @@ -19,7 +19,7 @@ class UniqueFieldValueValidator extends ConstraintValidator { * {@inheritdoc} */ public function validate($items, Constraint $constraint) { - if (!isset($items)) { + if (!$item = $items->first()) { return; } $field_name = $items->getFieldDefinition()->getName(); @@ -31,13 +31,13 @@ class UniqueFieldValueValidator extends ConstraintValidator { $value_taken = (bool) \Drupal::entityQuery($entity_type_id) // The id could be NULL, so we cast it to 0 in that case. ->condition($id_key, (int) $items->getEntity()->id(), '<>') - ->condition($field_name, $items->first()->value) + ->condition($field_name, $item->value) ->range(0, 1) ->count() ->execute(); if ($value_taken) { - $this->context->addViolation($constraint->message, array("%value" => $items->value)); + $this->context->addViolation($constraint->message, array("%value" => $item->value)); } } } diff --git a/core/modules/forum/src/Plugin/Validation/Constraint/ForumLeafConstraintValidator.php b/core/modules/forum/src/Plugin/Validation/Constraint/ForumLeafConstraintValidator.php index e4e9be260a8..f2d240067be 100644 --- a/core/modules/forum/src/Plugin/Validation/Constraint/ForumLeafConstraintValidator.php +++ b/core/modules/forum/src/Plugin/Validation/Constraint/ForumLeafConstraintValidator.php @@ -20,10 +20,10 @@ class ForumLeafConstraintValidator extends ConstraintValidator { * {@inheritdoc} */ public function validate($items, Constraint $constraint) { - if (!isset($items)) { - return; - } $item = $items->first(); + if (!isset($item)) { + return NULL; + } // Verify that a term has been selected. if (!$item->entity) { diff --git a/core/modules/quickedit/src/Form/QuickEditFieldForm.php b/core/modules/quickedit/src/Form/QuickEditFieldForm.php index 2db50c46a39..de8dc5b49a9 100644 --- a/core/modules/quickedit/src/Form/QuickEditFieldForm.php +++ b/core/modules/quickedit/src/Form/QuickEditFieldForm.php @@ -17,7 +17,7 @@ use Drupal\Core\Render\Element; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\user\PrivateTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\Validator\ValidatorInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; /** * Builds and process a form for editing a single entity field. @@ -48,7 +48,7 @@ class QuickEditFieldForm extends FormBase { /** * The typed data validator. * - * @var \Symfony\Component\Validator\ValidatorInterface + * @var \Symfony\Component\Validator\Validator\ValidatorInterface */ protected $validator; @@ -61,7 +61,7 @@ class QuickEditFieldForm extends FormBase { * The module handler. * @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage * The node type storage. - * @param \Symfony\Component\Validator\ValidatorInterface $validator + * @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator * The typed data validator service. */ public function __construct(PrivateTempStoreFactory $temp_store_factory, ModuleHandlerInterface $module_handler, EntityStorageInterface $node_type_storage, ValidatorInterface $validator) { @@ -165,7 +165,7 @@ class QuickEditFieldForm extends FormBase { // @todo: Improve this in https://www.drupal.org/node/2395831. $typed_entity = $entity->getTypedData(); $violations = $this->validator - ->validateValue($entity, $typed_entity->getConstraints()); + ->validate($typed_entity, $typed_entity->getConstraints()); foreach ($violations as $violation) { $form_state->setErrorByName($violation->getPropertyPath(), $violation->getMessage()); diff --git a/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php b/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php index 76bc17dd66b..f463c3a68fd 100644 --- a/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php +++ b/core/modules/user/src/Plugin/Validation/Constraint/UserMailRequired.php @@ -58,7 +58,7 @@ class UserMailRequired extends Constraint implements ConstraintValidatorInterfac public function validate($items, Constraint $constraint) { /** @var \Drupal\Core\Field\FieldItemListInterface $items */ /** @var \Drupal\user\UserInterface $account */ - $account = $this->context->getMetadata()->getTypedData()->getEntity(); + $account = $items->getEntity(); $existing_value = NULL; if ($account->id()) { $account_unchanged = \Drupal::entityManager() diff --git a/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php b/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php new file mode 100644 index 00000000000..e4faaa756f0 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/TypedData/RecursiveContextualValidatorTest.php @@ -0,0 +1,349 @@ + $this->root . '/core/lib/Drupal/Core/TypedData', + 'Drupal\\Core\\Validation' => $this->root . '/core/lib/Drupal/Core/Validation', + ]); + $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface') + ->disableOriginalConstructor() + ->getMock(); + $class_resolver = $this->getMockBuilder('Drupal\Core\DependencyInjection\ClassResolverInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->typedDataManager = new TypedDataManager($namespaces, $cache_backend, $module_handler, $class_resolver); + $this->typedDataManager->setValidationConstraintManager( + new ConstraintManager($namespaces, $cache_backend, $module_handler) + ); + // Typed data definitions access the manager in the container. + $container = new ContainerBuilder(); + $container->set('typed_data_manager', $this->typedDataManager); + \Drupal::setContainer($container); + + $translator = new DefaultTranslator(); + $this->contextFactory = new ExecutionContextFactory($translator); + $this->validatorFactory = new ConstraintValidatorFactory(); + $this->recursiveValidator = new RecursiveValidator($this->contextFactory, $this->validatorFactory, $this->typedDataManager); + } + + /** + * Ensures that passing an explicit group is not supported. + * + * @covers ::validate + * + * @expectedException \LogicException + */ + public function testValidateWithGroups() { + $this->recursiveValidator->validate('test', NULL, 'test group'); + } + + /** + * Ensures that passing a non typed data value is not supported. + * + * @covers ::validate + * + * @expectedException \InvalidArgumentException + */ + public function testValidateWithoutTypedData() { + $this->recursiveValidator->validate('test'); + } + + /** + * @covers ::validate + */ + public function testBasicValidateWithoutConstraints() { + $typed_data = $this->typedDataManager->create(DataDefinition::create('string')); + $violations = $this->recursiveValidator->validate($typed_data); + $this->assertCount(0, $violations); + } + + /** + * @covers ::validate + */ + public function testBasicValidateWithConstraint() { + $typed_data = $this->typedDataManager->create( + DataDefinition::create('string') + ->addConstraint('Callback', [ + 'callback' => function ($value, ExecutionContextInterface $context) { + $context->addViolation('test violation: ' . $value); + } + ]) + ); + $typed_data->setValue('foo'); + + $violations = $this->recursiveValidator->validate($typed_data); + $this->assertCount(1, $violations); + // Ensure that the right value is passed into the validator. + $this->assertEquals('test violation: foo', $violations->get(0)->getMessage()); + } + + /** + * @covers ::validate + */ + public function testBasicValidateWithMultipleConstraints() { + $options = [ + 'callback' => function ($value, ExecutionContextInterface $context) { + $context->addViolation('test violation'); + } + ]; + $typed_data = $this->typedDataManager->create( + DataDefinition::create('string') + ->addConstraint('Callback', $options) + ->addConstraint('NotNull') + ); + $violations = $this->recursiveValidator->validate($typed_data); + $this->assertCount(2, $violations); + } + + /** + * @covers ::validate + */ + public function testPropertiesValidateWithMultipleLevels() { + + $typed_data = $this->buildExampleTypedDataWithProperties(); + + $violations = $this->recursiveValidator->validate($typed_data); + $this->assertCount(6, $violations); + + $this->assertEquals('violation: 3', $violations->get(0)->getMessage()); + $this->assertEquals('violation: value1', $violations->get(1)->getMessage()); + $this->assertEquals('violation: value2', $violations->get(2)->getMessage()); + $this->assertEquals('violation: 2', $violations->get(3)->getMessage()); + $this->assertEquals('violation: subvalue1', $violations->get(4)->getMessage()); + $this->assertEquals('violation: subvalue2', $violations->get(5)->getMessage()); + + $this->assertEquals('', $violations->get(0)->getPropertyPath()); + $this->assertEquals('key1', $violations->get(1)->getPropertyPath()); + $this->assertEquals('key2', $violations->get(2)->getPropertyPath()); + $this->assertEquals('key_with_properties', $violations->get(3)->getPropertyPath()); + $this->assertEquals('key_with_properties.subkey1', $violations->get(4)->getPropertyPath()); + $this->assertEquals('key_with_properties.subkey2', $violations->get(5)->getPropertyPath()); + } + + /** + * Setups a typed data object used for test purposes. + * + * @param array $tree + * An array of value, constraints and properties. + * + * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected function setupTypedData(array $tree, $name = '') { + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('violation: ' . (is_array($value) ? count($value) : $value)); + }; + + $tree += ['constraints' => []]; + + if (isset($tree['properties'])) { + $map_data_definition = MapDataDefinition::create(); + $map_data_definition->addConstraint('Callback', ['callback' => $callback]); + foreach ($tree['properties'] as $property_name => $property) { + $sub_typed_data = $this->setupTypedData($property, $property_name); + $map_data_definition->setPropertyDefinition($property_name, $sub_typed_data->getDataDefinition()); + } + $typed_data = $this->typedDataManager->create( + $map_data_definition, + $tree['value'], + $name + ); + } + else { + /** @var \Drupal\Core\TypedData\TypedDataInterface $typed_data */ + $typed_data = $this->typedDataManager->create( + DataDefinition::create('string') + ->addConstraint('Callback', ['callback' => $callback]), + $tree['value'], + $name + ); + } + + return $typed_data; + } + + /** + * @covers ::validateProperty + * + * @expectedException \LogicException + */ + public function testValidatePropertyWithCustomGroup() { + $tree = [ + 'value' => [], + 'properties' => [ + 'key1' => ['value' => 'value1'], + ], + ]; + $typed_data = $this->setupTypedData($tree, 'test_name'); + $this->recursiveValidator->validateProperty($typed_data, 'key1', 'test group'); + } + + /** + * @covers ::validateProperty + * + * @dataProvider providerTestValidatePropertyWithInvalidObjects + * + * @expectedException \InvalidArgumentException + */ + public function testValidatePropertyWithInvalidObjects($object) { + $this->recursiveValidator->validateProperty($object, 'key1', NULL); + } + + /** + * Provides data for testValidatePropertyWithInvalidObjects. + * @return array + */ + public function providerTestValidatePropertyWithInvalidObjects() { + $data = []; + $data[] = [new \stdClass()]; + $data[] = [new TestClass()]; + + $data[] = [$this->getMock('Drupal\Core\TypedData\TypedDataInterface')]; + + return $data; + } + + /** + * @covers ::validateProperty + */ + public function testValidateProperty() { + $typed_data = $this->buildExampleTypedDataWithProperties(); + + $violations = $this->recursiveValidator->validateProperty($typed_data, 'key_with_properties'); + $this->assertCount(3, $violations); + + $this->assertEquals('violation: 2', $violations->get(0)->getMessage()); + $this->assertEquals('violation: subvalue1', $violations->get(1)->getMessage()); + $this->assertEquals('violation: subvalue2', $violations->get(2)->getMessage()); + + $this->assertEquals('', $violations->get(0)->getPropertyPath()); + $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath()); + $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath()); + } + + /** + * @covers ::validatePropertyValue + * + * @dataProvider providerTestValidatePropertyWithInvalidObjects + * + * @expectedException \InvalidArgumentException + */ + public function testValidatePropertyValueWithInvalidObjects($object) { + $this->recursiveValidator->validatePropertyValue($object, 'key1', [], NULL); + } + + /** + * @covers ::validatePropertyValue + */ + public function testValidatePropertyValue() { + $typed_data = $this->buildExampleTypedDataWithProperties(['subkey1' => 'subvalue11', 'subkey2' => 'subvalue22']); + + $violations = $this->recursiveValidator->validatePropertyValue($typed_data, 'key_with_properties', $typed_data->get('key_with_properties')); + $this->assertCount(3, $violations); + + $this->assertEquals('violation: 2', $violations->get(0)->getMessage()); + $this->assertEquals('violation: subvalue11', $violations->get(1)->getMessage()); + $this->assertEquals('violation: subvalue22', $violations->get(2)->getMessage()); + + $this->assertEquals('', $violations->get(0)->getPropertyPath()); + $this->assertEquals('subkey1', $violations->get(1)->getPropertyPath()); + $this->assertEquals('subkey2', $violations->get(2)->getPropertyPath()); + } + + /** + * + * Builds some example type data object. + * + * @return \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected function buildExampleTypedDataWithProperties($subkey_value = NULL) { + $subkey_value = $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2']; + $tree = [ + 'value' => [ + 'key1' => 'value1', + 'key2' => 'value2', + 'key_with_properties' => $subkey_value + ], + ]; + $tree['properties'] = [ + 'key1' => [ + 'value' => 'value1', + ], + 'key2' => [ + 'value' => 'value2', + ], + 'key_with_properties' => [ + 'value' => $subkey_value ?: ['subkey1' => 'subvalue1', 'subkey2' => 'subvalue2'], + ], + ]; + $tree['properties']['key_with_properties']['properties']['subkey1'] = ['value' => $tree['properties']['key_with_properties']['value']['subkey1']]; + $tree['properties']['key_with_properties']['properties']['subkey2'] = ['value' => $tree['properties']['key_with_properties']['value']['subkey2']]; + + return $this->setupTypedData($tree, 'test_name'); + } + +} + +class TestClass { + +} diff --git a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php index 0e906725e63..2bcb3185f65 100644 --- a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php +++ b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php @@ -7,6 +7,12 @@ namespace Drupal\Tests\Core\Validation\Plugin\Validation\Constraint; +use Drupal\Core\TypedData\DataDefinition; +use Drupal\Core\TypedData\Plugin\DataType\BooleanData; +use Drupal\Core\TypedData\Plugin\DataType\FloatData; +use Drupal\Core\TypedData\Plugin\DataType\IntegerData; +use Drupal\Core\TypedData\Plugin\DataType\StringData; +use Drupal\Core\TypedData\Plugin\DataType\Uri; use Drupal\Core\TypedData\PrimitiveInterface; use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraint; use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraintValidator; @@ -24,17 +30,10 @@ class PrimitiveTypeConstraintValidatorTest extends UnitTestCase { * @dataProvider provideTestValidate */ public function testValidate(PrimitiveInterface $typed_data, $value, $valid) { - $metadata = $this->getMockBuilder('Drupal\Core\TypedData\Validation\Metadata') - ->disableOriginalConstructor() - ->getMock(); - $metadata->expects($this->any()) - ->method('getTypedData') - ->willReturn($typed_data); - - $context = $this->getMock('Symfony\Component\Validator\ExecutionContextInterface'); + $context = $this->getMock('\Symfony\Component\Validator\Context\ExecutionContextInterface'); $context->expects($this->any()) - ->method('getMetadata') - ->willReturn($metadata); + ->method('getObject') + ->willReturn($typed_data); if ($valid) { $context->expects($this->never()) @@ -54,31 +53,31 @@ class PrimitiveTypeConstraintValidatorTest extends UnitTestCase { public function provideTestValidate() { $data = []; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\BooleanInterface'), NULL, TRUE]; + $data[] = [new BooleanData(DataDefinition::create('boolean')), NULL, TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\BooleanInterface'), 1, TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\BooleanInterface'), 'test', FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\FloatInterface'), 1.5, TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\FloatInterface'), 'test', FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\IntegerInterface'), 1, TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\IntegerInterface'), 1.5, FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\IntegerInterface'), 'test', FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), 'test', TRUE]; + $data[] = [new BooleanData(DataDefinition::create('boolean')), 1, TRUE]; + $data[] = [new BooleanData(DataDefinition::create('boolean')), 'test', FALSE]; + $data[] = [new FloatData(DataDefinition::create('float')), 1.5, TRUE]; + $data[] = [new FloatData(DataDefinition::create('float')), 'test', FALSE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), 1, TRUE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), 1.5, FALSE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), 'test', FALSE]; + $data[] = [new StringData(DataDefinition::create('string')), 'test', TRUE]; // It is odd that 1 is a valid string. // $data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), 1, FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), [], FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'http://www.drupal.org', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'https://www.drupal.org', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'Invalid', FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'entity:node/1', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'base:', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'base:node', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'internal:', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'public://', FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'public://foo.png', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'private://', FALSE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'private://foo.png', TRUE]; - $data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'drupal.org', FALSE]; + $data[] = [new StringData(DataDefinition::create('string')), [], FALSE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'http://www.drupal.org', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'https://www.drupal.org', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'Invalid', FALSE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'entity:node/1', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'base:', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'base:node', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'internal:', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'public://', FALSE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'public://foo.png', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'private://', FALSE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'private://foo.png', TRUE]; + $data[] = [new Uri(DataDefinition::create('uri')), 'drupal.org', FALSE]; return $data; } diff --git a/core/vendor/composer/ClassLoader.php b/core/vendor/composer/ClassLoader.php index 5e1469e8307..4e05d3b1583 100644 --- a/core/vendor/composer/ClassLoader.php +++ b/core/vendor/composer/ClassLoader.php @@ -351,7 +351,7 @@ class ClassLoader foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { if (0 === strpos($class, $prefix)) { foreach ($this->prefixDirsPsr4[$prefix] as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } @@ -361,7 +361,7 @@ class ClassLoader // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } @@ -380,7 +380,7 @@ class ClassLoader foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } @@ -390,7 +390,7 @@ class ClassLoader // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index 2e9c0e803f6..caf74266144 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -10,7 +10,7 @@ }, "dist": { "type": "zip", - "url": "https://github.com/doctrine/lexer/archive/v1.0.zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb", "reference": "v1.0", "shasum": "" }, @@ -64,7 +64,7 @@ }, "dist": { "type": "zip", - "url": "https://github.com/doctrine/inflector/archive/v1.0.zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/54b8333d2a5682afdc690060c1cf384ba9f47f08", "reference": "v1.0", "shasum": "" }, @@ -625,12 +625,12 @@ "version_normalized": "1.0.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log", + "url": "https://github.com/php-fig/log.git", "reference": "1.0.0" }, "dist": { "type": "zip", - "url": "https://github.com/php-fig/log/archive/1.0.0.zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", "reference": "1.0.0", "shasum": "" }, @@ -2080,80 +2080,6 @@ "description": "Symfony Translation Component", "homepage": "http://symfony.com" }, - { - "name": "symfony/validator", - "version": "v2.6.6", - "version_normalized": "2.6.6.0", - "target-dir": "Symfony/Component/Validator", - "source": { - "type": "git", - "url": "https://github.com/symfony/Validator.git", - "reference": "85d9b42fe71bf88e7a1e5dec2094605dc9fbff28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Validator/zipball/85d9b42fe71bf88e7a1e5dec2094605dc9fbff28", - "reference": "85d9b42fe71bf88e7a1e5dec2094605dc9fbff28", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/translation": "~2.0,>=2.0.5" - }, - "require-dev": { - "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0", - "doctrine/common": "~2.3", - "egulias/email-validator": "~1.2,>=1.2.1", - "symfony/config": "~2.2", - "symfony/expression-language": "~2.4", - "symfony/http-foundation": "~2.1", - "symfony/intl": "~2.3", - "symfony/phpunit-bridge": "~2.7", - "symfony/property-access": "~2.3", - "symfony/yaml": "~2.0,>=2.0.5" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader and metadata cache.", - "egulias/email-validator": "Strict (RFC compliant) email validation", - "symfony/config": "", - "symfony/expression-language": "For using the 2.4 Expression validator", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For using the 2.4 Validator API", - "symfony/yaml": "" - }, - "time": "2015-03-30 15:54:10", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Symfony\\Component\\Validator\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Validator Component", - "homepage": "http://symfony.com" - }, { "name": "symfony/process", "version": "v2.6.6", @@ -3325,5 +3251,79 @@ "testing", "xunit" ] + }, + { + "name": "symfony/validator", + "version": "2.6.x-dev", + "version_normalized": "2.6.9999999.9999999-dev", + "target-dir": "Symfony/Component/Validator", + "source": { + "type": "git", + "url": "https://github.com/symfony/Validator.git", + "reference": "6bb1b474d25cb80617d8da6cb14c955ba914e495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Validator/zipball/6bb1b474d25cb80617d8da6cb14c955ba914e495", + "reference": "6bb1b474d25cb80617d8da6cb14c955ba914e495", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/translation": "~2.0,>=2.0.5" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "doctrine/common": "~2.3", + "egulias/email-validator": "~1.2,>=1.2.1", + "symfony/config": "~2.2", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.1", + "symfony/intl": "~2.3", + "symfony/phpunit-bridge": "~2.7", + "symfony/property-access": "~2.3", + "symfony/yaml": "~2.0,>=2.0.5" + }, + "suggest": { + "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", + "doctrine/cache": "For using the default cached annotation reader and metadata cache.", + "egulias/email-validator": "Strict (RFC compliant) email validation", + "symfony/config": "", + "symfony/expression-language": "For using the 2.4 Expression validator", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For using the 2.4 Validator API", + "symfony/yaml": "" + }, + "time": "2015-05-05 01:29:27", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Validator\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Validator Component", + "homepage": "https://symfony.com" } ] diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Constraint.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Constraint.php index a8ae634e978..ad86f4418dc 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Constraint.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Constraint.php @@ -232,7 +232,7 @@ abstract class Constraint */ public function addImplicitGroupName($group) { - if (in_array(Constraint::DEFAULT_GROUP, $this->groups) && !in_array($group, $this->groups)) { + if (in_array(self::DEFAULT_GROUP, $this->groups) && !in_array($group, $this->groups)) { $this->groups[] = $group; } } diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Constraints/ChoiceValidator.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Constraints/ChoiceValidator.php index cf5774c63d1..51fb4673842 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Constraints/ChoiceValidator.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Constraints/ChoiceValidator.php @@ -36,7 +36,7 @@ class ChoiceValidator extends ConstraintValidator throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Choice'); } - if (!$constraint->choices && !$constraint->callback) { + if (!is_array($constraint->choices) && !$constraint->callback) { throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice'); } diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/DefaultTranslator.php b/core/vendor/symfony/validator/Symfony/Component/Validator/DefaultTranslator.php index 06967de9227..63fa09efd76 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/DefaultTranslator.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/DefaultTranslator.php @@ -88,7 +88,7 @@ class DefaultTranslator implements TranslatorInterface * have the same expressiveness. While Translator supports intervals in * message translations, which are needed for languages other than English, * this translator does not. You should use Translator or a custom - * implementation of {@link TranslatorInterface} if you need this or similar + * implementation of {@link \Symfony\Component\Translation\TranslatorInterface} if you need this or similar * functionality. * * Example usage: diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php index b6ef076bd8e..208dfceeb65 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Validator\Mapping\Factory; use Symfony\Component\Validator\Exception\NoSuchMetadataException; use Symfony\Component\Validator\Mapping\Cache\CacheInterface; use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\Loader\LoaderChain; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; /** @@ -28,7 +30,7 @@ use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; * Whenever a new metadata instance is created, it is passed to the loader, * which can configure the metadata based on configuration loaded from the * filesystem or a database. If you want to use multiple loaders, wrap them in a - * {@link Loader\LoaderChain}. + * {@link LoaderChain}. * * You can also optionally pass a {@link CacheInterface} instance to the * constructor. This cache will be used for persisting the generated metadata diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php index ef25174d0ec..58736e25118 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php @@ -14,7 +14,7 @@ namespace Symfony\Component\Validator\Mapping\Factory; use Symfony\Component\Validator\MetadataFactoryInterface as LegacyMetadataFactoryInterface; /** - * Returns {@link MetadataInterface} instances for values. + * Returns {@link \Symfony\Component\Validator\Mapping\MetadataInterface} instances for values. * * @since 2.5 * @author Bernhard Schussek diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php index af0bf5239a3..d1b8c35b368 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php @@ -58,7 +58,7 @@ class AnnotationLoader implements LoaderInterface } foreach ($reflClass->getProperties() as $property) { - if ($property->getDeclaringClass()->name == $className) { + if ($property->getDeclaringClass()->name === $className) { foreach ($this->reader->getPropertyAnnotations($property) as $constraint) { if ($constraint instanceof Constraint) { $metadata->addPropertyConstraint($property->name, $constraint); @@ -70,7 +70,7 @@ class AnnotationLoader implements LoaderInterface } foreach ($reflClass->getMethods() as $method) { - if ($method->getDeclaringClass()->name == $className) { + if ($method->getDeclaringClass()->name === $className) { foreach ($this->reader->getMethodAnnotations($method) as $constraint) { if ($constraint instanceof Callback) { $constraint->callback = $method->getName(); diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php index e293a6eb381..6075b270baa 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Mapping/Loader/YamlFileLoader.php @@ -85,7 +85,7 @@ class YamlFileLoader extends FileLoader $values = array(); foreach ($nodes as $name => $childNodes) { - if (is_numeric($name) && is_array($childNodes) && count($childNodes) == 1) { + if (is_numeric($name) && is_array($childNodes) && 1 === count($childNodes)) { $options = current($childNodes); if (is_array($options)) { diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/README.md b/core/vendor/symfony/validator/Symfony/Component/Validator/README.md index f6891ffa0c7..8c3dc4890b2 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/README.md +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/README.md @@ -113,7 +113,7 @@ https://github.com/fabpot/Silex/blob/master/src/Silex/Provider/ValidatorServiceP Documentation: -http://symfony.com/doc/2.6/book/validation.html +https://symfony.com/doc/2.6/book/validation.html JSR-303 Specification: diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/ConstraintViolationTest.php index 2ceb0169a10..dffc3840c99 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/ConstraintViolationTest.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/ConstraintViolationTest.php @@ -40,7 +40,7 @@ EOF; '42 cannot be used here', 'this is the message template', array(), - array('some_value' => 42), + array('some_value' => 42), 'some_value', null ); diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php index 579d6aa9086..b515b843584 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Constraints/ChoiceValidatorTest.php @@ -150,6 +150,23 @@ class ChoiceValidatorTest extends AbstractConstraintValidatorTest ->assertRaised(); } + public function testInvalidChoiceEmptyChoices() + { + $constraint = new Choice(array( + // May happen when the choices are provided dynamically, e.g. from + // the DB or the model + 'choices' => array(), + 'message' => 'myMessage', + )); + + $this->validator->validate('baz', $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"baz"') + ->setCode(Choice::NO_SUCH_CHOICE_ERROR) + ->assertRaised(); + } + public function testInvalidChoiceMultiple() { $constraint = new Choice(array( diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php index 94802b66ec6..a8b9af9de85 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php @@ -30,6 +30,7 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase array('', 'bar', 'bar', 'It returns the subPath if basePath is empty'), array('foo', 'bar', 'foo.bar', 'It append the subPath to the basePath'), array('foo', '[bar]', 'foo[bar]', 'It does not include the dot separator if subPath uses the array notation'), + array('0', 'bar', '0.bar', 'Leading zeros are kept.'), ); } } diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Util/PropertyPath.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Util/PropertyPath.php index 4d397a9124d..3ef8a8b1dc5 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Util/PropertyPath.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Util/PropertyPath.php @@ -17,6 +17,7 @@ namespace Symfony\Component\Validator\Util; * For more extensive functionality, use Symfony's PropertyAccess component. * * @since 2.5 + * * @author Bernhard Schussek */ class PropertyPath @@ -42,7 +43,7 @@ class PropertyPath return $basePath.$subPath; } - return $basePath ? $basePath.'.'.$subPath : $subPath; + return '' !== (string) $basePath ? $basePath.'.'.$subPath : $subPath; } return $basePath; diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/core/vendor/symfony/validator/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 191decdce45..d632d05d3f4 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -34,6 +34,7 @@ use Symfony\Component\Validator\Util\PropertyPath; * Recursive implementation of {@link ContextualValidatorInterface}. * * @since 2.5 + * * @author Bernhard Schussek */ class RecursiveContextualValidator implements ContextualValidatorInterface @@ -526,7 +527,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface } elseif ($metadata->isGroupSequenceProvider()) { // The group sequence is dynamically obtained from the validated // object - /** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */ + /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */ $group = $object->getGroupSequence(); $defaultOverridden = true; @@ -590,9 +591,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface $object, $cacheKey.':'.$propertyName, $propertyMetadata, - $propertyPath - ? $propertyPath.'.'.$propertyName - : $propertyName, + PropertyPath::append($propertyPath, $propertyName), $groups, $cascadedGroups, TraversalStrategy::IMPLICIT, diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/ValidatorInterface.php b/core/vendor/symfony/validator/Symfony/Component/Validator/ValidatorInterface.php index 03c8921bb28..0d773329587 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/ValidatorInterface.php +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/ValidatorInterface.php @@ -19,7 +19,7 @@ namespace Symfony\Component\Validator; * @api * * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. - * Use {@link Validator\ValidatorInterface} instead. + * Use {@link \Symfony\Component\Validator\Validator\ValidatorInterface} instead. */ interface ValidatorInterface { diff --git a/core/vendor/symfony/validator/Symfony/Component/Validator/composer.json b/core/vendor/symfony/validator/Symfony/Component/Validator/composer.json index 2d781eac178..ac8c81279f2 100644 --- a/core/vendor/symfony/validator/Symfony/Component/Validator/composer.json +++ b/core/vendor/symfony/validator/Symfony/Component/Validator/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "Symfony Validator Component", "keywords": [], - "homepage": "http://symfony.com", + "homepage": "https://symfony.com", "license": "MIT", "authors": [ { @@ -12,7 +12,7 @@ }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], "require": {