diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php index c6743c8..4501623 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php @@ -19,22 +19,11 @@ class BundleConstraintValidator extends ConstraintValidator { /** * {@inheritdoc} */ - public function validate($entity_adapter, Constraint $constraint) { - if (!isset($entity_adapter)) { + public function validate($entity, Constraint $constraint) { + if (!isset($entity)) { return; } - // @todo The $entity_adapter parameter passed to this function should always - // be a typed data object, but due to a bug, the unwrapped entity is - // passed for the computed entity property of entity reference fields. - // Remove this after fixing that in https://www.drupal.org/node/2346373. - if (!$entity_adapter instanceof TypedDataInterface) { - $entity = $entity_adapter; - } - else { - $entity = $entity_adapter->getValue(); - } - if (!in_array($entity->bundle(), $constraint->getBundleOption())) { $this->context->addViolation($constraint->message, array('%bundle' => implode(', ', $constraint->getBundleOption()))); } diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php index 5138856..36476df 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php @@ -17,24 +17,13 @@ class EntityTypeConstraintValidator extends ConstraintValidator { /** - * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate(). + * {@inheritdoc} */ - public function validate($entity_adapter, Constraint $constraint) { - if (!isset($entity_adapter)) { + public function validate($entity, Constraint $constraint) { + if (!isset($entity)) { return; } - // @todo The $entity_adapter parameter passed to this function should always - // be a typed data object, but due to a bug, the unwrapped entity is - // passed for the computed entity property of entity reference fields. - // Remove this after fixing that in https://www.drupal.org/node/2346373. - if (!$entity_adapter instanceof TypedDataInterface) { - $entity = $entity_adapter; - } - else { - $entity = $entity_adapter->getValue(); - } - /** @var $entity \Drupal\Core\Entity\EntityInterface */ if ($entity->getEntityTypeId() != $constraint->type) { $this->context->addViolation($constraint->message, array('%type' => $constraint->type)); diff --git a/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php index ef15e33..58fce03 100644 --- a/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php +++ b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php @@ -79,6 +79,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { foreach ($this->fieldTypePluginManager->getDefinitions() as $plugin_id => $definition) { $definition['definition_class'] = '\Drupal\Core\Field\TypedData\FieldItemDataDefinition'; $definition['list_definition_class'] = '\Drupal\Core\Field\BaseFieldDefinition'; + $definition['wrapped'] = FALSE; $this->derivatives[$plugin_id] = $definition; } return $this->derivatives; diff --git a/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php b/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php index bcca2ec..7327eb2 100644 --- a/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php @@ -18,7 +18,8 @@ * id = "field_item", * label = @Translation("Field item"), * list_class = "\Drupal\Core\Field\FieldItemList", - * deriver = "Drupal\Core\Field\Plugin\DataType\Deriver\FieldItemDeriver" + * deriver = "Drupal\Core\Field\Plugin\DataType\Deriver\FieldItemDeriver", + * wrapped = false * ) */ abstract class FieldItem { diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php index a0ad01f..c5ac341 100644 --- a/core/lib/Drupal/Core/Plugin/Context/Context.php +++ b/core/lib/Drupal/Core/Plugin/Context/Context.php @@ -46,7 +46,7 @@ public function getContextValue() { } return NULL; } - return $this->contextData->getValue(); + return $this->typedDataManager->unwrap($this->contextData); } /** diff --git a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php index 332c3ed..49d9fcd 100644 --- a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php +++ b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php @@ -106,4 +106,19 @@ class DataType extends Plugin { */ public $constraints; + /** + * Whether data of this type is wrapped by typed data objects. + * + * Mostly, typed data objects wrap a data value to make it usable based + * on the Typed Data API. However, data which is already represented by PHP + * objects may opt to implement the Typed Data interface directly, such that + * a wrapping object becomes unnecessary. In that case, this variable should + * be set to FALSE. + * + * @var bool + * + * @see \Drupal\Core\TypedData\TypedDataManager::unwrap() + */ + public $wrapped = TRUE; + } diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index 7fe362e..c5fbea0 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -332,7 +332,7 @@ public function setValidator(ValidatorInterface $validator) { public function getValidator() { if (!isset($this->validator)) { $this->validator = Validation::createValidatorBuilder() - ->setMetadataFactory(new MetadataFactory()) + ->setMetadataFactory(new MetadataFactory($this)) ->setTranslator(new DrupalTranslator()) ->setConstraintValidatorFactory(new ConstraintValidatorFactory($this->classResolver)) ->setApiVersion(Validation::API_VERSION_2_4) @@ -415,4 +415,35 @@ public function clearCachedDefinitions() { $this->prototypes = array(); } + /** + * Unwraps a data object if needed. + * + * Unwraps the object unless the data object directly implements the Typed + * Data API. See \Drupal\Core\TypedData\Annotation\DataType::$wrapped. + * + * This may be used to ensure data is in the right representation before it is + * passed on to other code components. E.g., when typed data is validated it + * gets unwrapped before it is passed on to constraint validators, such that + * entity objects get passed unwrapped and fields stay objects (which directly + * implemented TypedDataInterface). + * + * @param \Drupal\Core\TypedData\TypedDataInterface $data + * The data object to unwrap. + * + * @return mixed + * The data, without any wrapping Typed Data object. + */ + public function unwrap(TypedDataInterface $data) { + $data_definition = $data->getDataDefinition(); + // In case a list is passed, respect the 'wrapped' key of its data type. + if ($data_definition instanceof ListDataDefinitionInterface) { + $data_definition = $data_definition->getItemDefinition(); + } + $type_definition = $this->getDefinition($data_definition->getDataType()); + if (!empty($type_definition['wrapped'])) { + return $data->getValue(); + } + return $data; + } + } diff --git a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php index 73bfc8b..3f3ef11 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php +++ b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php @@ -8,6 +8,7 @@ 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; @@ -38,6 +39,13 @@ class Metadata implements PropertyMetadataInterface { protected $factory; /** + * The typed data manager. + * + * @var \Drupal\Core\TypedData\TypedDataManager + */ + protected $typedDataManager; + + /** * Constructs the object. * * @param \Drupal\Core\TypedData\TypedDataInterface $typed_data @@ -47,11 +55,14 @@ class Metadata implements PropertyMetadataInterface { * 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) { + 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; } /** @@ -62,7 +73,7 @@ public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, // @todo: Do we have to care about groups? Symfony class metadata has // $propagatedGroup. - $visitor->visit($this, $typed_data->getValue(), $group, $propertyPath); + $visitor->visit($this, $this->typedDataManager->unwrap($typed_data), $group, $propertyPath); } /** @@ -89,7 +100,7 @@ public function getPropertyName() { * @return mixed The value of the property. */ public function getPropertyValue($container) { - return $this->typedData->getValue(); + return $this->typedDataManager->unwrap($this->typedData); } /** diff --git a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php index 2858daf..fcd0557 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php +++ b/core/lib/Drupal/Core/TypedData/Validation/MetadataFactory.php @@ -10,6 +10,7 @@ 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; /** @@ -18,7 +19,24 @@ class MetadataFactory implements MetadataFactoryInterface { /** - * Implements MetadataFactoryInterface::getMetadataFor(). + * 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. @@ -32,7 +50,7 @@ public function getMetadataFor($typed_data, $name = '') { } $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, $name, $this, $this->typedDataManager); } /** diff --git a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php index f5850eb..9aa57dc 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php +++ b/core/lib/Drupal/Core/TypedData/Validation/PropertyContainerMetadata.php @@ -25,12 +25,16 @@ public function accept(ValidationVisitorInterface $visitor, $typed_data, $group, // if the data structure is empty. That way existing NotNull or NotBlank // constraints work as expected. if ($typed_data->isEmpty()) { - $typed_data = NULL; + $data = NULL; } - $visitor->visit($this, $typed_data, $group, $propertyPath); + else { + $data = $this->typedDataManager->unwrap($typed_data); + } + $visitor->visit($this, $data, $group, $propertyPath); $pathPrefix = isset($propertyPath) && $propertyPath !== '' ? $propertyPath . '.' : ''; - if ($typed_data) { + // 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); @@ -56,10 +60,10 @@ public function hasPropertyMetadata($property_name) { */ public function getPropertyMetadata($property_name) { if ($this->typedData instanceof ListInterface) { - return array(new Metadata($this->typedData[$property_name], $property_name)); + 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)); + return array(new Metadata($this->typedData->get($property_name), $property_name, $this->factory, $this->typedDataManager)); } else { throw new \LogicException("There are no known properties."); diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ComplexDataConstraintValidator.php index 31ffc5b..add46eb 100644 --- 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,7 @@ namespace Drupal\Core\Validation\Plugin\Validation\Constraint; use Drupal\Core\TypedData\ComplexDataInterface; +use Drupal\Core\TypedData\TypedDataInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -25,6 +26,10 @@ public function validate($value, Constraint $constraint) { return; } + // If un-wrapped data has been passed, fetch the typed data object first. + if (!$value instanceof TypedDataInterface) { + $value = $this->context->getMetadata()->getTypedData(); + } if (!$value instanceof ComplexDataInterface) { throw new UnexpectedTypeException($value, 'ComplexData'); }