Issue #2343035 by fago, dawehner, larowlan, ianthomas_uk, cilefen, hussainweb, Miroling, jibran, beejeebus, dashaforbes, webmozart, klausi: Upgrade validator integration for Symfony versions 2.5+

8.0.x
Alex Pott 2015-05-05 10:03:32 -07:00
parent d57ee5f9a3
commit 373d925dff
45 changed files with 1855 additions and 452 deletions

View File

@ -14,7 +14,7 @@
"symfony/http-kernel": "2.6.*", "symfony/http-kernel": "2.6.*",
"symfony/routing": "2.6.*", "symfony/routing": "2.6.*",
"symfony/serializer": "2.6.*", "symfony/serializer": "2.6.*",
"symfony/validator": "2.6.*", "symfony/validator": "2.6.*@dev",
"symfony/process": "2.6.*", "symfony/process": "2.6.*",
"symfony/yaml": "2.6.*", "symfony/yaml": "2.6.*",
"twig/twig": "1.18.*", "twig/twig": "1.18.*",

34
core/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "c523fe48318d98a520d2bc45286106e2", "hash": "ba1a97bf2c0bcef4fb771231a4c1fbdb",
"packages": [ "packages": [
{ {
"name": "behat/mink", "name": "behat/mink",
@ -461,7 +461,7 @@
}, },
"dist": { "dist": {
"type": "zip", "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", "reference": "v1.0",
"shasum": "" "shasum": ""
}, },
@ -578,7 +578,7 @@
}, },
"dist": { "dist": {
"type": "zip", "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", "reference": "v1.0",
"shasum": "" "shasum": ""
}, },
@ -1530,12 +1530,12 @@
"version": "1.0.0", "version": "1.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/log", "url": "https://github.com/php-fig/log.git",
"reference": "1.0.0" "reference": "1.0.0"
}, },
"dist": { "dist": {
"type": "zip", "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", "reference": "1.0.0",
"shasum": "" "shasum": ""
}, },
@ -2891,17 +2891,17 @@
}, },
{ {
"name": "symfony/validator", "name": "symfony/validator",
"version": "v2.6.6", "version": "2.6.x-dev",
"target-dir": "Symfony/Component/Validator", "target-dir": "Symfony/Component/Validator",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/Validator.git", "url": "https://github.com/symfony/Validator.git",
"reference": "85d9b42fe71bf88e7a1e5dec2094605dc9fbff28" "reference": "6bb1b474d25cb80617d8da6cb14c955ba914e495"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/Validator/zipball/85d9b42fe71bf88e7a1e5dec2094605dc9fbff28", "url": "https://api.github.com/repos/symfony/Validator/zipball/6bb1b474d25cb80617d8da6cb14c955ba914e495",
"reference": "85d9b42fe71bf88e7a1e5dec2094605dc9fbff28", "reference": "6bb1b474d25cb80617d8da6cb14c955ba914e495",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2948,18 +2948,18 @@
"MIT" "MIT"
], ],
"authors": [ "authors": [
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
},
{ {
"name": "Fabien Potencier", "name": "Fabien Potencier",
"email": "fabien@symfony.com" "email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony Validator Component", "description": "Symfony Validator Component",
"homepage": "http://symfony.com", "homepage": "https://symfony.com",
"time": "2015-03-30 15:54:10" "time": "2015-05-05 01:29:27"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
@ -3221,7 +3221,9 @@
"packages-dev": [], "packages-dev": [],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {
"symfony/validator": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {

View File

@ -13,11 +13,11 @@ use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager; 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\ConstraintManager;
use Drupal\Core\Validation\ConstraintValidatorFactory; use Drupal\Core\Validation\ConstraintValidatorFactory;
use Drupal\Core\Validation\DrupalTranslator; use Drupal\Core\Validation\DrupalTranslator;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
/** /**
@ -28,7 +28,7 @@ class TypedDataManager extends DefaultPluginManager {
/** /**
* The validator used for validating typed data. * The validator used for validating typed data.
* *
* @var \Symfony\Component\Validator\ValidatorInterface * @var \Symfony\Component\Validator\Validator\ValidatorInterface
*/ */
protected $validator; protected $validator;
@ -331,12 +331,11 @@ class TypedDataManager extends DefaultPluginManager {
*/ */
public function getValidator() { public function getValidator() {
if (!isset($this->validator)) { if (!isset($this->validator)) {
$this->validator = Validation::createValidatorBuilder() $this->validator = new RecursiveValidator(
->setMetadataFactory(new MetadataFactory($this)) new ExecutionContextFactory(new DrupalTranslator()),
->setTranslator(new DrupalTranslator()) new ConstraintValidatorFactory($this->classResolver),
->setConstraintValidatorFactory(new ConstraintValidatorFactory($this->classResolver)) $this
->setApiVersion(Validation::API_VERSION_2_4) );
->getValidator();
} }
return $this->validator; return $this->validator;
} }

View File

@ -0,0 +1,254 @@
<?php
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Util\PropertyPath;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
/**
* Defines a constraint violation builder for the Typed Data validator.
*
* We do not use the builder provided by Symfony as it is marked internal.
*
* @codingStandardsIgnoreStart
*/
class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface {
/**
* The list of violations.
*
* @var \Symfony\Component\Validator\ConstraintViolationList
*/
protected $violations;
/**
* The violation message.
*
* @var string
*/
protected $message;
/**
* The message parameters.
*
* @var array
*/
protected $parameters;
/**
* The root path.
*
* @var mixed
*/
protected $root;
/**
* The invalid value caused the violation.
*
* @var mixed
*/
protected $invalidValue;
/**
* The property path.
*
* @var string
*/
protected $propertyPath;
/**
* The translator.
*
* @var \Symfony\Component\Translation\TranslatorInterface
*/
protected $translator;
/**
* The translation domain.
*
* @var string|null
*/
protected $translationDomain;
/**
* The number used
* @var int|null
*/
protected $plural;
/**
* @var Constraint
*/
protected $constraint;
/**
* @var mixed
*/
protected $code;
/**
* @var mixed
*/
protected $cause;
/**
* Constructs a new ConstraintViolationBuilder instance.
*
* @param \Symfony\Component\Validator\ConstraintViolationList $violations
* The violation list.
* @param \Symfony\Component\Validator\Constraint $constraint
* The constraint.
* @param string $message
* The message.
* @param array $parameters
* The message parameters.
* @param mixed $root
* The root.
* @param string $propertyPath
* The property string.
* @param mixed $invalidValue
* The invalid value.
* @param \Symfony\Component\Translation\TranslatorInterface $translator
* The translator.
* @param null $translationDomain
* (optional) The translation domain.
*/
public function __construct(ConstraintViolationList $violations, Constraint $constraint, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null)
{
$this->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
));
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\RecursiveContextualValidatorInterface.
*/
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Validator\Validator\ContextualValidatorInterface as ContextualValidatorInterfaceBase;
/**
* Extends the contextual validator validate method by a new parameter.
*/
interface ContextualValidatorInterface extends ContextualValidatorInterfaceBase {
/**
* Validates a value against a constraint or a list of constraints.
*
* If no constraint is passed, the constraint
* \Symfony\Component\Validator\Constraints\Valid is assumed.
*
* @param mixed $value
* The value to validate
* @param \Symfony\Component\Validator\Constraint|\Symfony\Component\Validator\Constraint[] $constraints
* The constraint(s) to validate against.
* @param array|null $groups
* The validation groups to validate, defaults to "Default".
* @param bool $is_root_call
* (optional) Whether its the most upper call in the typed data tree.
*
* @see \Symfony\Component\Validator\Constraints\Valid
*
* @return $this
*/
public function validate($value, $constraints = NULL, $groups = NULL, $is_root_call = TRUE);
}

View File

@ -0,0 +1,320 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\ExecutionContext.
*/
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Util\PropertyPath;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Defines an execution context class.
*
* We do not use the context provided by Symfony as it is marked internal, so
* this class is pretty much the same, but has some code style changes as well
* as exceptions for methods we don't support.
*/
class ExecutionContext implements ExecutionContextInterface {
/**
* @var \Symfony\Component\Validator\ValidatorInterface
*/
protected $validator;
/**
* The root value of the validated object graph.
*
* @var mixed
*/
protected $root;
/**
* @var \Symfony\Component\Translation\TranslatorInterface
*/
protected $translator;
/**
* @var string
*/
protected $translationDomain;
/**
* The violations generated in the current context.
*
* @var \Symfony\Component\Validator\ConstraintViolationList
*/
protected $violations;
/**
* The currently validated value.
*
* @var mixed
*/
protected $value;
/**
* The currently validated typed data object.
*
* @var \Drupal\Core\TypedData\TypedDataInterface
*/
protected $data;
/**
* The property path leading to the current value.
*
* @var string
*/
protected $propertyPath = '';
/**
* The current validation metadata.
*
* @var \Symfony\Component\Validator\Mapping\MetadataInterface|null
*/
protected $metadata;
/**
* The currently validated group.
*
* @var string|null
*/
protected $group;
/**
* The currently validated constraint.
*
* @var \Symfony\Component\Validator\Constraint|null
*/
protected $constraint;
/**
* Stores which objects have been validated in which group.
*
* @var array
*/
protected $validatedObjects = array();
/**
* Stores which class constraint has been validated for which object.
*
* @var array
*/
protected $validatedConstraints = array();
/**
* Creates a new ExecutionContext.
*
* @param \Symfony\Component\Validator\Validator\ValidatorInterface $validator
* The validator.
* @param mixed $root
* The root.
* @param \Symfony\Component\Translation\TranslatorInterface $translator
* The translator.
* @param string $translationDomain
* (optional) The translation domain.
*
* @internal Called by \Drupal\Core\TypedData\Validation\ExecutionContextFactory.
* Should not be used in user code.
*/
public function __construct(ValidatorInterface $validator, $root, TranslatorInterface $translator, $translationDomain = NULL) {
$this->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.');
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\ExecutionContextFactory.
*/
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Defines an execution factory for the Typed Data validator.
*
* We do not use the factory provided by Symfony as it is marked internal.
*
* @codingStandardsIgnoreStart
*/
class ExecutionContextFactory implements ExecutionContextFactoryInterface {
/**
* @var TranslatorInterface
*/
protected $translator;
/**
* @var string|null
*/
protected $translationDomain;
/**
* Constructs a new ExecutionContextFactory instance.
*
* @param \Symfony\Component\Translation\TranslatorInterface $translator
* The translator instance.
* @param string $translationDomain
* (optional) The translation domain.
*/
public function __construct(TranslatorInterface $translator, $translationDomain = null)
{
$this->translator = $translator;
$this->translationDomain = $translationDomain;
}
/**
* {@inheritdoc}
*/
public function createContext(ValidatorInterface $validator, $root)
{
return new ExecutionContext(
$validator,
$root,
$this->translator,
$this->translationDomain
);
}
}

View File

@ -1,115 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\Metadata.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Symfony\Component\Validator\ValidationVisitorInterface;
use Symfony\Component\Validator\PropertyMetadataInterface;
/**
* Typed data implementation of the validator MetadataInterface.
*/
class Metadata implements PropertyMetadataInterface {
/**
* The name of the property, or empty if this is the root.
*
* @var string
*/
protected $name;
/**
* The typed data object the metadata is about.
*
* @var \Drupal\Core\TypedData\TypedDataInterface
*/
protected $typedData;
/**
* The metadata factory used.
*
* @var \Drupal\Core\TypedData\Validation\MetadataFactory
*/
protected $factory;
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* Constructs the object.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
* The typed data object the metadata is about.
* @param $name
* The name of the property to get metadata for. Leave empty, if
* the data is the root of the typed data tree.
* @param \Drupal\Core\TypedData\Validation\MetadataFactory $factory
* The factory to use for instantiating property metadata.
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* The typed data manager.
*/
public function __construct(TypedDataInterface $typed_data, $name = '', MetadataFactory $factory, TypedDataManager $typed_data_manager) {
$this->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;
}
}

View File

@ -1,62 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\MetadataFactory.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Symfony\Component\Validator\MetadataFactoryInterface;
/**
* Typed data implementation of the validator MetadataFactoryInterface.
*/
class MetadataFactory implements MetadataFactoryInterface {
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* Constructs the object.
*
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* The typed data manager.
*/
public function __construct(TypedDataManager $typed_data_manager) {
$this->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;
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\PropertyContainerMetadata.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListInterface;
use Symfony\Component\Validator\PropertyMetadataContainerInterface;
use Symfony\Component\Validator\ValidationVisitorInterface;
/**
* Typed data implementation of the validator MetadataInterface.
*/
class PropertyContainerMetadata extends Metadata implements PropertyMetadataContainerInterface {
/**
* Overrides Metadata::accept().
*/
public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) {
// To let all constraints properly handle empty structures, pass on NULL
// if the data structure is empty. That way existing NotNull or NotBlank
// constraints work as expected.
if ($typed_data->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.");
}
}
}

View File

@ -0,0 +1,239 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\PropertyContainerPropertyMetadata.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Util\PropertyPath;
/**
* Defines a recursive contextual validator for Typed Data.
*
* For both list and complex data it call recursively out to the properties /
* elements of the list.
*
* This class calls out to some methods on the execution context marked as
* internal. These methods are internal to the validator (which is implemented
* by this class) but should not be called by users.
* See http://symfony.com/doc/current/contributing/code/bc.html for more
* information about @internal.
*
* @see \Drupal\Core\TypedData\Validation\RecursiveValidator::startContext()
* @see \Drupal\Core\TypedData\Validation\RecursiveValidator::inContext()
*/
class RecursiveContextualValidator implements ContextualValidatorInterface {
/**
* The execution context.
*
* @var \Symfony\Component\Validator\Context\ExecutionContextInterface
*/
protected $context;
/**
* The metadata factory.
*
* @var \Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface
*/
protected $metadataFactory;
/**
* The constraint validator factory.
*
* @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
*/
protected $constraintValidatorFactory;
/**
* Creates a validator for the given context.
*
* @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
* The factory for creating new contexts.
* @param \Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface $metadata_factory
* The metadata factory.
* @param \Symfony\Component\Validator\ConstraintValidatorFactoryInterface $validator_factory
* The constraint validator factory.
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* The typed data manager.
*/
public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadata_factory, ConstraintValidatorFactoryInterface $validator_factory, TypedDataManager $typed_data_manager) {
$this->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);
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\RecursiveValidator.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManager;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Defines a recursive validator for Typed Data.
*
* The difference to \Symfony\Component\Validator\Validator\RecursiveValidator
* is that we just allow to validate typed data objects.
*/
class RecursiveValidator implements ValidatorInterface {
/**
* @var \Symfony\Component\Validator\Context\ExecutionContextFactoryInterface
*/
protected $contextFactory;
/**
* @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
*/
protected $constraintValidatorFactory;
/**
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* Creates a new validator.
*
* @param \Symfony\Component\Validator\Context\ExecutionContextFactoryInterface $context_factory
* The factory for creating new contexts.
* @param \Symfony\Component\Validator\ConstraintValidatorFactoryInterface $validator_factory
* The constraint validator factory.
* @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
* The typed data manager.
*/
public function __construct(ExecutionContextFactoryInterface $context_factory, ConstraintValidatorFactoryInterface $validator_factory, TypedDataManager $typed_data_manager) {
$this->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();
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Defines a trait to access the typed data object of a validated value.
*
* The trait assumes to be used on classes extending
* \Symfony\Component\Validator\ConstraintValidator.
*/
trait TypedDataAwareValidatorTrait {
/**
* Gets the typed data object for the validated value.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The typed data object.
*/
public function getTypedData() {
$context = $this->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;
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Validation\MetadataBase.
*/
namespace Drupal\Core\TypedData\Validation;
use Drupal\Core\TypedData\TypedDataInterface;
use Symfony\Component\Validator\Exception\BadMethodCallException;
use Symfony\Component\Validator\Mapping\CascadingStrategy;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Mapping\TraversalStrategy;
use Symfony\Component\Validator\ValidationVisitorInterface;
/**
* Validator metadata for typed data objects.
*
* @see \Drupal\Core\TypedData\Validation\RecursiveValidator::getMetadataFor()
*/
class TypedDataMetadata implements MetadataInterface {
/**
* The typed data object the metadata is about.
*
* @var \Drupal\Core\TypedData\TypedDataInterface
*/
protected $typedData;
/**
* Constructs the object.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $typed_data
* The typed data object the metadata is about.
*/
public function __construct(TypedDataInterface $typed_data) {
$this->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;
}
}

View File

@ -79,14 +79,9 @@ class ConstraintManager extends DefaultPluginManager {
* @see ConstraintManager::__construct() * @see ConstraintManager::__construct()
*/ */
public function registerDefinitions() { public function registerDefinitions() {
$this->discovery->setDefinition('Null', array( $this->discovery->setDefinition('Callback', array(
'label' => new TranslationWrapper('Null'), 'label' => new TranslationWrapper('Callback'),
'class' => '\Symfony\Component\Validator\Constraints\Null', 'class' => '\Symfony\Component\Validator\Constraints\Callback',
'type' => FALSE,
));
$this->discovery->setDefinition('NotNull', array(
'label' => new TranslationWrapper('Not null'),
'class' => '\Symfony\Component\Validator\Constraints\NotNull',
'type' => FALSE, 'type' => FALSE,
)); ));
$this->discovery->setDefinition('Blank', array( $this->discovery->setDefinition('Blank', array(

View File

@ -7,25 +7,53 @@
namespace Drupal\Core\Validation\Plugin\Validation\Constraint; 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\OptionsProviderInterface;
use Drupal\Core\TypedData\ComplexDataInterface; 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\Constraint;
use Symfony\Component\Validator\Constraints\ChoiceValidator; use Symfony\Component\Validator\Constraints\ChoiceValidator;
/** /**
* Validates the AllowedValues constraint. * 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} * {@inheritdoc}
*/ */
public function validate($value, Constraint $constraint) { public function validate($value, Constraint $constraint) {
$typed_data = $this->context->getMetadata()->getTypedData(); $typed_data = $this->getTypedData();
if ($typed_data instanceof OptionsProviderInterface) { if ($typed_data instanceof OptionsProviderInterface) {
$account = \Drupal::currentUser(); $allowed_values = $typed_data->getSettableValues($this->currentUser);
$allowed_values = $typed_data->getSettableValues($account);
$constraint->choices = $allowed_values; $constraint->choices = $allowed_values;
// If the data is complex, we have to validate its main property. // If the data is complex, we have to validate its main property.

View File

@ -9,6 +9,7 @@ namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@ -18,35 +19,28 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
*/ */
class ComplexDataConstraintValidator extends ConstraintValidator { class ComplexDataConstraintValidator extends ConstraintValidator {
use TypedDataAwareValidatorTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function validate($value, Constraint $constraint) { public function validate($data, Constraint $constraint) {
if (!isset($value)) {
return;
}
// If un-wrapped data has been passed, fetch the typed data object first. // If un-wrapped data has been passed, fetch the typed data object first.
if (!$value instanceof TypedDataInterface) { if (!$data instanceof TypedDataInterface) {
$value = $this->context->getMetadata()->getTypedData(); $data = $this->getTypedData();
} }
if (!$value instanceof ComplexDataInterface) { if (!$data instanceof ComplexDataInterface) {
throw new UnexpectedTypeException($value, 'ComplexData'); throw new UnexpectedTypeException($data, 'ComplexData');
} }
$group = $this->context->getGroup();
foreach ($constraint->properties as $name => $constraints) { foreach ($constraint->properties as $name => $constraints) {
$property = $value->get($name); $this->context->getValidator()
$is_container = $property instanceof ComplexDataInterface || $property instanceof ListInterface; ->inContext($this->context)
if (!$is_container) { // Specifically pass along FALSE as $root_call, as we validate the data
$property = $property->getValue(); // as part of the typed data tree.
} ->validate($data->get($name), $constraints, NULL, FALSE);
elseif ($property->isEmpty()) {
// @see \Drupal\Core\TypedData\Validation\PropertyContainerMetadata::accept();
$property = NULL;
}
$this->context->validateValue($property, $constraints, $name, $group);
} }
} }
} }

View File

@ -0,0 +1,23 @@
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint.
*/
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraints\NotNull;
/**
* NotNull constraint.
*
* Overrides the symfony constraint to handle empty Typed Data structures.
*
* @Plugin(
* id = "NotNull",
* label = @Translation("NotNull", context = "Validation"),
* type = false
* )
*/
class NotNullConstraint extends NotNull { }

View File

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraintValidator.
*/
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NotNullValidator;
/**
* NotNull constraint validator.
*
* Overrides the symfony validator to handle empty Typed Data structures.
*/
class NotNullConstraintValidator extends NotNullValidator {
use TypedDataAwareValidatorTrait;
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
$typed_data = $this->getTypedData();
if (($typed_data instanceof ListInterface || $typed_data instanceof ComplexDataInterface) && $typed_data->isEmpty()) {
$value = NULL;
}
parent::validate($value, $constraint);
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\NullConstraint.
*/
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraints\Null;
/**
* Null constraint.
*
* Overrides the symfony constraint to handle empty Typed Data structures.
*
* @Plugin(
* id = "Null",
* label = @Translation("Null", context = "Validation"),
* type = false
* )
*/
class NullConstraint extends Null { }

View File

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Validation\Plugin\Validation\Constraint\NullConstraintValidator.
*/
namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NullValidator;
/**
* Null constraint validator.
*
* Overrides the symfony validator to handle empty Typed Data structures.
*/
class NullConstraintValidator extends NullValidator {
use TypedDataAwareValidatorTrait;
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
$typed_data = $this->getTypedData();
if (($typed_data instanceof ListInterface || $typed_data instanceof ComplexDataInterface) && $typed_data->isEmpty()) {
$value = NULL;
}
parent::validate($value, $constraint);
}
}

View File

@ -15,6 +15,7 @@ use Drupal\Core\TypedData\Type\FloatInterface;
use Drupal\Core\TypedData\Type\IntegerInterface; use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\Type\StringInterface;
use Drupal\Core\TypedData\Type\UriInterface; use Drupal\Core\TypedData\Type\UriInterface;
use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidator;
@ -23,6 +24,8 @@ use Symfony\Component\Validator\ConstraintValidator;
*/ */
class PrimitiveTypeConstraintValidator extends ConstraintValidator { class PrimitiveTypeConstraintValidator extends ConstraintValidator {
use TypedDataAwareValidatorTrait;
/** /**
* Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate(). * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
*/ */
@ -32,7 +35,7 @@ class PrimitiveTypeConstraintValidator extends ConstraintValidator {
return; return;
} }
$typed_data = $this->context->getMetadata()->getTypedData(); $typed_data = $this->getTypedData();
$valid = TRUE; $valid = TRUE;
if ($typed_data instanceof BinaryInterface && !is_resource($value)) { if ($typed_data instanceof BinaryInterface && !is_resource($value)) {
$valid = FALSE; $valid = FALSE;

View File

@ -19,7 +19,7 @@ class UniqueFieldValueValidator extends ConstraintValidator {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function validate($items, Constraint $constraint) { public function validate($items, Constraint $constraint) {
if (!isset($items)) { if (!$item = $items->first()) {
return; return;
} }
$field_name = $items->getFieldDefinition()->getName(); $field_name = $items->getFieldDefinition()->getName();
@ -31,13 +31,13 @@ class UniqueFieldValueValidator extends ConstraintValidator {
$value_taken = (bool) \Drupal::entityQuery($entity_type_id) $value_taken = (bool) \Drupal::entityQuery($entity_type_id)
// The id could be NULL, so we cast it to 0 in that case. // The id could be NULL, so we cast it to 0 in that case.
->condition($id_key, (int) $items->getEntity()->id(), '<>') ->condition($id_key, (int) $items->getEntity()->id(), '<>')
->condition($field_name, $items->first()->value) ->condition($field_name, $item->value)
->range(0, 1) ->range(0, 1)
->count() ->count()
->execute(); ->execute();
if ($value_taken) { if ($value_taken) {
$this->context->addViolation($constraint->message, array("%value" => $items->value)); $this->context->addViolation($constraint->message, array("%value" => $item->value));
} }
} }
} }

View File

@ -20,10 +20,10 @@ class ForumLeafConstraintValidator extends ConstraintValidator {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function validate($items, Constraint $constraint) { public function validate($items, Constraint $constraint) {
if (!isset($items)) {
return;
}
$item = $items->first(); $item = $items->first();
if (!isset($item)) {
return NULL;
}
// Verify that a term has been selected. // Verify that a term has been selected.
if (!$item->entity) { if (!$item->entity) {

View File

@ -17,7 +17,7 @@ use Drupal\Core\Render\Element;
use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\user\PrivateTempStoreFactory; use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface; 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. * Builds and process a form for editing a single entity field.
@ -48,7 +48,7 @@ class QuickEditFieldForm extends FormBase {
/** /**
* The typed data validator. * The typed data validator.
* *
* @var \Symfony\Component\Validator\ValidatorInterface * @var \Symfony\Component\Validator\Validator\ValidatorInterface
*/ */
protected $validator; protected $validator;
@ -61,7 +61,7 @@ class QuickEditFieldForm extends FormBase {
* The module handler. * The module handler.
* @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage * @param \Drupal\Core\Entity\EntityStorageInterface $node_type_storage
* The 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. * The typed data validator service.
*/ */
public function __construct(PrivateTempStoreFactory $temp_store_factory, ModuleHandlerInterface $module_handler, EntityStorageInterface $node_type_storage, ValidatorInterface $validator) { 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. // @todo: Improve this in https://www.drupal.org/node/2395831.
$typed_entity = $entity->getTypedData(); $typed_entity = $entity->getTypedData();
$violations = $this->validator $violations = $this->validator
->validateValue($entity, $typed_entity->getConstraints()); ->validate($typed_entity, $typed_entity->getConstraints());
foreach ($violations as $violation) { foreach ($violations as $violation) {
$form_state->setErrorByName($violation->getPropertyPath(), $violation->getMessage()); $form_state->setErrorByName($violation->getPropertyPath(), $violation->getMessage());

View File

@ -58,7 +58,7 @@ class UserMailRequired extends Constraint implements ConstraintValidatorInterfac
public function validate($items, Constraint $constraint) { public function validate($items, Constraint $constraint) {
/** @var \Drupal\Core\Field\FieldItemListInterface $items */ /** @var \Drupal\Core\Field\FieldItemListInterface $items */
/** @var \Drupal\user\UserInterface $account */ /** @var \Drupal\user\UserInterface $account */
$account = $this->context->getMetadata()->getTypedData()->getEntity(); $account = $items->getEntity();
$existing_value = NULL; $existing_value = NULL;
if ($account->id()) { if ($account->id()) {
$account_unchanged = \Drupal::entityManager() $account_unchanged = \Drupal::entityManager()

View File

@ -0,0 +1,349 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\TypedData\RecursiveContextualValidatorTest.
*/
namespace Drupal\Tests\Core\TypedData;
use Drupal\Core\Cache\NullBackend;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\MapDataDefinition;
use Drupal\Core\TypedData\TypedDataManager;
use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
use Drupal\Core\TypedData\Validation\RecursiveValidator;
use Drupal\Core\Validation\ConstraintManager;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\DefaultTranslator;
/**
* @coversDefaultClass \Drupal\Core\TypedData\Validation\RecursiveContextualValidator
* @group typedData
*/
class RecursiveContextualValidatorTest extends UnitTestCase {
/**
* The type data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManager
*/
protected $typedDataManager;
/**
* The recursive validator.
*
* @var \Drupal\Core\TypedData\Validation\RecursiveValidator
*/
protected $recursiveValidator;
/**
* The validator factory.
*
* @var \Symfony\Component\Validator\ConstraintValidatorFactoryInterface
*/
protected $validatorFactory;
/**
* The execution context factory.
*
* @var \Drupal\Core\TypedData\Validation\ExecutionContextFactory
*/
protected $contextFactory;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$cache_backend = new NullBackend('cache');
$namespaces = new \ArrayObject([
'Drupal\\Core\\TypedData' => $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 {
}

View File

@ -7,6 +7,12 @@
namespace Drupal\Tests\Core\Validation\Plugin\Validation\Constraint; 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\TypedData\PrimitiveInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraint; use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraint;
use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraintValidator; use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraintValidator;
@ -24,17 +30,10 @@ class PrimitiveTypeConstraintValidatorTest extends UnitTestCase {
* @dataProvider provideTestValidate * @dataProvider provideTestValidate
*/ */
public function testValidate(PrimitiveInterface $typed_data, $value, $valid) { public function testValidate(PrimitiveInterface $typed_data, $value, $valid) {
$metadata = $this->getMockBuilder('Drupal\Core\TypedData\Validation\Metadata') $context = $this->getMock('\Symfony\Component\Validator\Context\ExecutionContextInterface');
->disableOriginalConstructor()
->getMock();
$metadata->expects($this->any())
->method('getTypedData')
->willReturn($typed_data);
$context = $this->getMock('Symfony\Component\Validator\ExecutionContextInterface');
$context->expects($this->any()) $context->expects($this->any())
->method('getMetadata') ->method('getObject')
->willReturn($metadata); ->willReturn($typed_data);
if ($valid) { if ($valid) {
$context->expects($this->never()) $context->expects($this->never())
@ -54,31 +53,31 @@ class PrimitiveTypeConstraintValidatorTest extends UnitTestCase {
public function provideTestValidate() { public function provideTestValidate() {
$data = []; $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[] = [new BooleanData(DataDefinition::create('boolean')), 1, TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\BooleanInterface'), 'test', FALSE]; $data[] = [new BooleanData(DataDefinition::create('boolean')), 'test', FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\FloatInterface'), 1.5, TRUE]; $data[] = [new FloatData(DataDefinition::create('float')), 1.5, TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\FloatInterface'), 'test', FALSE]; $data[] = [new FloatData(DataDefinition::create('float')), 'test', FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\IntegerInterface'), 1, TRUE]; $data[] = [new IntegerData(DataDefinition::create('integer')), 1, TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\IntegerInterface'), 1.5, FALSE]; $data[] = [new IntegerData(DataDefinition::create('integer')), 1.5, FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\IntegerInterface'), 'test', FALSE]; $data[] = [new IntegerData(DataDefinition::create('integer')), 'test', FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), 'test', TRUE]; $data[] = [new StringData(DataDefinition::create('string')), 'test', TRUE];
// It is odd that 1 is a valid string. // 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'), 1, FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), [], FALSE]; $data[] = [new StringData(DataDefinition::create('string')), [], FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'http://www.drupal.org', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'http://www.drupal.org', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'https://www.drupal.org', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'https://www.drupal.org', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'Invalid', FALSE]; $data[] = [new Uri(DataDefinition::create('uri')), 'Invalid', FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'entity:node/1', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'entity:node/1', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'base:', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'base:', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'base:node', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'base:node', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'internal:', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'internal:', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'public://', FALSE]; $data[] = [new Uri(DataDefinition::create('uri')), 'public://', FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'public://foo.png', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'public://foo.png', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'private://', FALSE]; $data[] = [new Uri(DataDefinition::create('uri')), 'private://', FALSE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'private://foo.png', TRUE]; $data[] = [new Uri(DataDefinition::create('uri')), 'private://foo.png', TRUE];
$data[] = [$this->getMock('Drupal\Core\TypedData\Type\UriInterface'), 'drupal.org', FALSE]; $data[] = [new Uri(DataDefinition::create('uri')), 'drupal.org', FALSE];
return $data; return $data;
} }

View File

@ -351,7 +351,7 @@ class ClassLoader
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) { if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) { 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; return $file;
} }
} }
@ -361,7 +361,7 @@ class ClassLoader
// PSR-4 fallback dirs // PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) { foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file; return $file;
} }
} }
@ -380,7 +380,7 @@ class ClassLoader
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) { if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file; return $file;
} }
} }
@ -390,7 +390,7 @@ class ClassLoader
// PSR-0 fallback dirs // PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) { foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file; return $file;
} }
} }

View File

@ -10,7 +10,7 @@
}, },
"dist": { "dist": {
"type": "zip", "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", "reference": "v1.0",
"shasum": "" "shasum": ""
}, },
@ -64,7 +64,7 @@
}, },
"dist": { "dist": {
"type": "zip", "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", "reference": "v1.0",
"shasum": "" "shasum": ""
}, },
@ -625,12 +625,12 @@
"version_normalized": "1.0.0.0", "version_normalized": "1.0.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/log", "url": "https://github.com/php-fig/log.git",
"reference": "1.0.0" "reference": "1.0.0"
}, },
"dist": { "dist": {
"type": "zip", "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", "reference": "1.0.0",
"shasum": "" "shasum": ""
}, },
@ -2080,80 +2080,6 @@
"description": "Symfony Translation Component", "description": "Symfony Translation Component",
"homepage": "http://symfony.com" "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", "name": "symfony/process",
"version": "v2.6.6", "version": "v2.6.6",
@ -3325,5 +3251,79 @@
"testing", "testing",
"xunit" "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"
} }
] ]

View File

@ -232,7 +232,7 @@ abstract class Constraint
*/ */
public function addImplicitGroupName($group) 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; $this->groups[] = $group;
} }
} }

View File

@ -36,7 +36,7 @@ class ChoiceValidator extends ConstraintValidator
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Choice'); 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'); throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice');
} }

View File

@ -88,7 +88,7 @@ class DefaultTranslator implements TranslatorInterface
* have the same expressiveness. While Translator supports intervals in * have the same expressiveness. While Translator supports intervals in
* message translations, which are needed for languages other than English, * message translations, which are needed for languages other than English,
* this translator does not. You should use Translator or a custom * 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. * functionality.
* *
* Example usage: * Example usage:

View File

@ -14,6 +14,8 @@ namespace Symfony\Component\Validator\Mapping\Factory;
use Symfony\Component\Validator\Exception\NoSuchMetadataException; use Symfony\Component\Validator\Exception\NoSuchMetadataException;
use Symfony\Component\Validator\Mapping\Cache\CacheInterface; use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata; 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; 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, * Whenever a new metadata instance is created, it is passed to the loader,
* which can configure the metadata based on configuration loaded from the * 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 * 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 * You can also optionally pass a {@link CacheInterface} instance to the
* constructor. This cache will be used for persisting the generated metadata * constructor. This cache will be used for persisting the generated metadata

View File

@ -14,7 +14,7 @@ namespace Symfony\Component\Validator\Mapping\Factory;
use Symfony\Component\Validator\MetadataFactoryInterface as LegacyMetadataFactoryInterface; 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 * @since 2.5
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>

View File

@ -58,7 +58,7 @@ class AnnotationLoader implements LoaderInterface
} }
foreach ($reflClass->getProperties() as $property) { foreach ($reflClass->getProperties() as $property) {
if ($property->getDeclaringClass()->name == $className) { if ($property->getDeclaringClass()->name === $className) {
foreach ($this->reader->getPropertyAnnotations($property) as $constraint) { foreach ($this->reader->getPropertyAnnotations($property) as $constraint) {
if ($constraint instanceof Constraint) { if ($constraint instanceof Constraint) {
$metadata->addPropertyConstraint($property->name, $constraint); $metadata->addPropertyConstraint($property->name, $constraint);
@ -70,7 +70,7 @@ class AnnotationLoader implements LoaderInterface
} }
foreach ($reflClass->getMethods() as $method) { foreach ($reflClass->getMethods() as $method) {
if ($method->getDeclaringClass()->name == $className) { if ($method->getDeclaringClass()->name === $className) {
foreach ($this->reader->getMethodAnnotations($method) as $constraint) { foreach ($this->reader->getMethodAnnotations($method) as $constraint) {
if ($constraint instanceof Callback) { if ($constraint instanceof Callback) {
$constraint->callback = $method->getName(); $constraint->callback = $method->getName();

View File

@ -85,7 +85,7 @@ class YamlFileLoader extends FileLoader
$values = array(); $values = array();
foreach ($nodes as $name => $childNodes) { 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); $options = current($childNodes);
if (is_array($options)) { if (is_array($options)) {

View File

@ -113,7 +113,7 @@ https://github.com/fabpot/Silex/blob/master/src/Silex/Provider/ValidatorServiceP
Documentation: Documentation:
http://symfony.com/doc/2.6/book/validation.html https://symfony.com/doc/2.6/book/validation.html
JSR-303 Specification: JSR-303 Specification:

View File

@ -40,7 +40,7 @@ EOF;
'42 cannot be used here', '42 cannot be used here',
'this is the message template', 'this is the message template',
array(), array(),
array('some_value' => 42), array('some_value' => 42),
'some_value', 'some_value',
null null
); );

View File

@ -150,6 +150,23 @@ class ChoiceValidatorTest extends AbstractConstraintValidatorTest
->assertRaised(); ->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() public function testInvalidChoiceMultiple()
{ {
$constraint = new Choice(array( $constraint = new Choice(array(

View File

@ -30,6 +30,7 @@ class PropertyPathTest extends \PHPUnit_Framework_TestCase
array('', 'bar', 'bar', 'It returns the subPath if basePath is empty'), 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 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('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.'),
); );
} }
} }

View File

@ -17,6 +17,7 @@ namespace Symfony\Component\Validator\Util;
* For more extensive functionality, use Symfony's PropertyAccess component. * For more extensive functionality, use Symfony's PropertyAccess component.
* *
* @since 2.5 * @since 2.5
*
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class PropertyPath class PropertyPath
@ -42,7 +43,7 @@ class PropertyPath
return $basePath.$subPath; return $basePath.$subPath;
} }
return $basePath ? $basePath.'.'.$subPath : $subPath; return '' !== (string) $basePath ? $basePath.'.'.$subPath : $subPath;
} }
return $basePath; return $basePath;

View File

@ -34,6 +34,7 @@ use Symfony\Component\Validator\Util\PropertyPath;
* Recursive implementation of {@link ContextualValidatorInterface}. * Recursive implementation of {@link ContextualValidatorInterface}.
* *
* @since 2.5 * @since 2.5
*
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class RecursiveContextualValidator implements ContextualValidatorInterface class RecursiveContextualValidator implements ContextualValidatorInterface
@ -526,7 +527,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
} elseif ($metadata->isGroupSequenceProvider()) { } elseif ($metadata->isGroupSequenceProvider()) {
// The group sequence is dynamically obtained from the validated // The group sequence is dynamically obtained from the validated
// object // object
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */ /* @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */
$group = $object->getGroupSequence(); $group = $object->getGroupSequence();
$defaultOverridden = true; $defaultOverridden = true;
@ -590,9 +591,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
$object, $object,
$cacheKey.':'.$propertyName, $cacheKey.':'.$propertyName,
$propertyMetadata, $propertyMetadata,
$propertyPath PropertyPath::append($propertyPath, $propertyName),
? $propertyPath.'.'.$propertyName
: $propertyName,
$groups, $groups,
$cascadedGroups, $cascadedGroups,
TraversalStrategy::IMPLICIT, TraversalStrategy::IMPLICIT,

View File

@ -19,7 +19,7 @@ namespace Symfony\Component\Validator;
* @api * @api
* *
* @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. * @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 interface ValidatorInterface
{ {

View File

@ -3,7 +3,7 @@
"type": "library", "type": "library",
"description": "Symfony Validator Component", "description": "Symfony Validator Component",
"keywords": [], "keywords": [],
"homepage": "http://symfony.com", "homepage": "https://symfony.com",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {
@ -12,7 +12,7 @@
}, },
{ {
"name": "Symfony Community", "name": "Symfony Community",
"homepage": "http://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"require": { "require": {