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/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.*",

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",
"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": {

View File

@ -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;
}

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()
*/
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(

View File

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

View File

@ -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);
}
}
}

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\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;

View File

@ -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));
}
}
}

View File

@ -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) {

View File

@ -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());

View File

@ -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()

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;
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;
}

View File

@ -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;
}
}

View File

@ -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"
}
]

View File

@ -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;
}
}

View File

@ -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');
}

View File

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

View File

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

View File

@ -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 <bschussek@gmail.com>

View File

@ -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();

View File

@ -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)) {

View File

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

View File

@ -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
);

View File

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

View File

@ -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.'),
);
}
}

View File

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

View File

@ -34,6 +34,7 @@ use Symfony\Component\Validator\Util\PropertyPath;
* Recursive implementation of {@link ContextualValidatorInterface}.
*
* @since 2.5
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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,

View File

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

View File

@ -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": {