diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index 3216d21..c81f7c3 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -138,7 +138,14 @@ public function getConstraints() { */ public function validate() { // @todo: Add the typed data manager as proper dependency. - return \Drupal::typedDataManager()->getValidator()->validate($this); + if ($this instanceof PrimitiveInterface) { + // Primitive data is always validated unwrapped. For that to work we have + // to manually pass on the constraints. + return \Drupal::typedDataManager()->getValidator()->validate($this->getValue(), $this->getConstraints()); + } + else { + return \Drupal::typedDataManager()->getValidator()->validate($this); + } } /** diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php old mode 100644 new mode 100755 index 7fe362e..39142e1 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -335,7 +335,7 @@ public function getValidator() { ->setMetadataFactory(new MetadataFactory()) ->setTranslator(new DrupalTranslator()) ->setConstraintValidatorFactory(new ConstraintValidatorFactory($this->classResolver)) - ->setApiVersion(Validation::API_VERSION_2_4) + ->setApiVersion(Validation::API_VERSION_2_5) ->getValidator(); } return $this->validator; diff --git a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php old mode 100644 new mode 100755 index 73bfc8b..23f1945 --- a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php +++ b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php @@ -7,98 +7,47 @@ namespace Drupal\Core\TypedData\Validation; -use Drupal\Core\TypedData\TypedDataInterface; -use Symfony\Component\Validator\ValidationVisitorInterface; -use Symfony\Component\Validator\PropertyMetadataInterface; +use Symfony\Component\Validator\Exception\BadMethodCallException; +use Symfony\Component\Validator\Mapping\CascadingStrategy; /** - * Typed data implementation of the validator MetadataInterface. + * Validator metadata for typed data objects containing no properties. */ -class Metadata implements PropertyMetadataInterface { +class Metadata extends MetadataBase { /** - * The name of the property, or empty if this is the root. - * - * @var string + * {@inheritdoc} */ - 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; - - /** - * 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. - */ - public function __construct(TypedDataInterface $typed_data, $name = '', MetadataFactory $factory) { - $this->typedData = $typed_data; - $this->name = $name; - $this->factory = $factory; - } - - /** - * 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, $typed_data->getValue(), $group, $propertyPath); + public function getConstrainedProperties() { + return []; } /** - * Implements MetadataInterface::findConstraints(). + * {@inheritdoc} */ - public function findConstraints($group) { - return $this->typedData->getConstraints(); + public function hasPropertyMetadata($property_name) { + return []; } /** - * Returns the name of the property. - * - * @return string The property name. + * {@inheritdoc} */ - public function getPropertyName() { - return $this->name; + public function getPropertyMetadata($property_name) { + throw new BadMethodCallException("The data does not contain any properties."); } /** - * 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. + * {@inheritdoc} */ - public function getPropertyValue($container) { - return $this->typedData->getValue(); + public function getCascadingStrategy() { + return CascadingStrategy::NONE; } /** - * Returns the typed data object. - * - * @return \Drupal\Core\TypedData\TypedDataInterface - * The typed data object. + * {@inheritdoc} */ public function getTypedData() { - return $this->typedData; + return $this->typedData->getParent(); } + } diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php new file mode 100755 index 0000000..6584405 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataBase.php @@ -0,0 +1,165 @@ +typedData = $typed_data; + $this->factory = $factory; + } + + /** + * Returns the typed data object. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The typed data object. + */ + public function getTypedData() { + return $this->typedData; + } + + /** + * {@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() { + $constraints = $this->typedData->getConstraints(); + // Merge in property constraints from any ComplexDataConstraints applied to + // the parent. + return array_merge($constraints, $this->constraints); + } + + /** + * {@inheritdoc} + */ + public function getTraversalStrategy() { + return TraversalStrategy::NONE; + } + + /** + * {@inheritdoc} + */ + public function getClassName() { + return get_class($this->typedData); + } + + /** + * {@inheritdoc} + */ + public function getGroupSequence() { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function hasGroupSequence() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isGroupSequenceProvider() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getPropertyName() { + return $this->typedData->getName(); + } + + /** + * {@inheritdoc} + */ + public function getPropertyValue($container) { + $property = $container->get($this->getPropertyName()); + if ($property instanceof ListInterface || $property instanceof ComplexDataInterface) { + // 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. + return !$property->isEmpty() ? $property : NULL; + } + else { + return $property->getValue(); + } + } + + /** + * Adds a property constraint. + * + * @param \Symfony\Component\Validator\Constraint $constraint + * The constraint to add. + * + * @return self + * Metadata instance. + */ + public function addPropertyConstraint(Constraint $constraint) { + $this->constraints[] = $constraint; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php old mode 100644 new mode 100755 index 2858daf..2d52915 --- a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php +++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php @@ -10,7 +10,7 @@ use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\ListInterface; use Drupal\Core\TypedData\TypedDataInterface; -use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; /** * Typed data implementation of the validator MetadataFactoryInterface. @@ -18,27 +18,25 @@ class MetadataFactory implements MetadataFactoryInterface { /** - * Implements MetadataFactoryInterface::getMetadataFor(). + * {@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 = '') { + public function getMetadataFor($typed_data) { 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); + return new $class($typed_data, $this); } /** - * Implements MetadataFactoryInterface::hasMetadataFor(). + * {@inheritdoc} */ public function hasMetadataFor($value) { return $value instanceof TypedDataInterface; } + } diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php index f5850eb..6f9f5d7 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php +++ b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php @@ -9,37 +9,64 @@ use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\ListInterface; -use Symfony\Component\Validator\PropertyMetadataContainerInterface; +use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\Validation\Plugin\Validation\Constraint\ComplexDataConstraint; +use Symfony\Component\Validator\Exception\BadMethodCallException; +use Symfony\Component\Validator\Mapping\CascadingStrategy; +use Symfony\Component\Validator\Mapping\TraversalStrategy; use Symfony\Component\Validator\ValidationVisitorInterface; /** - * Typed data implementation of the validator MetadataInterface. + * Validator metadata for typed data property containers. */ -class PropertyContainerMetadata extends Metadata implements PropertyMetadataContainerInterface { +class PropertyContainerMetadata extends MetadataBase { /** - * Overrides Metadata::accept(). + * Array of property constraints. + * + * @var \Symfony\Component\Validator\Constraint[] */ - 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()) { - $typed_data = NULL; - } - $visitor->visit($this, $typed_data, $group, $propertyPath); - $pathPrefix = isset($propertyPath) && $propertyPath !== '' ? $propertyPath . '.' : ''; + protected $propertyConstraints = []; - if ($typed_data) { - foreach ($typed_data as $name => $data) { - $metadata = $this->factory->getMetadataFor($data, $name); - $metadata->accept($visitor, $data, $group, $pathPrefix . $name); + /** + * {@inheritdoc} + */ + public function __construct(TypedDataInterface $typed_data, MetadataFactory $factory) { + parent::__construct($typed_data, $factory); + $constraints = $typed_data->getConstraints(); + foreach ($constraints as $constraint) { + if ($constraint instanceof ComplexDataConstraint) { + $this->propertyConstraints = $constraint->properties; + } + else { + $this->constraints[] = $constraint; } } } /** - * Implements PropertyMetadataContainerInterface::hasPropertyMetadata(). + * {@inheritdoc} + */ + public function findConstraints($group) { + return $this->getConstraints(); + } + + /** + * {@inheritdoc} + */ + public function getConstraints() { + return $this->constraints; + } + + /** + * {@inheritdoc} + */ + public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, $propertyPath) { + throw new BadMethodCallException('Not supported.'); + } + + /** + * {@inheritdoc} */ public function hasPropertyMetadata($property_name) { try { @@ -52,17 +79,48 @@ public function hasPropertyMetadata($property_name) { } /** - * Implements PropertyMetadataContainerInterface::getPropertyMetadata(). + * {@inheritdoc} */ public function getPropertyMetadata($property_name) { + if ($this->typedData instanceof ListInterface || $this->typedData instanceof ComplexDataInterface) { + /* @var \Drupal\Core\TypedData\Validation\MetadataBase $metadata */ + $metadata = $this->factory->getMetadataFor($this->typedData->get($property_name)); + // Merge in any applicable property constraints from + // ComplexDataConstraints that were applied to this object. + if (isset($this->propertyConstraints[$property_name])) { + foreach ($this->propertyConstraints[$property_name] as $constraint) { + $metadata->addPropertyConstraint($constraint); + } + } + return [$metadata]; + } + else { + throw new \LogicException("There are no known properties."); + } + } + + /** + * {@inheritdoc} + */ + public function getConstrainedProperties() { if ($this->typedData instanceof ListInterface) { - return array(new Metadata($this->typedData[$property_name], $property_name)); + // @todo: When https://www.drupal.org/node/2164601 gets committed, + // simplify this to return the list indexes based on the list count. + return array_keys(iterator_to_array($this->typedData)); } elseif ($this->typedData instanceof ComplexDataInterface) { - return array(new Metadata($this->typedData->get($property_name), $property_name)); + return array_keys($this->typedData->getProperties()); } else { throw new \LogicException("There are no known properties."); } } + + /** + * {@inheritdoc} + */ + public function getCascadingStrategy() { + return CascadingStrategy::CASCADE; + } + } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php index efc6906..16782fb 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AllowedValuesConstraintValidator.php @@ -7,30 +7,48 @@ 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 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 { + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * 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(); - 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. if ($typed_data instanceof ComplexDataInterface) { - $name = $typed_data->getDataDefinition()->getMainPropertyName(); + $name = $value->getDataDefinition()->getMainPropertyName(); if (!isset($name)) { throw new \LogicException('Cannot validate allowed values for complex data without a main property.'); } @@ -49,4 +67,11 @@ public function validate($value, Constraint $constraint) { parent::validate($value, $constraint); } + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('current_user')); + } + } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php old mode 100644 new mode 100755 index 31ffc5b..9e8c7cc --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php @@ -8,6 +8,8 @@ namespace Drupal\Core\Validation\Plugin\Validation\Constraint; use Drupal\Core\TypedData\ComplexDataInterface; +use Drupal\Core\TypedData\ListInterface; +use Drupal\Core\TypedData\Validation\TypedDataPropertyValidationEnvelope; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -38,10 +40,18 @@ public function validate($value, Constraint $constraint) { $property = $property->getValue(); } elseif ($property->isEmpty()) { - // @see \Drupal\Core\TypedData\Validation\PropertyContainerMetadata::accept(); + // @see \Drupal\Core\TypedData\Validation\ClassMetadata::accept(); $property = NULL; } - $this->context->validateValue($property, $constraints, $name, $group); + + // Wrap the property in a validation envelope containing the constraints, + // the typed-data and the property value. + $envelope = TypedDataPropertyValidationEnvelope::create($value, $constraints, $property); + + $this->context + ->getValidator() + ->inContext($this->context) + ->validate($envelope, NULL, $group); } } }