diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 2ad6d69..54ba225 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -9,7 +9,6 @@ use Drupal\Component\Utility\String; use Drupal\Core\Entity\Plugin\DataType\EntityReference; -use Drupal\Core\Entity\TypedData\EntityDataDefinition; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\TypedDataInterface; @@ -131,13 +130,6 @@ protected $entityKeys = array(); /** - * The instantiated entity data definition. - * - * @var \Drupal\Core\Entity\TypedData\EntityDataDefinition - */ - protected $dataDefinition; - - /** * Overrides Entity::__construct(). */ public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) { @@ -261,98 +253,8 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco /** * {@inheritdoc} */ - public function getDataDefinition() { - if (!$this->dataDefinition) { - $this->dataDefinition = EntityDataDefinition::create($this->getEntityTypeId()); - $this->dataDefinition->setBundles(array($this->bundle())); - } - return $this->dataDefinition; - } - - /** - * {@inheritdoc} - */ - public function getValue() { - // @todo: This does not make much sense, so remove once TypedDataInterface - // is removed. See https://drupal.org/node/2002138. - return $this->toArray(); - } - - /** - * {@inheritdoc} - */ - public function setValue($value, $notify = TRUE) { - // @todo: This does not make much sense, so remove once TypedDataInterface - // is removed. See https://drupal.org/node/2002138. - foreach ($value as $field_name => $field_value) { - $this->set($field_name, $field_value, $notify); - } - } - - /** - * {@inheritdoc} - */ - public function getString() { - return (string) $this->label(); - } - - /** - * {@inheritdoc} - */ public function validate() { - return $this->typedDataManager()->getValidator()->validate($this); - } - - /** - * {@inheritdoc} - */ - public function applyDefaultValue($notify = TRUE) { - foreach ($this->getProperties() as $property) { - $property->applyDefaultValue(FALSE); - } - } - - /** - * {@inheritdoc} - */ - public function getConstraints() { - return array(); - } - - /** - * {@inheritdoc} - */ - public function getName() { - return NULL; - } - - /** - * {@inheritdoc} - */ - public function getRoot() { - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPropertyPath() { - return ''; - } - - /** - * {@inheritdoc} - */ - public function getParent() { - return NULL; - } - - /** - * {@inheritdoc} - */ - public function setContext($name = NULL, TypedDataInterface $parent = NULL) { - // As entities are always the root of the tree of typed data, we do not need - // to set any parent or name. + return $this->getTypedData()->validate(); } /** @@ -376,7 +278,6 @@ public function __sleep() { } $this->fields = array(); $this->fieldDefinitions = NULL; - $this->dataDefinition = NULL; $this->clearTranslationCache(); return parent::__sleep(); @@ -413,11 +314,11 @@ public function hasField($field_name) { /** * {@inheritdoc} */ - public function get($property_name) { - if (!isset($this->fields[$property_name][$this->activeLangcode])) { - return $this->getTranslatedField($property_name, $this->activeLangcode); + public function get($field_name) { + if (!isset($this->fields[$field_name][$this->activeLangcode])) { + return $this->getTranslatedField($field_name, $this->activeLangcode); } - return $this->fields[$property_name][$this->activeLangcode]; + return $this->fields[$field_name][$this->activeLangcode]; } /** @@ -452,7 +353,8 @@ protected function getTranslatedField($name, $langcode) { if (isset($this->values[$name][$langcode])) { $value = $this->values[$name][$langcode]; } - $field = \Drupal::typedDataManager()->getPropertyInstance($this, $name, $value); + $entity_adapter = $this->getTypedData(); + $field = \Drupal::typedDataManager()->getPropertyInstance($entity_adapter, $name, $value); if ($default) { // $this->defaultLangcode might not be set if we are initializing the // default language code cache, in which case there is no valid @@ -481,21 +383,21 @@ public function set($name, $value, $notify = TRUE) { /** * {@inheritdoc} */ - public function getProperties($include_computed = FALSE) { - $properties = array(); + public function getFields($include_computed = FALSE) { + $fields = array(); foreach ($this->getFieldDefinitions() as $name => $definition) { if ($include_computed || !$definition->isComputed()) { - $properties[$name] = $this->get($name); + $fields[$name] = $this->get($name); } } - return $properties; + return $fields; } /** * {@inheritdoc} */ public function getIterator() { - return new \ArrayIterator($this->getProperties()); + return new \ArrayIterator($this->getFields()); } /** @@ -525,7 +427,7 @@ public function getFieldDefinitions() { */ public function toArray() { $values = array(); - foreach ($this->getProperties() as $name => $property) { + foreach ($this->getFields() as $name => $property) { $values[$name] = $property->getValue(); } return $values; @@ -534,21 +436,6 @@ public function toArray() { /** * {@inheritdoc} */ - public function isEmpty() { - if (!$this->isNew()) { - return FALSE; - } - foreach ($this->getProperties() as $property) { - if ($property->getValue() !== NULL) { - return FALSE; - } - } - return TRUE; - } - - /** - * {@inheritdoc} - */ public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { if ($operation == 'create') { return $this->entityManager() @@ -644,8 +531,6 @@ public function onChange($name) { /** * {@inheritdoc} - * - * @return \Drupal\Core\Entity\ContentEntityInterface */ public function getTranslation($langcode) { // Ensure we always use the default language code when dealing with the @@ -826,15 +711,6 @@ public function getTranslationLanguages($include_default = TRUE) { } /** - * Overrides Entity::translations(). - * - * @todo: Remove once Entity::translations() gets removed. - */ - public function translations() { - return $this->getTranslationLanguages(FALSE); - } - - /** * Updates the original values with the interim changes. */ public function updateOriginalValues() { @@ -957,8 +833,6 @@ public function createDuplicate() { $duplicate->{$entity_type->getKey('revision')}->value = NULL; } - $duplicate->entityKeys = array(); - return $duplicate; } @@ -969,6 +843,8 @@ public function __clone() { // Avoid deep-cloning when we are initializing a translation object, since // it will represent the same entity, only with a different active language. if (!$this->translationInitialize) { + // The translation is a different object, and needs its own TypedData object. + $this->typedData = NULL; $definitions = $this->getFieldDefinitions(); foreach ($this->fields as $name => $values) { $this->fields[$name] = array(); @@ -981,7 +857,7 @@ public function __clone() { } foreach ($values as $langcode => $items) { $this->fields[$name][$langcode] = clone $items; - $this->fields[$name][$langcode]->setContext($name, $this); + $this->fields[$name][$langcode]->setContext($name, $this->getTypedData()); } } @@ -1015,11 +891,11 @@ public function referencedEntities() { $referenced_entities = array(); // Gather a list of referenced entities. - foreach ($this->getProperties() as $field_items) { + foreach ($this->getFields() as $field_items) { foreach ($field_items as $field_item) { // Loop over all properties of a field item. foreach ($field_item->getProperties(TRUE) as $property) { - if ($property instanceof EntityReference && $entity = $property->getTarget()) { + if ($property instanceof EntityReference && $entity = $property->getValue()) { $referenced_entities[] = $entity; } } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php index 249c302..fac4591 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Entity; -use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TranslatableInterface; /** @@ -28,7 +27,7 @@ * * @ingroup entity_api */ -interface ContentEntityInterface extends EntityInterface, RevisionableInterface, TranslatableInterface, ComplexDataInterface { +interface ContentEntityInterface extends EntityInterface, RevisionableInterface, TranslatableInterface { /** * Marks the translation identified by the given language code as existing. @@ -152,4 +151,67 @@ public function getFieldDefinitions(); */ public function toArray(); + /** + * Gets a field item list. + * + * @param string $field_name + * The name of the field to get; e.g., 'title' or 'name'. + * + * @throws \InvalidArgumentException + * If an invalid field name is given. + * + * @return \Drupal\Core\Field\FieldItemListInterface + * The field item list, containing the field items. + */ + public function get($field_name); + + /** + * Sets a field value. + * + * @param string $field_name + * The name of the field to set; e.g., 'title' or 'name'. + * @param mixed $value + * The value to set, or NULL to unset the field. + * @param bool $notify + * (optional) Whether to notify the entity of the change. Defaults to + * TRUE. If the update stems from the entity, set it to FALSE to avoid + * being notified again. + * + * @throws \InvalidArgumentException + * If the specified field does not exist. + * + * @return $this + */ + public function set($field_name, $value, $notify = TRUE); + + /** + * Gets an array of field item lists. + * + * @param bool $include_computed + * If set to TRUE, computed fields are included. Defaults to FALSE. + * + * @return \Drupal\Core\Field\FieldItemListInterface[] + * An array of field item lists implementing, keyed by field name. + */ + public function getFields($include_computed = FALSE); + + /** + * Reacts to changes to a field. + * + * Note that this is invoked after any changes have been applied. + * + * @param string $field_name + * The name of the field which is changed. + */ + public function onChange($field_name); + + /** + * Validates the currently set values. + * + * @return \Symfony\Component\Validator\ConstraintViolationListInterface + * A list of constraint violations. If the list is empty, validation + * succeeded. + */ + public function validate(); + } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 6d75946..07d2efa 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -195,7 +195,7 @@ protected function invokeTranslationHooks(ContentEntityInterface $entity) { protected function invokeFieldMethod($method, ContentEntityInterface $entity) { foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { $translation = $entity->getTranslation($langcode); - foreach ($translation->getProperties(TRUE) as $field) { + foreach ($translation->getFields(TRUE) as $field) { $field->$method(); } } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 51737de..6778836 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -12,8 +12,6 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException; -use Drupal\Core\Entity\Exception\AmbiguousEntityClassException; -use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException; use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageInterface; @@ -24,7 +22,10 @@ * Defines a base entity class. */ abstract class Entity implements EntityInterface { - use DependencySerializationTrait; + + use DependencySerializationTrait { + __sleep as traitSleep; + } /** * The entity type. @@ -41,6 +42,13 @@ protected $enforceIsNew; /** + * A typed data object wrapping this entity. + * + * @var \Drupal\Core\TypedData\ComplexDataInterface + */ + protected $typedData; + + /** * Constructs an Entity object. * * @param array $values @@ -547,4 +555,23 @@ public function toArray() { return array(); } + /** + * {@inheritdoc} + */ + public function getTypedData() { + if (!isset($this->typedData)) { + $class = \Drupal::typedDataManager()->getDefinition('entity')['class']; + $this->typedData = $class::createFromEntity($this); + } + return $this->typedData; + } + + /** + * {@inheritdoc} + */ + public function __sleep() { + $this->typedData = NULL; + return $this->traitSleep(); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index f60b635..5063d66 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -362,6 +362,19 @@ public function setOriginalId($id); public function toArray(); /** + * Returns a typed data object for this entity object. + * + * The returned typed data object wraps this entity and allows dealing with + * entities based on the generic typed data API. + * + * @return \Drupal\Core\TypedData\ComplexDataInterface + * The typed data object for this entity. + * + * @see \Drupal\Core\TypedData\TypedDataInterface + */ + public function getTypedData(); + + /** * The unique cache tag associated with this entity. * * @return array diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php index dc54e75..0c80fcf 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php @@ -83,7 +83,6 @@ public function getDerivativeDefinitions($base_plugin_definition) { foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { $this->derivatives[$entity_type_id] = array( 'label' => $entity_type->getLabel(), - 'class' => $entity_type->getClass(), 'constraints' => array('EntityType' => $entity_type_id), ) + $base_plugin_definition; @@ -92,7 +91,6 @@ public function getDerivativeDefinitions($base_plugin_definition) { if ($bundle !== $entity_type_id) { $this->derivatives[$entity_type_id . ':' . $bundle] = array( 'label' => $bundle_info['label'], - 'class' => $entity_type->getClass(), 'constraints' => array( 'EntityType' => $entity_type_id, 'Bundle' => $bundle, diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Entity.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Entity.php deleted file mode 100644 index 5755c51..0000000 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Entity.php +++ /dev/null @@ -1,26 +0,0 @@ -setEntityTypeId($entity->getEntityTypeId()) + ->setBundles([ $entity->bundle() ]); + $instance = new static($definition); + $instance->setValue($entity); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getValue() { + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function setValue($entity, $notify = TRUE) { + $this->entity = $entity; + // Notify the parent of any changes. + if ($notify && isset($this->parent)) { + $this->parent->onChange($this->name); + } + } + + /** + * {@inheritdoc} + */ + public function get($property_name) { + if (!isset($this->entity)) { + throw new MissingDataException(String::format('Unable to get property @name as no entity has been provided.', array('@name' => $property_name))); + } + if (!$this->entity instanceof ContentEntityInterface) { + // @todo: Add support for config entities. + throw new \InvalidArgumentException(String::format('Unable to get unknown property @name.', array('@name' => $property_name))); + } + // This will throw an exception for unknown fields. + return $this->entity->get($property_name); + } + + /** + * {@inheritdoc} + */ + public function set($property_name, $value, $notify = TRUE) { + if (!isset($this->entity)) { + throw new MissingDataException(String::format('Unable to set property @name as no entity has been provided.', array('@name' => $property_name))); + } + if (!$this->entity instanceof ContentEntityInterface) { + // @todo: Add support for config entities. + throw new \InvalidArgumentException(String::format('Unable to set unknown property @name.', array('@name' => $property_name))); + } + // This will throw an exception for unknown fields. + return $this->entity->set($property_name, $value, $notify); + } + + /** + * {@inheritdoc} + */ + public function getProperties($include_computed = FALSE) { + if (!isset($this->entity)) { + throw new MissingDataException(String::format('Unable to get properties as no entity has been provided.')); + } + if (!$this->entity instanceof ContentEntityInterface) { + // @todo: Add support for config entities. + return array(); + } + return $this->entity->getFields($include_computed); + } + + /** + * {@inheritdoc} + */ + public function toArray() { + if (!isset($this->entity)) { + throw new MissingDataException(String::format('Unable to get property values as no entity has been provided.')); + } + return $this->entity->toArray(); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + return !isset($this->entity); + } + + /** + * {@inheritdoc} + */ + public function onChange($property_name) { + if (isset($this->entity) && $this->entity instanceof ContentEntityInterface) { + // Let the entity know of any changes. + $this->entity->onChange($property_name); + } + } + + /** + * {@inheritdoc} + */ + public function getDataDefinition() { + return $this->definition; + } + + /** + * {@inheritdoc} + */ + public function getString() { + return isset($this->entity) ? $this->entity->label() : ''; + } + + /** + * {@inheritdoc} + */ + public function applyDefaultValue($notify = TRUE) { + // Apply the default value of all properties. + foreach ($this->getProperties() as $property) { + $property->applyDefaultValue(FALSE); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + return isset($this->entity) ? $this->entity->getIterator() : new \ArrayIterator([]); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php index a4e8940..3ecce46 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php @@ -60,9 +60,9 @@ public function getTargetDefinition() { */ public function getTarget() { if (!isset($this->target) && isset($this->id)) { - // If we have a valid reference, return the entity object which is typed - // data itself. - $this->target = entity_load($this->getTargetDefinition()->getEntityTypeId(), $this->id); + // If we have a valid reference, return the entity's TypedData adapter. + $entity = entity_load($this->getTargetDefinition()->getEntityTypeId(), $this->id); + $this->target = isset($entity) ? $entity->getTypedData() : NULL; } return $this->target; } @@ -82,22 +82,17 @@ public function getTargetIdentifier() { /** * {@inheritdoc} */ - public function getValue() { - // Entities are already typed data, so just return that. - return $this->getTarget(); - } - - /** - * {@inheritdoc} - */ public function setValue($value, $notify = TRUE) { unset($this->target); unset($this->id); // Both the entity ID and the entity object may be passed as value. The // reference may also be unset by passing NULL as value. - if (!isset($value) || $value instanceof EntityInterface) { - $this->target = $value; + if (!isset($value)) { + $this->target = NULL; + } + elseif ($value instanceof EntityInterface) { + $this->target = $value->getTypedData(); } elseif (!is_scalar($value) || $this->getTargetDefinition()->getEntityTypeId() === NULL) { throw new \InvalidArgumentException('Value is not a valid entity.'); 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 196dab9..746eb95 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity\Plugin\Validation\Constraint; +use Drupal\Core\TypedData\TypedDataInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -16,11 +17,20 @@ class BundleConstraintValidator extends ConstraintValidator { /** - * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate(). + * {@inheritdoc} */ public function validate($entity, Constraint $constraint) { - if (!empty($entity) && !in_array($entity->bundle(), $constraint->getBundleOption())) { + if (!isset($entity)) { + return; + } + // The constraint is used on both entity references and typed data entity + // adapters. For the latter, we need to unwrap the actual entity. + if ($entity instanceof TypedDataInterface) { + $entity = $entity->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 5e316ca..a74f20d 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Entity\Plugin\Validation\Constraint; -use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\TypedData\TypedDataInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -20,10 +20,18 @@ class EntityTypeConstraintValidator extends ConstraintValidator { * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate(). */ public function validate($entity, Constraint $constraint) { - + if (!isset($entity)) { + return; + } + // The constraint is used on both entity references and typed data entity + // adapters. For the latter, we need to unwrap the actual entity. + if ($entity instanceof TypedDataInterface) { + $entity = $entity->getValue(); + } /** @var $entity \Drupal\Core\Entity\EntityInterface */ - if (!empty($entity) && $entity->getEntityTypeId() != $constraint->type) { + if ($entity->getEntityTypeId() != $constraint->type) { $this->context->addViolation($constraint->message, array('%type' => $constraint->type)); } } + } diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php index bc269c5..75cc152 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php @@ -27,7 +27,7 @@ public function validate($value, Constraint $constraint) { if (empty($id)) { return; } - $referenced_entity = $value->get('entity')->getTarget(); + $referenced_entity = $value->get('entity')->getValue(); if (!$referenced_entity) { $type = $value->getFieldDefinition()->getSetting('target_type'); $this->context->addViolation($constraint->message, array('%type' => $type, '%id' => $id)); diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index af81e10..5b21b6b 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -1632,7 +1632,8 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit // Create field item objects and return. foreach ($items_by_entity as $revision_id => $values) { - $items_by_entity[$revision_id] = \Drupal::typedDataManager()->create($field_definition, $values, $field_definition->getName(), $entities[$revision_id]); + $entity_adapter = $entities[$revision_id]->getTypedData(); + $items_by_entity[$revision_id] = \Drupal::typedDataManager()->create($field_definition, $values, $field_definition->getName(), $entity_adapter); } return $items_by_entity; } diff --git a/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php b/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php index c2268c8..30a63a2 100644 --- a/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php +++ b/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php @@ -57,9 +57,9 @@ public static function createFromDataType($data_type) { public function getPropertyDefinitions() { if (!isset($this->propertyDefinitions)) { if ($entity_type_id = $this->getEntityTypeId()) { - // Return an empty array for entity types that don't support typed data. + // Return an empty array for entities that are not content entities. $entity_type_class = \Drupal::entityManager()->getDefinition($entity_type_id)->getClass(); - if (!in_array('Drupal\Core\TypedData\TypedDataInterface', class_implements($entity_type_class))) { + if (!in_array('Drupal\Core\Entity\ContentEntityInterface', class_implements($entity_type_class))) { $this->propertyDefinitions = array(); } else { @@ -89,9 +89,13 @@ public function getDataType() { $type = 'entity'; if ($entity_type = $this->getEntityTypeId()) { $type .= ':' . $entity_type; - // Append the bundle only if we know it for sure. + // Append the bundle only if we know it for sure and it is not the default + // bundle. if (($bundles = $this->getBundles()) && count($bundles) == 1) { - $type .= ':' . reset($bundles); + $bundle = reset($bundles); + if ($bundle != $entity_type) { + $type .= ':' . $bundle; + } } } return $type; diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index 03fab78..26e885e 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -90,7 +90,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) /** * Gets the entity that field belongs to. * - * @return \Drupal\Core\Entity\EntityInterface + * @return \Drupal\Core\Entity\ContentEntityInterface * The entity object. */ public function getEntity(); diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index 69daf67..f6ae7d5 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -57,7 +57,9 @@ public function __construct(DataDefinitionInterface $definition, $name = NULL, T * {@inheritdoc} */ public function getEntity() { - return $this->getParent(); + // The "parent" is the TypedData object for the entity, we need to unwrap + // the actual entity. + return $this->getParent()->getValue(); } /** diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php index 0366b71..a0ad01f 100644 --- a/core/lib/Drupal/Core/Plugin/Context/Context.php +++ b/core/lib/Drupal/Core/Plugin/Context/Context.php @@ -10,7 +10,6 @@ use Drupal\Component\Plugin\Context\Context as ComponentContext; use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Utility\String; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedDataTrait; @@ -47,11 +46,6 @@ public function getContextValue() { } return NULL; } - // Special case entities. - // @todo: Remove once entities do not implemented TypedDataInterface. - if ($this->contextData instanceof ContentEntityInterface) { - return $this->contextData; - } return $this->contextData->getValue(); } diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php index 923090e..1397485 100644 --- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php @@ -31,11 +31,13 @@ * @param $property_name * The name of the property to get; e.g., 'title' or 'name'. * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The property object. + * * @throws \InvalidArgumentException * If an invalid property name is given. - * - * @return \Drupal\Core\TypedData\TypedDataInterface - * The property object. + * @throws \Drupal\Core\TypedData\Exception\MissingDataException + * If the complex data structure is unset and no property can be created. */ public function get($property_name); @@ -51,11 +53,13 @@ public function get($property_name); * TRUE. If the update stems from a parent object, set it to FALSE to avoid * being notified again. * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The property object. + * * @throws \InvalidArgumentException * If the specified property does not exist. - * - * @return \Drupal\Core\TypedData\TypedDataInterface - * The property object. + * @throws \Drupal\Core\TypedData\Exception\MissingDataException + * If the complex data structure is unset and no property can be set. */ public function set($property_name, $value, $notify = TRUE); @@ -68,6 +72,9 @@ public function set($property_name, $value, $notify = TRUE); * @return \Drupal\Core\TypedData\TypedDataInterface[] * An array of property objects implementing the TypedDataInterface, keyed * by property name. + * + * @throws \Drupal\Core\TypedData\Exception\MissingDataException + * If the complex data structure is unset and no property can be created. */ public function getProperties($include_computed = FALSE); @@ -79,6 +86,9 @@ public function getProperties($include_computed = FALSE); * * @return array * An array of property values, keyed by property name. + * + * @throws \Drupal\Core\TypedData\Exception\MissingDataException + * If the complex data structure is unset and no property can be created. */ public function toArray(); diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionException.php b/core/lib/Drupal/Core/TypedData/DataDefinitionException.php deleted file mode 100644 index 2a23315..0000000 --- a/core/lib/Drupal/Core/TypedData/DataDefinitionException.php +++ /dev/null @@ -1,15 +0,0 @@ -_entity; $item = new \stdClass(); - foreach ($entity->getProperties() as $name => $value) { + foreach ($entity as $name => $field) { // views_view_row_rss takes care about the escaping. - $item->{$name} = $value->value; + $item->{$name} = $field->value; } $item->elements = array( diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php index cec6b4a..6a24454 100644 --- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php @@ -85,7 +85,7 @@ public function normalize($entity, $format = NULL, array $context = array()) { } } else { - $fields = $entity->getProperties(); + $fields = $entity->getFields(); } // Ignore the entity ID and revision ID. $exclude = array($entity->getEntityType()->getKey('id'), $entity->getEntityType()->getKey('revision')); diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php index c9eaa9a..652c650 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php @@ -61,10 +61,10 @@ public function testNormalize() { $content_entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase') ->disableOriginalConstructor() - ->setMethods(array('getProperties')) + ->setMethods(array('getFields')) ->getMockForAbstractClass(); $content_entity->expects($this->once()) - ->method('getProperties') + ->method('getFields') ->will($this->returnValue($definitions)); $serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer') diff --git a/core/modules/system/src/Tests/Entity/EntityFieldTest.php b/core/modules/system/src/Tests/Entity/EntityFieldTest.php index e9d50ad..dd9b2ef 100644 --- a/core/modules/system/src/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/src/Tests/Entity/EntityFieldTest.php @@ -416,27 +416,28 @@ protected function checkIntrospection($entity_type) { $this->assertEqual($textfield_properties['processed']->getDataType(), 'string', $entity_type .': String processed property of the test-text field found.'); // Make sure provided contextual information is right. - $this->assertIdentical($entity->getRoot(), $entity, 'Entity is root object.'); - $this->assertEqual($entity->getPropertyPath(), ''); - $this->assertEqual($entity->getName(), ''); - $this->assertEqual($entity->getParent(), NULL); + $entity_adapter = $entity->getTypedData(); + $this->assertIdentical($entity_adapter->getRoot(), $entity_adapter, 'Entity is root object.'); + $this->assertEqual($entity_adapter->getPropertyPath(), ''); + $this->assertEqual($entity_adapter->getName(), ''); + $this->assertEqual($entity_adapter->getParent(), NULL); $field = $entity->user_id; - $this->assertIdentical($field->getRoot(), $entity, 'Entity is root object.'); + $this->assertIdentical($field->getRoot()->getValue(), $entity, 'Entity is root object.'); $this->assertIdentical($field->getEntity(), $entity, 'getEntity() returns the entity.'); $this->assertEqual($field->getPropertyPath(), 'user_id'); $this->assertEqual($field->getName(), 'user_id'); - $this->assertIdentical($field->getParent(), $entity, 'Parent object matches.'); + $this->assertIdentical($field->getParent()->getValue(), $entity, 'Parent object matches.'); $field_item = $field[0]; - $this->assertIdentical($field_item->getRoot(), $entity, 'Entity is root object.'); + $this->assertIdentical($field_item->getRoot()->getValue(), $entity, 'Entity is root object.'); $this->assertIdentical($field_item->getEntity(), $entity, 'getEntity() returns the entity.'); $this->assertEqual($field_item->getPropertyPath(), 'user_id.0'); $this->assertEqual($field_item->getName(), '0'); $this->assertIdentical($field_item->getParent(), $field, 'Parent object matches.'); $item_value = $field_item->get('entity'); - $this->assertIdentical($item_value->getRoot(), $entity, 'Entity is root object.'); + $this->assertIdentical($item_value->getRoot()->getValue(), $entity, 'Entity is root object.'); $this->assertEqual($item_value->getPropertyPath(), 'user_id.0.entity'); $this->assertEqual($item_value->getName(), 'entity'); $this->assertIdentical($item_value->getParent(), $field_item, 'Parent object matches.'); @@ -476,9 +477,9 @@ protected function assertIterator($entity_type) { } } - $properties = $entity->getProperties(); - $this->assertEqual(array_keys($properties), array_keys($entity->getDataDefinition()->getPropertyDefinitions()), format_string('%entity_type: All properties returned.', array('%entity_type' => $entity_type))); - $this->assertEqual($properties, iterator_to_array($entity->getIterator()), format_string('%entity_type: Entity iterator iterates over all properties.', array('%entity_type' => $entity_type))); + $fields = $entity->getFields(); + $this->assertEqual(array_keys($fields), array_keys($entity->getTypedData()->getDataDefinition()->getPropertyDefinitions()), format_string('%entity_type: All fields returned.', array('%entity_type' => $entity_type))); + $this->assertEqual($fields, iterator_to_array($entity->getIterator()), format_string('%entity_type: Entity iterator iterates over all fields.', array('%entity_type' => $entity_type))); } /** @@ -504,7 +505,7 @@ protected function assertDataStructureInterfaces($entity_type) { // contained properties and getting all contained strings, limited by a // certain depth. $strings = array(); - $this->getContainedStrings($entity, 0, $strings); + $this->getContainedStrings($entity->getTypedData(), 0, $strings); // @todo: Once the user entity has defined properties this should contain // the user name and other user entity strings as well. diff --git a/core/modules/system/src/Tests/Entity/EntityUUIDTest.php b/core/modules/system/src/Tests/Entity/EntityUUIDTest.php index 39bfa70..5f3b6e1 100644 --- a/core/modules/system/src/Tests/Entity/EntityUUIDTest.php +++ b/core/modules/system/src/Tests/Entity/EntityUUIDTest.php @@ -73,7 +73,7 @@ protected function assertCRUD($entity_type) { // Creating a duplicate needs to result in a new UUID. $entity_duplicate = $entity->createDuplicate(); - foreach ($entity->getProperties() as $property => $value) { + foreach ($entity->getFields() as $property => $value) { switch($property) { case 'uuid': $this->assertNotNull($entity_duplicate->uuid()); diff --git a/core/modules/system/src/Tests/Entity/EntityValidationTest.php b/core/modules/system/src/Tests/Entity/EntityValidationTest.php index 73f0fd9..8fdcf82 100644 --- a/core/modules/system/src/Tests/Entity/EntityValidationTest.php +++ b/core/modules/system/src/Tests/Entity/EntityValidationTest.php @@ -133,7 +133,7 @@ protected function checkValidation($entity_type) { // Make sure the information provided by a violation is correct. $violation = $violations[0]; - $this->assertEqual($violation->getRoot(), $test_entity, 'Violation root is entity.'); + $this->assertEqual($violation->getRoot()->getValue(), $test_entity, 'Violation root is entity.'); $this->assertEqual($violation->getPropertyPath(), 'name.0.value', 'Violation property path is correct.'); $this->assertEqual($violation->getInvalidValue(), $test_entity->name->value, 'Violation contains invalid value.'); @@ -151,7 +151,7 @@ protected function checkValidation($entity_type) { // Make sure the information provided by a violation is correct. $violation = $violations[0]; - $this->assertEqual($violation->getRoot(), $test_entity, 'Violation root is entity.'); + $this->assertEqual($violation->getRoot()->getValue(), $test_entity, 'Violation root is entity.'); $this->assertEqual($violation->getPropertyPath(), 'field_test_text.0.format', 'Violation property path is correct.'); $this->assertEqual($violation->getInvalidValue(), $test_entity->field_test_text->format, 'Violation contains invalid value.'); } diff --git a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php index d67d055..c7dab96 100644 --- a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php +++ b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php @@ -68,7 +68,7 @@ function testContext() { $user = entity_create('user', array('name' => $name)); $plugin->setContextValue('user', $user); - $this->assertEqual($plugin->getContextValue('user')->getName(), $user->getName()); + $this->assertEqual($plugin->getContextValue('user')->getUsername(), $user->getUsername()); $this->assertEqual($user->label(), $plugin->getTitle()); // Test Optional context handling. diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index bf6b42a..f81f251 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1127,4 +1127,11 @@ public function getListCacheTags() { $this->storage->getListCacheTags(); } + /** + * {@inheritdoc} + */ + public function getTypedData() { + $this->storage->getTypedData(); + } + } diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php index b543ec3..168f86e 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php @@ -10,7 +10,6 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Field\BaseFieldDefinition; -use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Language\LanguageInterface; use Drupal\Tests\UnitTestCase; use Drupal\Core\Language\Language; @@ -138,6 +137,10 @@ protected function setUp() { $this->typedDataManager = $this->getMockBuilder('\Drupal\Core\TypedData\TypedDataManager') ->disableOriginalConstructor() ->getMock(); + $this->typedDataManager->expects($this->any()) + ->method('getDefinition') + ->with('entity') + ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\Entity'])); $english = new Language(array('id' => 'en')); $not_specified = new Language(array('id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE)); @@ -228,7 +231,7 @@ public function testIsNewRevision() { $this->typedDataManager->expects($this->any()) ->method('getPropertyInstance') - ->with($this->entity, 'revision_id', NULL) + ->with($this->entity->getTypedData(), 'revision_id', NULL) ->will($this->returnValue($field_item_list)); $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item)); @@ -305,23 +308,6 @@ public function testPreSaveRevision() { } /** - * @covers ::getString - */ - public function testGetString() { - $label = $this->randomMachineName(); - /** @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject $entity */ - $entity = $this->getMockBuilder('\Drupal\Core\Entity\ContentEntityBase') - ->setMethods(array('label')) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $entity->expects($this->once()) - ->method('label') - ->will($this->returnValue($label)); - - $this->assertSame($label, $entity->getString()); - } - - /** * @covers ::validate */ public function testValidate() { @@ -335,11 +321,11 @@ public function testValidate() { $non_empty_violation_list->add($violation); $validator->expects($this->at(0)) ->method('validate') - ->with($this->entity) + ->with($this->entity->getTypedData()) ->will($this->returnValue($empty_violation_list)); $validator->expects($this->at(1)) ->method('validate') - ->with($this->entity) + ->with($this->entity->getTypedData()) ->will($this->returnValue($non_empty_violation_list)); $this->typedDataManager->expects($this->exactly(2)) ->method('getValidator') @@ -349,51 +335,6 @@ public function testValidate() { } /** - * @covers ::getConstraints - */ - public function testGetConstraints() { - $this->assertInternalType('array', $this->entity->getConstraints()); - } - - /** - * @covers ::getName - */ - public function testGetName() { - $this->assertNull($this->entity->getName()); - } - - /** - * @covers ::getRoot - */ - public function testGetRoot() { - $this->assertSame(spl_object_hash($this->entity), spl_object_hash($this->entity->getRoot())); - } - - /** - * @covers ::getPropertyPath - */ - public function testGetPropertyPath() { - $this->assertSame('', $this->entity->getPropertyPath()); - } - - /** - * @covers ::getParent - */ - public function testGetParent() { - $this->assertNull($this->entity->getParent()); - } - - /** - * @covers ::setContext - */ - public function testSetContext() { - $name = $this->randomMachineName(); - $parent = $this->getMock('\Drupal\Core\TypedData\TypedDataInterface'); - // Our mocked entity->setContext() returns NULL, so assert that. - $this->assertNull($this->entity->setContext($name, $parent)); - } - - /** * @covers ::bundle */ public function testBundle() { diff --git a/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php new file mode 100644 index 0000000..74b9b44 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php @@ -0,0 +1,422 @@ +id = 1; + $values = array( + 'id' => $this->id, + 'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a', + 'defaultLangcode' => array(LanguageInterface::LANGCODE_DEFAULT => 'en'), + ); + $this->entityTypeId = $this->randomMachineName(); + $this->bundle = $this->randomMachineName(); + + $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $this->entityType->expects($this->any()) + ->method('getKeys') + ->will($this->returnValue(array( + 'id' => 'id', + 'uuid' => 'uuid', + ))); + + $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); + $this->entityManager->expects($this->any()) + ->method('getDefinition') + ->with($this->entityTypeId) + ->will($this->returnValue($this->entityType)); + + $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface'); + + $this->typedDataManager = $this->getMockBuilder('\Drupal\Core\TypedData\TypedDataManager') + ->disableOriginalConstructor() + ->getMock(); + $this->typedDataManager->expects($this->any()) + ->method('getDefinition') + ->with('entity') + ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\Entity'])); + $this->typedDataManager->expects($this->any()) + ->method('getDefaultConstraints') + ->willReturn([]); + + $validation_constraint_manager = $this->getMockBuilder('\Drupal\Core\Validation\ConstraintManager') + ->disableOriginalConstructor() + ->getMock(); + $validation_constraint_manager->expects($this->any()) + ->method('create') + ->willReturn([]); + $this->typedDataManager->expects($this->any()) + ->method('getValidationConstraintManager') + ->willReturn($validation_constraint_manager); + + $this->fieldItemList = $this->getMock('\Drupal\Core\Field\FieldItemListInterface'); + $this->typedDataManager->expects($this->any()) + ->method('getPropertyInstance') + ->willReturn($this->fieldItemList); + + $not_specified = new Language(array('id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE)); + $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface'); + $this->languageManager->expects($this->any()) + ->method('getLanguages') + ->will($this->returnValue(array(LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified))); + + $this->languageManager->expects($this->any()) + ->method('getLanguage') + ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED) + ->will($this->returnValue($not_specified)); + + $this->fieldTypePluginManager = $this->getMockBuilder('\Drupal\Core\Field\FieldTypePluginManager') + ->disableOriginalConstructor() + ->getMock(); + $this->fieldTypePluginManager->expects($this->any()) + ->method('getDefaultStorageSettings') + ->will($this->returnValue(array())); + $this->fieldTypePluginManager->expects($this->any()) + ->method('getDefaultFieldSettings') + ->will($this->returnValue(array())); + + $container = new ContainerBuilder(); + $container->set('entity.manager', $this->entityManager); + $container->set('uuid', $this->uuid); + $container->set('typed_data_manager', $this->typedDataManager); + $container->set('language_manager', $this->languageManager); + $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager); + \Drupal::setContainer($container); + + $this->fieldDefinitions = array( + 'id' => BaseFieldDefinition::create('integer'), + 'revision_id' => BaseFieldDefinition::create('integer'), + ); + + $this->entityManager->expects($this->any()) + ->method('getFieldDefinitions') + ->with($this->entityTypeId, $this->bundle) + ->will($this->returnValue($this->fieldDefinitions)); + $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', array($values, $this->entityTypeId, $this->bundle)); + + $this->entityAdapter = EntityAdapter::createFromEntity($this->entity); + } + + /** + * @covers ::getConstraints + */ + public function testGetConstraints() { + $this->assertInternalType('array', $this->entityAdapter->getConstraints()); + } + + /** + * @covers ::getName + */ + public function testGetName() { + $this->assertNull($this->entityAdapter->getName()); + } + + /** + * @covers ::getRoot + */ + public function testGetRoot() { + $this->assertSame(spl_object_hash($this->entityAdapter), spl_object_hash($this->entityAdapter->getRoot())); + } + + /** + * @covers ::getPropertyPath + */ + public function testGetPropertyPath() { + $this->assertSame('', $this->entityAdapter->getPropertyPath()); + } + + /** + * @covers ::getParent + */ + public function testGetParent() { + $this->assertNull($this->entityAdapter->getParent()); + } + + /** + * @covers ::setContext + */ + public function testSetContext() { + $name = $this->randomMachineName(); + $parent = $this->getMock('\Drupal\Core\TypedData\TypedDataInterface'); + // Our mocked entity->setContext() returns NULL, so assert that. + $this->assertNull($this->entityAdapter->setContext($name, $parent)); + $this->assertEquals($name, $this->entityAdapter->getName()); + $this->assertEquals($parent, $this->entityAdapter->getParent()); + } + + /** + * @covers ::getValue + */ + public function testGetValue() { + $this->assertEquals($this->entity, $this->entityAdapter->getValue()); + } + + /** + * @covers ::setValue + */ + public function testSetValue() { + $this->entityAdapter->setValue(NULL); + $this->assertNull($this->entityAdapter->getValue()); + } + + /** + * @covers ::get + */ + public function testGet() { + $this->assertInstanceOf('\Drupal\Core\Field\FieldItemListInterface', $this->entityAdapter->get('id')); + } + + /** + * @covers ::get + * @expectedException \InvalidArgumentException + */ + public function testGetInvalidField() { + $this->entityAdapter->get('invalid'); + } + + /** + * @covers ::get + * @expectedException \Drupal\Core\TypedData\Exception\MissingDataException + */ + public function testGetWithoutData() { + $this->entityAdapter->setValue(NULL); + $this->entityAdapter->get('id'); + } + + /** + * @covers ::set + */ + public function testSet() { + $id_items = [ array('value' => $this->id + 1) ]; + + $this->fieldItemList->expects($this->once()) + ->method('setValue') + ->with($id_items); + + $this->entityAdapter->set('id', $id_items); + } + + /** + * @covers ::set + * @expectedException \Drupal\Core\TypedData\Exception\MissingDataException + */ + public function testSetWithoutData() { + $this->entityAdapter->setValue(NULL); + $id_items = [ array('value' => $this->id + 1) ]; + $this->entityAdapter->set('id', $id_items); + } + + /** + * @covers ::getProperties + */ + public function testGetProperties() { + $fields = $this->entityAdapter->getProperties(); + $this->assertInstanceOf('Drupal\Core\Field\FieldItemListInterface', $fields['id']); + $this->assertInstanceOf('Drupal\Core\Field\FieldItemListInterface', $fields['revision_id']); + } + + /** + * @covers ::toArray + */ + public function testToArray() { + $array = $this->entityAdapter->toArray(); + // Mock field objects return NULL values, so test keys only. + $this->assertArrayHasKey('id', $array); + $this->assertArrayHasKey('revision_id', $array); + $this->assertEquals(count($array), 2); + } + + /** + * @covers ::toArray + * @expectedException \Drupal\Core\TypedData\Exception\MissingDataException + */ + public function testToArrayWithoutData() { + $this->entityAdapter->setValue(NULL); + $this->entityAdapter->toArray(); + } + + /** + * @covers ::isEmpty + */ + public function testIsEmpty() { + $this->assertFalse($this->entityAdapter->isEmpty()); + $this->entityAdapter->setValue(NULL); + $this->assertTrue($this->entityAdapter->isEmpty()); + } + + /** + * @covers ::onChange + */ + public function testOnChange() { + $entity = $this->getMock('\Drupal\Core\Entity\ContentEntityInterface'); + $entity->expects($this->once()) + ->method('onChange') + ->with('foo') + ->willReturn(NULL); + $this->entityAdapter->setValue($entity); + $this->entityAdapter->onChange('foo'); + } + + /** + * @covers ::getDataDefinition + */ + public function testGetDataDefinition() { + $definition = $this->entityAdapter->getDataDefinition(); + $this->assertInstanceOf('\Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface', $definition); + $this->assertEquals($definition->getEntityTypeId(), $this->entityTypeId); + $this->assertEquals($definition->getBundles(), [ $this->bundle ]); + } + + /** + * @covers ::getString + */ + public function testGetString() { + $entity = $this->getMock('\Drupal\Core\Entity\ContentEntityInterface'); + $entity->expects($this->once()) + ->method('label') + ->willReturn('foo'); + $this->entityAdapter->setValue($entity); + $this->assertEquals('foo', $this->entityAdapter->getString()); + $this->entityAdapter->setValue(NULL); + $this->assertEquals('', $this->entityAdapter->getString()); + } + + /** + * @covers ::applyDefaultValue + */ + public function testApplyDefaultValue() { + // For each field on the entity the mock method has to be invoked once. + $this->fieldItemList->expects($this->exactly(2)) + ->method('applyDefaultValue'); + $this->entityAdapter->applyDefaultValue(); + } + + /** + * @covers ::getIterator + */ + public function testGetIterator() { + $iterator = $this->entityAdapter->getIterator(); + $fields = iterator_to_array($iterator); + $this->assertArrayHasKey('id', $fields); + $this->assertArrayHasKey('revision_id', $fields); + $this->assertEquals(count($fields), 2); + + $this->entityAdapter->setValue(NULL); + $this->assertEquals(new \ArrayIterator([]), $this->entityAdapter->getIterator()); + } +}