From c96966f079533c333260fb3051fb890ab186a027 Mon Sep 17 00:00:00 2001 From: catch Date: Wed, 2 Jan 2013 11:51:19 +0000 Subject: [PATCH] Issue #1869250 by fago, Berdir, das-peter, YesCT, mradcliffe, fubhy: Various EntityNG and TypedData API improvements. --- .../Core/Config/Entity/ConfigEntityBase.php | 50 +++ .../Core/Entity/DatabaseStorageController.php | 21 +- .../Entity/DatabaseStorageControllerNG.php | 51 +-- core/lib/Drupal/Core/Entity/Entity.php | 50 +++ .../Drupal/Core/Entity/EntityBCDecorator.php | 368 ++++++++++++++++++ .../Core/Entity/EntityFormControllerNG.php | 12 +- .../Drupal/Core/Entity/EntityInterface.php | 22 +- core/lib/Drupal/Core/Entity/EntityNG.php | 282 +++++++++----- .../Core/Entity/EntityRenderController.php | 19 +- .../Core/Entity/Field/FieldItemBase.php | 84 ++-- .../Core/Entity/Field/Type/BooleanItem.php | 2 +- .../Core/Entity/Field/Type/DateItem.php | 2 +- .../Entity/Field/Type/EntityReferenceItem.php | 11 +- .../Entity/Field/Type/EntityTranslation.php | 63 +-- .../Core/Entity/Field/Type/EntityWrapper.php | 88 ++--- .../Drupal/Core/Entity/Field/Type/Field.php | 171 ++++---- .../Core/Entity/Field/Type/IntegerItem.php | 2 +- .../Core/Entity/Field/Type/LanguageItem.php | 9 +- .../Core/Entity/Field/Type/StringItem.php | 2 +- .../Core/TypedData/ComplexDataInterface.php | 3 + .../Core/TypedData/ContextAwareInterface.php | 60 +-- .../Core/TypedData/ContextAwareTypedData.php | 109 ++++++ .../Drupal/Core/TypedData/ListInterface.php | 10 +- .../lib/Drupal/Core/TypedData/Type/Binary.php | 4 +- .../Drupal/Core/TypedData/Type/Boolean.php | 4 +- core/lib/Drupal/Core/TypedData/Type/Date.php | 4 +- .../Drupal/Core/TypedData/Type/Duration.php | 4 +- core/lib/Drupal/Core/TypedData/Type/Float.php | 4 +- .../Drupal/Core/TypedData/Type/Integer.php | 4 +- .../Drupal/Core/TypedData/Type/Language.php | 51 +-- .../lib/Drupal/Core/TypedData/Type/String.php | 4 +- core/lib/Drupal/Core/TypedData/Type/Uri.php | 4 +- .../Core/TypedData/{Type => }/TypedData.php | 6 +- .../Core/TypedData/TypedDataFactory.php | 40 +- .../Core/TypedData/TypedDataManager.php | 165 ++++++-- .../Drupal/jsonld/JsonldEntityNormalizer.php | 8 + .../lib/Drupal/simpletest/WebTestBase.php | 4 +- .../system/Tests/Entity/EntityFieldTest.php | 28 +- .../EntityTestStorageController.php | 27 +- .../Plugin/Core/Entity/EntityTest.php | 10 +- .../text/lib/Drupal/text/TextProcessed.php | 76 ++-- .../text/lib/Drupal/text/Type/TextItem.php | 4 +- .../lib/Drupal/text/Type/TextSummaryItem.php | 4 +- .../EntityTranslationControllerNG.php} | 10 +- .../Tests/EntityTranslationUITest.php | 2 +- .../views_ui/lib/Drupal/views_ui/ViewUI.php | 49 +++ 46 files changed, 1346 insertions(+), 661 deletions(-) create mode 100644 core/lib/Drupal/Core/Entity/EntityBCDecorator.php create mode 100644 core/lib/Drupal/Core/TypedData/ContextAwareTypedData.php rename core/lib/Drupal/Core/TypedData/{Type => }/TypedData.php (90%) rename core/modules/{system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php => translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php} (56%) diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index a5000ce6790..490470d848b 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Config\Entity; use Drupal\Core\Entity\Entity; +use Drupal\Core\TypedData\ContextAwareInterface; /** * Defines a base configuration entity class. @@ -126,4 +127,53 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface return $properties; } + /** + * Implements Drupal\Core\Entity\EntityInterface::getBCEntity(). + */ + public function getBCEntity() { + return $this; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::getOriginalEntity(). + */ + public function getOriginalEntity() { + return $this; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getName(). + */ + public function getName() { + return NULL; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getRoot(). + */ + public function getRoot() { + return $this; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getPropertyPath(). + */ + public function getPropertyPath() { + return ''; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getParent(). + */ + public function getParent() { + return NULL; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::setContext(). + */ + public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { + // As entities are always the root of the tree, we do not need to set any + // context. + } } diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 16b5a56dd09..a9f08abf45d 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -55,6 +55,13 @@ class DatabaseStorageController implements EntityStorageControllerInterface { */ protected $entityFieldInfo; + /** + * Static cache of field definitions per bundle. + * + * @var array + */ + protected $fieldDefinitions; + /** * Additional arguments to pass to hook_TYPE_load(). * @@ -687,15 +694,17 @@ class DatabaseStorageController implements EntityStorageControllerInterface { } } - $definitions = $this->entityFieldInfo['definitions']; + $bundle = !empty($constraints['bundle']) ? $constraints['bundle'] : FALSE; // Add in per-bundle properties. - // @todo: Should this be statically cached as well? - if (!empty($constraints['bundle']) && isset($this->entityFieldInfo['bundle map'][$constraints['bundle']])) { - $definitions += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$constraints['bundle']])); - } + if (!isset($this->fieldDefinitions[$bundle])) { + $this->fieldDefinitions[$bundle] = $this->entityFieldInfo['definitions']; - return $definitions; + if ($bundle && isset($this->entityFieldInfo['bundle map'][$constraints['bundle']])) { + $this->fieldDefinitions[$bundle] += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$constraints['bundle']])); + } + } + return $this->fieldDefinitions[$bundle]; } /** diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 10a7a77e722..5b538c895eb 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -77,20 +77,17 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { * A new entity object. */ public function create(array $values) { - $entity = new $this->entityClass(array(), $this->entityType); + // We have to determine the bundle first. + $bundle = $this->bundleKey ? $values[$this->bundleKey] : FALSE; + $entity = new $this->entityClass(array(), $this->entityType, $bundle); - // Make sure to set the bundle first. - if ($this->bundleKey) { - $entity->{$this->bundleKey} = $values[$this->bundleKey]; - unset($values[$this->bundleKey]); - } // Set all other given values. foreach ($values as $name => $value) { $entity->$name = $value; } // Assign a new UUID if there is none yet. - if ($this->uuidKey && !isset($entity->{$this->uuidKey})) { + if ($this->uuidKey && !isset($entity->{$this->uuidKey}->value)) { $uuid = new Uuid(); $entity->{$this->uuidKey}->value = $uuid->generate(); } @@ -109,19 +106,20 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { // Attach fields. if ($this->entityInfo['fieldable']) { + // Prepare BC compatible entities for field API. + $bc_entities = array(); + foreach ($queried_entities as $key => $entity) { + $bc_entities[$key] = $entity->getBCEntity(); + } + if ($load_revision) { - field_attach_load_revision($this->entityType, $queried_entities); + field_attach_load_revision($this->entityType, $bc_entities); } else { - field_attach_load($this->entityType, $queried_entities); + field_attach_load($this->entityType, $bc_entities); } } - // Loading is finished, so disable compatibility mode now. - foreach ($queried_entities as $entity) { - $entity->setCompatibilityMode(FALSE); - } - // Call hook_entity_load(). foreach (module_implements('entity_load') as $module) { $function = $module . '_entity_load'; @@ -150,13 +148,14 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { protected function mapFromStorageRecords(array $records, $load_revision = FALSE) { foreach ($records as $id => $record) { - $entity = new $this->entityClass(array(), $this->entityType); - $entity->setCompatibilityMode(TRUE); - + $values = array(); foreach ($record as $name => $value) { - $entity->{$name}[LANGUAGE_DEFAULT][0]['value'] = $value; + // Avoid unnecessary array hierarchies to save memory. + $values[$name][LANGUAGE_DEFAULT] = $value; } - $records[$id] = $entity; + $bundle = $this->bundleKey ? $record->{$this->bundleKey} : FALSE; + // Turn the record into an entity class. + $records[$id] = new $this->entityClass($values, $this->entityType, $bundle); } return $records; } @@ -179,11 +178,6 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { // Create the storage record to be saved. $record = $this->maptoStorageRecord($entity); - // Update the original values so that the compatibility mode works with - // the update values, what is required by field API attachers. - // @todo Once field API has been converted to use the Field API, move - // this after insert/update hooks. - $entity->updateOriginalValues(); if (!$entity->isNew()) { if ($entity->isDefaultRevision()) { @@ -215,6 +209,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $this->postSave($entity, FALSE); $this->invokeHook('insert', $entity); } + $entity->updateOriginalValues(); // Ignore slave server temporarily. db_ignore_slave(); @@ -270,8 +265,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { /** * Overrides DatabaseStorageController::invokeHook(). * - * Invokes field API attachers in compatibility mode and disables it - * afterwards. + * Invokes field API attachers with a BC entity. */ protected function invokeHook($hook, EntityInterface $entity) { $function = 'field_attach_' . $hook; @@ -281,9 +275,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $function = 'field_attach_delete_revision'; } if (!empty($this->entityInfo['fieldable']) && function_exists($function)) { - $entity->setCompatibilityMode(TRUE); - $function($this->entityType, $entity); - $entity->setCompatibilityMode(FALSE); + $function($this->entityType, $entity->getBCEntity()); } // Invoke the hook. @@ -311,7 +303,6 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { foreach ($this->entityInfo['schema_fields_sql']['revision_table'] as $name) { $record->$name = $entity->$name->value; } - return $record; } } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 1b511e1002f..adbb0476b70 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -9,6 +9,7 @@ namespace Drupal\Core\Entity; use Drupal\Component\Uuid\Uuid; use Drupal\Core\Language\Language; +use Drupal\Core\TypedData\ContextAwareInterface; use IteratorAggregate; /** @@ -379,4 +380,53 @@ class Entity implements IteratorAggregate, EntityInterface { return array(); } + /** + * Implements Drupal\Core\Entity\EntityInterface::getBCEntity(). + */ + public function getBCEntity() { + return $this; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::getOriginalEntity(). + */ + public function getOriginalEntity() { + return $this; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getName(). + */ + public function getName() { + return NULL; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getRoot(). + */ + public function getRoot() { + return $this; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getPropertyPath(). + */ + public function getPropertyPath() { + return ''; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getParent(). + */ + public function getParent() { + return NULL; + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::setContext(). + */ + public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { + // As entities are always the root of the tree, we do not need to set any + // context. + } } diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php new file mode 100644 index 00000000000..9f7764933fa --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php @@ -0,0 +1,368 @@ +values and $entity->fields + * to references itself as well, which is problematic during __clone() (this is + * something that would not be easy to fix as an unset() on the variable is + * problematic with the magic getter/setter then). + */ +class EntityBCDecorator implements IteratorAggregate, EntityInterface { + + /** + * The EntityInterface object being decorated. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $decorated; + + /** + * Constructs a Drupal\Core\Entity\EntityCompatibilityDecorator object. + * + * @param \Drupal\Core\Entity\EntityInterface $decorated + * The decorated entity. + */ + function __construct(EntityNG $decorated) { + $this->decorated = $decorated; + } + + /** + * Overrides Entity::getOriginalEntity(). + */ + public function getOriginalEntity() { + return $this->decorated; + } + + /** + * Overrides Entity::getBCEntity(). + */ + public function getBCEntity() { + return $this; + } + + /** + * Implements the magic method for getting object properties. + * + * Directly accesses the plain field values, as done in Drupal 7. + */ + public function &__get($name) { + // Make sure $this->decorated->values reflects the latest values. + if (!empty($this->decorated->fields[$name])) { + foreach ($this->decorated->fields[$name] as $langcode => $field) { + $this->decorated->values[$name][$langcode] = $field->getValue(); + } + // Values might be changed by reference, so remove the field object to + // avoid them becoming out of sync. + unset($this->decorated->fields[$name]); + } + // Allow accessing field values in entity default languages other than + // LANGUAGE_DEFAULT by mapping the values to LANGUAGE_DEFAULT. + $langcode = $this->decorated->language()->langcode; + if ($langcode != LANGUAGE_DEFAULT && isset($this->decorated->values[$name][LANGUAGE_DEFAULT]) && !isset($this->decorated->values[$name][$langcode])) { + $this->decorated->values[$name][$langcode] = &$this->decorated->values[$name][LANGUAGE_DEFAULT]; + } + + if (!isset($this->decorated->values[$name])) { + $this->decorated->values[$name] = NULL; + } + return $this->decorated->values[$name]; + } + + /** + * Implements the magic method for setting object properties. + * + * Directly writes to the plain field values, as done by Drupal 7. + */ + public function __set($name, $value) { + if (is_array($value) && $definition = $this->decorated->getPropertyDefinition($name)) { + // If field API sets a value with a langcode in entity language, move it + // to LANGUAGE_DEFAULT. + foreach ($value as $langcode => $data) { + if ($langcode != LANGUAGE_DEFAULT && $langcode == $this->decorated->language()->langcode) { + $value[LANGUAGE_DEFAULT] = $data; + unset($value[$langcode]); + } + } + } + $this->decorated->values[$name] = $value; + unset($this->decorated->fields[$name]); + } + + /** + * Implements the magic method for isset(). + */ + public function __isset($name) { + $value = $this->__get($name); + return isset($value); + } + + /** + * Implements the magic method for unset(). + */ + public function __unset($name) { + $value = &$this->__get($name); + $value = array(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) { + return $this->decorated->access($account); + } + + /** + * Forwards the call to the decorated entity. + */ + public function get($property_name) { + return $this->decorated->get($property_name); + } + + /** + * Forwards the call to the decorated entity. + */ + public function set($property_name, $value) { + return $this->decorated->set($property_name, $value); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getProperties($include_computed = FALSE) { + return $this->decorated->getProperties($include_computed); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getPropertyValues() { + return $this->decorated->getPropertyValues(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function setPropertyValues($values) { + return $this->decorated->setPropertyValues($values); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getPropertyDefinition($name) { + return $this->decorated->getPropertyDefinition($name); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getPropertyDefinitions() { + return $this->decorated->getPropertyDefinitions(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function isEmpty() { + return $this->decorated->isEmpty(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getIterator() { + return $this->decorated->getIterator(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function id() { + return $this->decorated->id(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function uuid() { + return $this->decorated->uuid(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function isNew() { + return $this->decorated->isNew(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function isNewRevision() { + return $this->decorated->isNewRevision(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function setNewRevision($value = TRUE) { + return $this->decorated->setNewRevision($value); + } + + /** + * Forwards the call to the decorated entity. + */ + public function enforceIsNew($value = TRUE) { + return $this->decorated->enforceIsNew($value); + } + + /** + * Forwards the call to the decorated entity. + */ + public function entityType() { + return $this->decorated->entityType(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function bundle() { + return $this->decorated->bundle(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function label($langcode = NULL) { + return $this->decorated->label($langcode); + } + + /** + * Forwards the call to the decorated entity. + */ + public function uri() { + return $this->decorated->uri(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function save() { + return $this->decorated->save(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function delete() { + return $this->decorated->delete(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function createDuplicate() { + return $this->decorated->createDuplicate(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function entityInfo() { + return $this->decorated->entityInfo(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getRevisionId() { + return $this->decorated->getRevisionId(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function isDefaultRevision($new_value = NULL) { + return $this->decorated->isDefaultRevision($new_value); + } + + /** + * Forwards the call to the decorated entity. + */ + public function language() { + return $this->decorated->language(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getTranslationLanguages($include_default = TRUE) { + return $this->decorated->getTranslationLanguages($include_default); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getTranslation($langcode, $strict = TRUE) { + return $this->decorated->getTranslation($langcode, $strict); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getName() { + return $this->decorated->getName(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getRoot() { + return $this->decorated->getRoot(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getPropertyPath() { + return $this->decorated->getPropertyPath(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getParent() { + return $this->decorated->getParent(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { + $this->decorated->setContext($name, $parent); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getExportProperties() { + $this->decorated->getExportProperties(); + } +} diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php index 812a192bdef..51bc911bb71 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php +++ b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php @@ -23,9 +23,7 @@ class EntityFormControllerNG extends EntityFormController { // entity properties. $info = $entity->entityInfo(); if (!empty($info['fieldable'])) { - $entity->setCompatibilityMode(TRUE); - field_attach_form($entity->entityType(), $entity, $form, $form_state, $this->getFormLangcode($form_state)); - $entity->setCompatibilityMode(FALSE); + field_attach_form($entity->entityType(), $entity->getBCEntity(), $form, $form_state, $this->getFormLangcode($form_state)); } return $form; } @@ -40,9 +38,7 @@ class EntityFormControllerNG extends EntityFormController { $info = $entity->entityInfo(); if (!empty($info['fieldable'])) { - $entity->setCompatibilityMode(TRUE); - field_attach_form_validate($entity->entityType(), $entity, $form, $form_state); - $entity->setCompatibilityMode(FALSE); + field_attach_form_validate($entity->entityType(), $entity->getBCEntity(), $form, $form_state); } // @todo Remove this. @@ -82,9 +78,7 @@ class EntityFormControllerNG extends EntityFormController { // Copy field values to the entity. if ($info['fieldable']) { - $entity->setCompatibilityMode(TRUE); - field_attach_submit($entity_type, $entity, $form, $form_state); - $entity->setCompatibilityMode(FALSE); + field_attach_submit($entity_type, $entity->getBCEntity(), $form, $form_state); } return $entity; } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index a3e4a622dfb..94ecc3bd5dc 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\TypedData\AccessibleInterface; +use Drupal\Core\TypedData\ContextAwareInterface; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TranslatableInterface; @@ -17,7 +18,7 @@ use Drupal\Core\TypedData\TranslatableInterface; * When implementing this interface which extends Traversable, make sure to list * IteratorAggregate or Iterator before this interface in the implements clause. */ -interface EntityInterface extends ComplexDataInterface, AccessibleInterface, TranslatableInterface { +interface EntityInterface extends ContextAwareInterface, ComplexDataInterface, AccessibleInterface, TranslatableInterface { /** * Returns the entity identifier (the entity's machine name or numeric ID). @@ -190,4 +191,23 @@ interface EntityInterface extends ComplexDataInterface, AccessibleInterface, Tra */ public function getExportProperties(); + /** + * Gets a backward compatibility decorator entity. + * + * @return \Drupal\Core\Entity\EntityInterface + * The backward compatible entity. + * + * @see \Drupal\Core\Entity\EntityInterface::getOriginalEntity() + */ + public function getBCEntity(); + + /** + * Removes any possible (backward compatibility) decorator in use. + * + * @return \Drupal\Core\Entity\EntityInterface + * The original, not backward compatible entity object. + * + * @see \Drupal\Core\Entity\EntityInterface::getBCEntity() + */ + public function getOriginalEntity(); } diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index c486aa8ddcf..7df5b1e787f 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -25,6 +25,13 @@ use InvalidArgumentException; */ class EntityNG extends Entity { + /** + * Local cache holding the value of the bundle field. + * + * @var string + */ + protected $bundle; + /** * The plain data values of the contained fields. * @@ -50,22 +57,69 @@ class EntityNG extends Entity { protected $fields = array(); /** - * Whether the entity is in pre-Entity Field API compatibility mode. + * An instance of the backward compatibility decorator. * - * If set to TRUE, field values are written directly to $this->values, thus - * must be plain property values keyed by language code. This must be enabled - * when calling legacy field API attachers. - * - * @var bool + * @var EntityBCDecorator */ - protected $compatibilityMode = FALSE; - + protected $bcEntity; /** - * Overrides Entity::id(). + * Local cache for field definitions. + * + * @see self::getPropertyDefinitions() + * + * @var array + */ + protected $fieldDefinitions; + + /** + * Overrides Entity::__construct(). + */ + public function __construct(array $values, $entity_type, $bundle = FALSE) { + $this->entityType = $entity_type; + $this->bundle = $bundle ? $bundle : $this->entityType; + foreach ($values as $key => $value) { + $this->values[$key] = $value; + } + $this->init(); + } + + /** + * Gets the typed data type of the entity. + * + * @return string + */ + public function getType() { + return $this->entityType; + } + + /** + * Initialize the object. Invoked upon construction and wake up. + */ + protected function init() { + // We unset all defined properties, so magic getters apply. + unset($this->langcode); + } + + /** + * Magic __wakeup() implemenation. + */ + public function __wakeup() { + $this->init(); + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::id(). */ public function id() { - return $this->get('id')->value; + return $this->id->value; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::bundle(). + */ + public function bundle() { + return $this->bundle; } /** @@ -105,9 +159,11 @@ class EntityNG extends Entity { $this->fields[$property_name][$langcode] = $this->getTranslatedField($property_name, LANGUAGE_DEFAULT); } else { - $value = isset($this->values[$property_name][$langcode]) ? $this->values[$property_name][$langcode] : NULL; - $context = array('parent' => $this, 'name' => $property_name); - $this->fields[$property_name][$langcode] = typed_data()->create($definition, $value, $context); + $value = NULL; + if (isset($this->values[$property_name][$langcode])) { + $value = $this->values[$property_name][$langcode]; + } + $this->fields[$property_name][$langcode] = typed_data()->getPropertyInstance($this, $property_name, $value); } } return $this->fields[$property_name][$langcode]; @@ -144,18 +200,14 @@ class EntityNG extends Entity { * Implements ComplexDataInterface::getPropertyDefinition(). */ public function getPropertyDefinition($name) { - // First try getting property definitions which apply to all entities of - // this type. Then if this fails add in definitions of optional properties - // as well. That way we can use property definitions of base properties - // when determining the optional properties of an entity. - $definitions = entity_get_controller($this->entityType)->getFieldDefinitions(array()); - - if (isset($definitions[$name])) { - return $definitions[$name]; + if (!isset($this->fieldDefinitions)) { + $this->getPropertyDefinitions(); } - // Add in optional properties if any. - if ($definitions = $this->getPropertyDefinitions()) { - return isset($definitions[$name]) ? $definitions[$name] : FALSE; + if (isset($this->fieldDefinitions[$name])) { + return $this->fieldDefinitions[$name]; + } + else { + return FALSE; } } @@ -163,10 +215,13 @@ class EntityNG extends Entity { * Implements ComplexDataInterface::getPropertyDefinitions(). */ public function getPropertyDefinitions() { - return entity_get_controller($this->entityType)->getFieldDefinitions(array( - 'entity type' => $this->entityType, - 'bundle' => $this->bundle(), - )); + if (!isset($this->fieldDefinitions)) { + $this->fieldDefinitions = entity_get_controller($this->entityType)->getFieldDefinitions(array( + 'entity type' => $this->entityType, + 'bundle' => $this->bundle, + )); + } + return $this->fieldDefinitions; } /** @@ -208,7 +263,13 @@ class EntityNG extends Entity { * Implements TranslatableInterface::language(). */ public function language() { - return $this->get('langcode')->language; + $language = $this->get('langcode')->language; + if (!$language) { + // Make sure we return a proper language object. + // @todo Refactor this, see: http://drupal.org/node/1834542. + $language = language_default(); + } + return $language; } /** @@ -236,6 +297,9 @@ class EntityNG extends Entity { $fields[$name] = $this->getTranslatedField($name, $langcode); } } + // @todo: Add a way to get the definition of a translation to the + // TranslatableInterface and leverage TypeDataManager::getPropertyInstance + // also. $translation_definition = array( 'type' => 'entity_translation', 'constraints' => array( @@ -243,11 +307,11 @@ class EntityNG extends Entity { 'bundle' => $this->bundle(), ), ); - $translation = typed_data()->create($translation_definition, $fields, array( - 'parent' => $this, - 'name' => $langcode, - )); + $translation = typed_data()->create($translation_definition, $fields); $translation->setStrictMode($strict); + if ($translation instanceof ContextAwareInterface) { + $translation->setContext('@' . $langcode, $this); + } return $translation; } @@ -256,89 +320,101 @@ class EntityNG extends Entity { */ public function getTranslationLanguages($include_default = TRUE) { $translations = array(); - // Build an array with the translation langcodes set as keys. + // Build an array with the translation langcodes set as keys. Empty + // translations must be filtered out. foreach ($this->getProperties() as $name => $property) { - if (isset($this->values[$name])) { - $translations += $this->values[$name]; + foreach ($this->fields[$name] as $langcode => $field) { + if (!$field->isEmpty()) { + $translations[$langcode] = TRUE; + } + if (isset($this->values[$name])) { + foreach ($this->values[$name] as $langcode => $values) { + if ($values && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) { + $translations[$langcode] = TRUE; + } + } + } } - $translations += $this->fields[$name]; } unset($translations[LANGUAGE_DEFAULT]); if ($include_default) { $translations[$this->language()->langcode] = TRUE; } - - // Now get languages based upon translation langcodes. Empty languages must - // be filtered out as they concern empty/unset properties. - $languages = array_intersect_key(language_list(LANGUAGE_ALL), array_filter($translations)); - return $languages; + // Now get languages based upon translation langcodes. + return array_intersect_key(language_list(LANGUAGE_ALL), $translations); } /** - * Enables or disable the compatibility mode. + * Overrides Entity::translations(). * - * @param bool $enabled - * Whether to enable the mode. - * - * @see EntityNG::compatibilityMode + * @todo: Remove once Entity::translations() gets removed. */ - public function setCompatibilityMode($enabled) { - $this->compatibilityMode = (bool) $enabled; - if ($enabled) { - $this->updateOriginalValues(); - $this->fields = array(); + public function translations() { + return $this->getTranslationLanguages(FALSE); + } + + /** + * Overrides Entity::getBCEntity(). + */ + public function getBCEntity() { + if (!isset($this->bcEntity)) { + $this->bcEntity = new EntityBCDecorator($this); } - } - - /** - * Returns whether the compatibility mode is active. - */ - public function getCompatibilityMode() { - return $this->compatibilityMode; + return $this->bcEntity; } /** * Updates the original values with the interim changes. - * - * Note: This should be called by the storage controller during a save - * operation. */ public function updateOriginalValues() { - foreach ($this->fields as $name => $properties) { - foreach ($properties as $langcode => $property) { - $this->values[$name][$langcode] = $property->getValue(); + if (!$this->fields) { + return; + } + foreach ($this->getPropertyDefinitions() as $name => $definition) { + if (empty($definition['computed']) && !empty($this->fields[$name])) { + foreach ($this->fields[$name] as $langcode => $field) { + $this->values[$name][$langcode] = $field->getValue(); + } } } } /** - * Magic getter: Gets the property in default language. + * Implements the magic method for setting object properties. * + * Uses default language always. * For compatibility mode to work this must return a reference. */ public function &__get($name) { - if ($this->compatibilityMode) { - if (!isset($this->values[$name])) { - $this->values[$name] = NULL; - } - return $this->values[$name]; - } if (isset($this->fields[$name][LANGUAGE_DEFAULT])) { return $this->fields[$name][LANGUAGE_DEFAULT]; } - if ($this->getPropertyDefinition($name)) { - $return = $this->get($name); + // Inline getPropertyDefinition() to speed up things. + if (!isset($this->fieldDefinitions)) { + $this->getPropertyDefinitions(); + } + if (isset($this->fieldDefinitions[$name])) { + $return = $this->getTranslatedField($name, LANGUAGE_DEFAULT); return $return; } - if (!isset($this->$name)) { - $this->$name = NULL; + // Allow the EntityBCDecorator to directly access the values and fields. + // @todo: Remove once the EntityBCDecorator gets removed. + if ($name == 'values' || $name == 'fields') { + return $this->$name; } - return $this->$name; + // Else directly read/write plain values. That way, fields not yet converted + // to the entity field API can always be directly accessed. + if (!isset($this->values[$name])) { + $this->values[$name] = NULL; + } + return $this->values[$name]; } /** - * Magic getter: Sets the property in default language. + * Implements the magic method for setting object properties. + * + * Uses default language always. */ public function __set($name, $value) { // Support setting values via property objects. @@ -346,41 +422,40 @@ class EntityNG extends Entity { $value = $value->getValue(); } - if ($this->compatibilityMode) { - $this->values[$name] = $value; - } - elseif (isset($this->fields[$name][LANGUAGE_DEFAULT])) { + if (isset($this->fields[$name][LANGUAGE_DEFAULT])) { $this->fields[$name][LANGUAGE_DEFAULT]->setValue($value); } elseif ($this->getPropertyDefinition($name)) { - $this->get($name)->setValue($value); + $this->getTranslatedField($name, LANGUAGE_DEFAULT)->setValue($value); } + // Else directly read/write plain values. That way, fields not yet converted + // to the entity field API can always be directly accessed. else { - $this->$name = $value; + $this->values[$name] = $value; } } /** - * Magic method. + * Implements the magic method for isset(). */ public function __isset($name) { - if ($this->compatibilityMode) { - return isset($this->values[$name]); + if ($this->getPropertyDefinition($name)) { + return $this->get($name)->getValue() !== NULL; } - elseif ($this->getPropertyDefinition($name)) { - return $this->get($name)->valueIsSet(); + else { + return isset($this->values[$name]); } } /** - * Magic method. + * Implements the magic method for unset. */ public function __unset($name) { - if ($this->compatibilityMode) { - unset($this->values[$name]); + if ($this->getPropertyDefinition($name)) { + $this->get($name)->setValue(NULL); } - elseif ($this->getPropertyDefinition($name)) { - $this->get($name)->unsetValue(); + else { + unset($this->values[$name]); } } @@ -404,13 +479,30 @@ class EntityNG extends Entity { * Implements a deep clone. */ public function __clone() { + $this->bcEntity = NULL; + foreach ($this->fields as $name => $properties) { foreach ($properties as $langcode => $property) { $this->fields[$name][$langcode] = clone $property; if ($property instanceof ContextAwareInterface) { - $this->fields[$name][$langcode]->setParent($this); + $this->fields[$name][$langcode]->setContext($name, $this); } } } } + + /** + * Overrides Entity::label() to access the label field with the new API. + */ + public function label($langcode = NULL) { + $label = NULL; + $entity_info = $this->entityInfo(); + if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) { + $label = $entity_info['label_callback']($this->entityType, $this, $langcode); + } + elseif (!empty($entity_info['entity_keys']['label']) && isset($this->{$entity_info['entity_keys']['label']})) { + $label = $this->{$entity_info['entity_keys']['label']}->value; + } + return $label; + } } diff --git a/core/lib/Drupal/Core/Entity/EntityRenderController.php b/core/lib/Drupal/Core/Entity/EntityRenderController.php index eb3147e40ab..f942ae0230f 100644 --- a/core/lib/Drupal/Core/Entity/EntityRenderController.php +++ b/core/lib/Drupal/Core/Entity/EntityRenderController.php @@ -68,23 +68,8 @@ class EntityRenderController implements EntityRenderControllerInterface { // Prepare and build field content, grouped by view mode. foreach ($view_modes as $view_mode => $view_mode_entities) { - $call_prepare = array(); - // To ensure hooks are only run once per entity, check for an - // entity_view_prepared flag and only process relevant entities. - foreach ($view_mode_entities as $entity) { - if (empty($entity->entity_view_prepared) || $entity->entity_view_prepared != $view_mode) { - // Add this entity to the items to be prepared. - $call_prepare[$entity->id()] = $entity; - - // Mark this item as prepared for this view mode. - $entity->entity_view_prepared = $view_mode; - } - } - - if (!empty($call_prepare)) { - field_attach_prepare_view($this->entityType, $call_prepare, $displays[$view_mode], $langcode); - module_invoke_all('entity_prepare_view', $call_prepare, $this->entityType); - } + field_attach_prepare_view($this->entityType, $view_mode_entities, $displays[$view_mode], $langcode); + module_invoke_all('entity_prepare_view', $view_mode_entities, $this->entityType); foreach ($view_mode_entities as $entity) { $entity->content += field_attach_view($this->entityType, $entity, $displays[$view_mode][$entity->bundle()], $langcode); diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php index 5c71aa3258c..a1fdc6ba3a8 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php @@ -7,8 +7,7 @@ namespace Drupal\Core\Entity\Field; -use Drupal\Core\TypedData\Type\TypedData; -use Drupal\Core\TypedData\ComplexDataInterface; +use Drupal\Core\TypedData\ContextAwareTypedData; use Drupal\Core\TypedData\ContextAwareInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\user; @@ -24,21 +23,7 @@ use InvalidArgumentException; * * @see \Drupal\Core\Entity\Field\FieldItemInterface */ -abstract class FieldItemBase extends TypedData implements IteratorAggregate, FieldItemInterface { - - /** - * The item delta or name. - * - * @var integer - */ - protected $name; - - /** - * The parent entity field. - * - * @var \Drupal\Core\Entity\Field\FieldInterface - */ - protected $parent; +abstract class FieldItemBase extends ContextAwareTypedData implements IteratorAggregate, FieldItemInterface { /** * The array of properties. @@ -52,10 +37,10 @@ abstract class FieldItemBase extends TypedData implements IteratorAggregate, Fie protected $properties = array(); /** - * Implements TypedDataInterface::__construct(). + * Overrides ContextAwareTypedData::__construct(). */ - public function __construct(array $definition) { - $this->definition = $definition; + public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) { + parent::__construct($definition, $name, $parent); // Initialize all property objects, but postpone the creating of computed // properties to a second step. That way computed properties can safely get @@ -63,17 +48,15 @@ abstract class FieldItemBase extends TypedData implements IteratorAggregate, Fie $step2 = array(); foreach ($this->getPropertyDefinitions() as $name => $definition) { if (empty($definition['computed'])) { - $context = array('name' => $name, 'parent' => $this); - $this->properties[$name] = typed_data()->create($definition, NULL, $context); + $this->properties[$name] = typed_data()->getPropertyInstance($this, $name); } else { - $step2[$name] = $definition; + $step2[] = $name; } } - foreach ($step2 as $name => $definition) { - $context = array('name' => $name, 'parent' => $this); - $this->properties[$name] = typed_data()->create($definition, NULL, $context); + foreach ($step2 as $name) { + $this->properties[$name] = typed_data()->getPropertyInstance($this, $name); } } @@ -96,14 +79,19 @@ abstract class FieldItemBase extends TypedData implements IteratorAggregate, Fie */ public function setValue($values) { // Treat the values as property value of the first property, if no array is - // given and we only have one property. - if (!is_array($values) && count($this->properties) == 1) { + // given. + if (!is_array($values)) { $keys = array_keys($this->properties); $values = array($keys[0] => $values); } foreach ($this->properties as $name => $property) { - $property->setValue(isset($values[$name]) ? $values[$name] : NULL); + if (isset($values[$name])) { + $property->setValue($values[$name]); + } + else { + $property->setValue(NULL); + } } // @todo: Throw an exception for invalid values once conversion is // totally completed. @@ -178,35 +166,6 @@ abstract class FieldItemBase extends TypedData implements IteratorAggregate, Fie } } - /** - * Implements ContextAwareInterface::getName(). - */ - public function getName() { - return $this->name; - } - - /** - * Implements ContextAwareInterface::setName(). - */ - public function setName($name) { - $this->name = $name; - } - - /** - * Implements ContextAwareInterface::getParent(). - * - * @return \Drupal\Core\Entity\Field\FieldInterface - */ - public function getParent() { - return $this->parent; - } - - /** - * Implements ContextAwareInterface::setParent(). - */ - public function setParent($parent) { - $this->parent = $parent; - } /** * Implements ComplexDataInterface::getProperties(). @@ -249,7 +208,12 @@ abstract class FieldItemBase extends TypedData implements IteratorAggregate, Fie */ public function getPropertyDefinition($name) { $definitions = $this->getPropertyDefinitions(); - return isset($definitions[$name]) ? $definitions[$name] : FALSE; + if (isset($definitions[$name])) { + return $definitions[$name]; + } + else { + return FALSE; + } } /** @@ -271,7 +235,7 @@ abstract class FieldItemBase extends TypedData implements IteratorAggregate, Fie foreach ($this->properties as $name => $property) { $this->properties[$name] = clone $property; if ($property instanceof ContextAwareInterface) { - $this->properties[$name]->setParent($this); + $this->properties[$name]->setContext($name, $this); } } } diff --git a/core/lib/Drupal/Core/Entity/Field/Type/BooleanItem.php b/core/lib/Drupal/Core/Entity/Field/Type/BooleanItem.php index 131d0a212ac..39f61b5f440 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/BooleanItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/BooleanItem.php @@ -15,7 +15,7 @@ use Drupal\Core\Entity\Field\FieldItemBase; class BooleanItem extends FieldItemBase { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * diff --git a/core/lib/Drupal/Core/Entity/Field/Type/DateItem.php b/core/lib/Drupal/Core/Entity/Field/Type/DateItem.php index eb1ab12fbf0..b53b97f3fc4 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/DateItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/DateItem.php @@ -15,7 +15,7 @@ use Drupal\Core\Entity\Field\FieldItemBase; class DateItem extends FieldItemBase { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php index a60e65e62ec..6c4a6fce78d 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php @@ -13,13 +13,13 @@ use InvalidArgumentException; /** * Defines the 'entityreference_field' entity field item. * - * Required settings (below the definition's 'settings' key) are: - * - entity type: The entity type to reference. + * Available settings (below the definition's 'settings' key) are: + * - entity type: (required) The entity type to reference. */ class EntityReferenceItem extends FieldItemBase { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * @@ -71,8 +71,11 @@ class EntityReferenceItem extends FieldItemBase { if (isset($values['value'])) { $this->properties['value']->setValue($values['value']); } + elseif (isset($values['entity'])) { + $this->properties['entity']->setValue($values['entity']); + } else { - $this->properties['entity']->setValue(isset($values['entity']) ? $values['entity'] : NULL); + $this->properties['entity']->setValue(NULL); } unset($values['entity'], $values['value']); if ($values) { diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php index 280fca9a5ab..b4fe089ffeb 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php @@ -7,11 +7,9 @@ namespace Drupal\Core\Entity\Field\Type; -use Drupal\Core\TypedData\Type\TypedData; use Drupal\Core\TypedData\AccessibleInterface; use Drupal\Core\TypedData\ComplexDataInterface; -use Drupal\Core\TypedData\ContextAwareInterface; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\ContextAwareTypedData; use ArrayIterator; use IteratorAggregate; use InvalidArgumentException; @@ -19,7 +17,7 @@ use InvalidArgumentException; /** * Makes translated entity properties available via the Field API. */ -class EntityTranslation extends TypedData implements IteratorAggregate, AccessibleInterface, ComplexDataInterface, ContextAwareInterface { +class EntityTranslation extends ContextAwareTypedData implements IteratorAggregate, AccessibleInterface, ComplexDataInterface { /** * The array of translated properties, each being an instance of @@ -29,20 +27,6 @@ class EntityTranslation extends TypedData implements IteratorAggregate, Accessib */ protected $properties = array(); - /** - * The language code of the translation. - * - * @var string - */ - protected $langcode; - - /** - * The parent entity. - * - * @var \Drupal\Core\Entity\EntityInterface - */ - protected $parent; - /** * Whether the entity translation acts in strict mode. * @@ -72,37 +56,6 @@ class EntityTranslation extends TypedData implements IteratorAggregate, Accessib $this->strict = $strict; } - /** - * Implements ContextAwareInterface::getName(). - */ - public function getName() { - // The name of the translation is the language code. - return $this->langcode; - } - - /** - * Implements ContextAwareInterface::setName(). - */ - public function setName($name) { - // The name of the translation is the language code. - $this->langcode = $name; - } - - /** - * Implements ContextAwareInterface::getParent(). - * - * @return \Drupal\Core\Entity\EntityInterface - */ - public function getParent() { - return $this->parent; - } - - /** - * Implements ContextAwareInterface::setParent(). - */ - public function setParent($parent) { - $this->parent = $parent; - } /** * Implements TypedDataInterface::getValue(). */ @@ -186,7 +139,12 @@ class EntityTranslation extends TypedData implements IteratorAggregate, Accessib */ public function getPropertyDefinition($name) { $definitions = $this->getPropertyDefinitions(); - return isset($definitions[$name]) ? $definitions[$name] : FALSE; + if (isset($definitions[$name])) { + return $definitions[$name]; + } + else { + return FALSE; + } } /** @@ -235,7 +193,10 @@ class EntityTranslation extends TypedData implements IteratorAggregate, Accessib */ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) { $method = $operation . 'Access'; - return entity_access_controller($this->parent->entityType())->$method($this->parent, $this->langcode, $account); + // @todo Add a way to set and get the langcode so that's more obvious what + // we're doing here. + $langocde = substr($this->getName(), 1); + return entity_access_controller($this->parent->entityType())->$method($this->parent, $langocde, $account); } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php index 97449e75807..ffc9fd10c9c 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php @@ -8,9 +8,9 @@ namespace Drupal\Core\Entity\Field\Type; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\TypedData\Type\TypedData; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\ContextAwareInterface; +use Drupal\Core\TypedData\ContextAwareTypedData; use Drupal\Core\TypedData\TypedDataInterface; use ArrayIterator; use IteratorAggregate; @@ -36,21 +36,7 @@ use InvalidArgumentException; * - id source: If used as computed property, the ID property used to load * the entity object. */ -class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataInterface, ContextAwareInterface { - - /** - * The name. - * - * @var string - */ - protected $name; - - /** - * The parent data structure. - * - * @var mixed - */ - protected $parent; +class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate, ComplexDataInterface { /** * The referenced entity type. @@ -67,10 +53,10 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI protected $id; /** - * Implements TypedDataInterface::__construct(). + * Overrides ContextAwareTypedData::__construct(). */ - public function __construct(array $definition) { - $this->definition = $definition + array('constraints' => array()); + public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) { + parent::__construct($definition, $name, $parent); $this->entityType = isset($this->definition['constraints']['entity type']) ? $this->definition['constraints']['entity type'] : NULL; } @@ -120,8 +106,10 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI * Implements TypedDataInterface::getString(). */ public function getString() { - $entity = $this->getValue(); - return $entity ? $entity->label() : ''; + if ($entity = $this->getValue()) { + return $entity->label(); + } + return ''; } /** @@ -135,17 +123,20 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI * Implements IteratorAggregate::getIterator(). */ public function getIterator() { - $entity = $this->getValue(); - return $entity ? $entity->getIterator() : new ArrayIterator(array()); + if ($entity = $this->getValue()) { + return $entity->getIterator(); + } + return new ArrayIterator(array()); } /** * Implements ComplexDataInterface::get(). */ public function get($property_name) { - $entity = $this->getValue(); // @todo: Allow navigating through the tree without data as well. - return $entity ? $entity->get($property_name) : NULL; + if ($entity = $this->getValue()) { + return $entity->get($property_name); + } } /** @@ -155,40 +146,14 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI $this->get($property_name)->setValue($value); } - /** - * Implements ContextAwareInterface::getName(). - */ - public function getName() { - return $this->name; - } - - /** - * Implements ContextAwareInterface::setName(). - */ - public function setName($name) { - $this->name = $name; - } - - /** - * Implements ContextAwareInterface::getParent(). - */ - public function getParent() { - return $this->parent; - } - - /** - * Implements ContextAwareInterface::setParent(). - */ - public function setParent($parent) { - $this->parent = $parent; - } - /** * Implements ComplexDataInterface::getProperties(). */ public function getProperties($include_computed = FALSE) { - $entity = $this->getValue(); - return $entity ? $entity->getProperties($include_computed) : array(); + if ($entity = $this->getValue()) { + return $entity->getProperties($include_computed); + } + return array(); } /** @@ -196,7 +161,12 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI */ public function getPropertyDefinition($name) { $definitions = $this->getPropertyDefinitions(); - return isset($definitions[$name]) ? $definitions[$name] : FALSE; + if (isset($definitions[$name])) { + return $definitions[$name]; + } + else { + return FALSE; + } } /** @@ -211,8 +181,10 @@ class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataI * Implements ComplexDataInterface::getPropertyValues(). */ public function getPropertyValues() { - $entity = $this->getValue(); - return $entity ? $entity->getPropertyValues() : array(); + if ($entity = $this->getValue()) { + return $entity->getPropertyValues(); + } + return array(); } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index 0e7a982e64f..03d17e40e6b 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -8,9 +8,10 @@ namespace Drupal\Core\Entity\Field\Type; use Drupal\Core\Entity\Field\FieldInterface; -use Drupal\Core\TypedData\TypedDataInterface; -use Drupal\Core\TypedData\Type\TypedData; use Drupal\user\Plugin\Core\Entity\User; +use Drupal\Core\TypedData\ContextAwareInterface; +use Drupal\Core\TypedData\ContextAwareTypedData; +use Drupal\Core\TypedData\TypedDataInterface; use ArrayIterator; use IteratorAggregate; use InvalidArgumentException; @@ -26,21 +27,7 @@ use InvalidArgumentException; * * @see \Drupal\Core\Entity\Field\FieldInterface */ -class Field extends TypedData implements IteratorAggregate, FieldInterface { - - /** - * The entity field name. - * - * @var string - */ - protected $name; - - /** - * The parent entity. - * - * @var \Drupal\Core\Entity\EntityInterface - */ - protected $parent; +class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInterface { /** * Numerically indexed array of field items, implementing the @@ -51,21 +38,33 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { protected $list = array(); /** - * Flag to indicate if this field has been set. - * - * @var bool + * Overrides ContextAwareTypedData::__construct(). */ - protected $isset = FALSE; + public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) { + parent::__construct($definition, $name, $parent); + // Always initialize one empty item as usually that will be needed. That + // way prototypes created by + // \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() will + // already have one field item ready for use after cloning. + $this->list[0] = $this->createItem(0); + } /** * Implements TypedDataInterface::getValue(). */ public function getValue() { - $values = array(); - foreach ($this->list as $delta => $item) { - $values[$delta] = !$item->isEmpty() ? $item->getValue() : NULL; + if (isset($this->list)) { + $values = array(); + foreach ($this->list as $delta => $item) { + if (!$item->isEmpty()) { + $values[$delta] = $item->getValue(); + } + else { + $values[$delta] = NULL; + } + } + return $values; } - return $values; } /** @@ -75,19 +74,18 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { * An array of values of the field items. */ public function setValue($values) { - $this->isset = TRUE; - if (isset($values) && $values !== array()) { + if (!isset($values) || $values === array()) { + $this->list = $values; + } + else { // Support passing in only the value of the first item. if (!is_array($values) || !is_numeric(current(array_keys($values)))) { $values = array(0 => $values); } - if (!is_array($values)) { - throw new InvalidArgumentException("An entity field requires a numerically indexed array of items as value."); - } // Clear the values of properties for which no value has been passed. - foreach (array_diff_key($this->list, $values) as $delta => $item) { - unset($this->list[$delta]); + if (isset($this->list)) { + $this->list = array_intersect_key($this->list, $values); } // Set the values. @@ -96,24 +94,13 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { throw new InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.'); } elseif (!isset($this->list[$delta])) { - $this->list[$delta] = $this->createItem($value); + $this->list[$delta] = $this->createItem($delta, $value); } else { $this->list[$delta]->setValue($value); } } } - else { - $this->list = array(); - } - } - - /** - * Mark this field as not set. - */ - public function unsetValue() { - $this->list = array(); - $this->isset = FALSE; } /** @@ -123,10 +110,12 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { */ public function getString() { $strings = array(); - foreach ($this->list() as $item) { - $strings[] = $item->getString(); + if (isset($this->list)) { + foreach ($this->list() as $item) { + $strings[] = $item->getString(); + } + return implode(', ', array_filter($strings)); } - return implode(', ', array_filter($strings)); } /** @@ -140,14 +129,16 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { * Implements ArrayAccess::offsetExists(). */ public function offsetExists($offset) { - return array_key_exists($offset, $this->list); + return isset($this->list) && array_key_exists($offset, $this->list); } /** * Implements ArrayAccess::offsetUnset(). */ public function offsetUnset($offset) { - unset($this->list[$offset]); + if (isset($this->list)) { + unset($this->list[$offset]); + } } /** @@ -160,7 +151,7 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { // Allow getting not yet existing items as well. // @todo: Maybe add a public createItem() method in addition? elseif (!isset($this->list[$offset])) { - $this->list[$offset] = $this->createItem(); + $this->list[$offset] = $this->createItem($offset); } return $this->list[$offset]; } @@ -170,9 +161,15 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { * * @return \Drupal\Core\TypedData\TypedDataInterface */ - protected function createItem($value = NULL) { - $context = array('parent' => $this); - return typed_data()->create(array('list' => FALSE) + $this->definition, $value, $context); + protected function createItem($offset = 0, $value = NULL) { + return typed_data()->getPropertyInstance($this, $offset, $value); + } + + /** + * Implements ListInterface::getItemDefinition(). + */ + public function getItemDefinition() { + return array('list' => FALSE) + $this->definition; } /** @@ -199,44 +196,17 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { * Implements IteratorAggregate::getIterator(). */ public function getIterator() { - return new ArrayIterator($this->list); + if (isset($this->list)) { + return new ArrayIterator($this->list); + } + return new ArrayIterator(array()); } /** * Implements Countable::count(). */ public function count() { - return count($this->list); - } - - /** - * Implements ContextAwareInterface::getName(). - */ - public function getName() { - return $this->name; - } - - /** - * Implements ContextAwareInterface::setName(). - */ - public function setName($name) { - $this->name = $name; - } - - /** - * Implements ContextAwareInterface::getParent(). - * - * @return \Drupal\Core\Entity\EntityInterface - */ - public function getParent() { - return $this->parent; - } - - /** - * Implements ContextAwareInterface::setParent(). - */ - public function setParent($parent) { - $this->parent = $parent; + return isset($this->list) ? count($this->list) : 0; } /** @@ -257,7 +227,7 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { * Delegate. */ public function __get($property_name) { - return $this->offsetGet(0)->__get($property_name); + return $this->offsetGet(0)->get($property_name)->getValue(); } /** @@ -272,14 +242,13 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { */ public function __set($property_name, $value) { $this->offsetGet(0)->__set($property_name, $value); - $this->isset = TRUE; } /** * Delegate. */ public function __isset($property_name) { - return $this->isset && $this->offsetGet(0)->__isset($property_name); + return $this->offsetGet(0)->__isset($property_name); } /** @@ -293,29 +262,27 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { * Implements ListInterface::isEmpty(). */ public function isEmpty() { - foreach ($this->list as $item) { - if (!$item->isEmpty()) { - return FALSE; + if (isset($this->list)) { + foreach ($this->list as $item) { + if (!$item->isEmpty()) { + return FALSE; + } } } return TRUE; } - /** - * Determines if this field has been set. - * - * @return bool - */ - public function valueIsSet() { - return $this->isset; - } - /** * Implements a deep clone. */ public function __clone() { - foreach ($this->list as $delta => $property) { - $this->list[$delta] = clone $property; + if (isset($this->list)) { + foreach ($this->list as $delta => $property) { + $this->list[$delta] = clone $property; + if ($property instanceof ContextAwareInterface) { + $this->list[$delta]->setContext($delta, $this); + } + } } } diff --git a/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php b/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php index 1f4b4e6d009..2d1585f5121 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php @@ -15,7 +15,7 @@ use Drupal\Core\Entity\Field\FieldItemBase; class IntegerItem extends FieldItemBase { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * diff --git a/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php b/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php index f7b2a912d66..a6e548ef1fe 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php @@ -16,9 +16,9 @@ use InvalidArgumentException; class LanguageItem extends FieldItemBase { /** - * Array of property definitions of contained properties. + * Definitions of the contained properties. * - * @see PropertyEntityReferenceItem::getPropertyDefinitions() + * @see self::getPropertyDefinitions() * * @var array */ @@ -61,8 +61,11 @@ class LanguageItem extends FieldItemBase { if (!empty($values['value'])) { $this->properties['value']->setValue($values['value']); } + elseif (isset($values['language'])) { + $this->properties['language']->setValue($values['language']); + } else { - $this->properties['language']->setValue(isset($values['language']) ? $values['language'] : NULL); + $this->properties['language']->setValue(NULL); } unset($values['language'], $values['value']); if ($values) { diff --git a/core/lib/Drupal/Core/Entity/Field/Type/StringItem.php b/core/lib/Drupal/Core/Entity/Field/Type/StringItem.php index 7c8c57af196..6d98a520131 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/StringItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/StringItem.php @@ -15,7 +15,7 @@ use Drupal\Core\Entity\Field\FieldItemBase; class StringItem extends FieldItemBase { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php index 71364ce80b2..d6bffc6d638 100644 --- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php @@ -12,6 +12,9 @@ use Traversable; /** * Interface for complex data; i.e. data containing named and typed properties. * + * The name of a property has to be a valid PHP variable name, starting with + * an alphabetic character. + * * This is implemented by entities as well as by field item classes of * entities. * diff --git a/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php b/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php index 41c3031ff67..d2b2d3b587f 100644 --- a/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php +++ b/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\Core\TypedData\ContextAwareInterface. + * Contains \Drupal\Core\TypedData\ContextAwareInterface. */ namespace Drupal\Core\TypedData; @@ -22,37 +22,49 @@ interface ContextAwareInterface { */ public function getName(); - /** - * Sets the name of a property or item. - * - * This method is supposed to be used by the parental data structure in order - * to provide appropriate context only. - * - * @param string $name - * The name to set for a property or item. - * - * @see ContextAwareInterface::getName() - */ - public function setName($name); - /** * Returns the parent data structure; i.e. either complex data or a list. * - * @return Drupal\Core\TypedData\ComplexDataInterface|Drupal\Core\TypedData\ListInterface - * The parent data structure; either complex data or a list. + * @return \Drupal\Core\TypedData\ComplexDataInterface|\Drupal\Core\TypedData\ListInterface + * The parent data structure, either complex data or a list; or NULL if this + * is the root of the typed data tree. */ public function getParent(); /** - * Sets the parent of a property or item. + * Returns the root of the typed data tree. * - * This method is supposed to be used by the parental data structure in order - * to provide appropriate context only. + * Returns the root data for a tree of typed data objects; e.g. for an entity + * field item the root of the tree is its parent entity object. * - * @param mixed $parent - * The parent data structure; either complex data or a list. - * - * @see ContextAwareInterface::getParent() + * @return \Drupal\Core\TypedData\ComplexDataInterface|\Drupal\Core\TypedData\ListInterface + * The root data structure, either complex data or a list. */ - public function setParent($parent); + public function getRoot(); + + /** + * Returns the property path of the data. + * + * The trail of property names relative to the root of the typed data tree, + * separated by dots; e.g. 'field_text.0.format'. + * + * @return string + * The property path relative to the root of the typed tree, or an empty + * string if this is the root. + */ + public function getPropertyPath(); + + /** + * Sets the context of a property or item via a context aware parent. + * + * This method is supposed to be called by the factory only. + * + * @param string $name + * (optional) The name of the property or the delta of the list item, + * or NULL if it is the root of a typed data tree. Defaults to NULL. + * @param \Drupal\Core\TypedData\ContextAwareInterface $parent + * (optional) The parent object of the data property, or NULL if it is the + * root of a typed data tree. Defaults to NULL. + */ + public function setContext($name = NULL, ContextAwareInterface $parent = NULL); } diff --git a/core/lib/Drupal/Core/TypedData/ContextAwareTypedData.php b/core/lib/Drupal/Core/TypedData/ContextAwareTypedData.php new file mode 100644 index 00000000000..e7133244c73 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ContextAwareTypedData.php @@ -0,0 +1,109 @@ +definition = $definition; + $this->setContext($name, $parent); + } + + /** + * Implements ContextAwareInterface::setContext(). + */ + public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { + $this->parent = $parent; + $this->name = $name; + } + + /** + * Implements ContextAwareInterface::getName(). + */ + public function getName() { + return $this->name; + } + + /** + * Implements ContextAwareInterface::getRoot(). + */ + public function getRoot() { + if (isset($this->parent)) { + return $this->parent->getRoot(); + } + return $this; + } + + /** + * Implements ContextAwareInterface::getPropertyPath(). + */ + public function getPropertyPath() { + if (isset($this->parent)) { + $prefix = $this->parent->getPropertyPath(); + return (strlen($prefix) ? $prefix . '.' : '') . $this->name; + } + elseif (isset($this->name)) { + return $this->name; + } + return ''; + } + + /** + * Implements ContextAwareInterface::getParent(). + * + * @return \Drupal\Core\Entity\Field\FieldInterface + */ + public function getParent() { + return $this->parent; + } + + /** + * Implements TypedDataInterface::validate(). + */ + public function validate() { + // @todo: Implement validate() method. + } +} diff --git a/core/lib/Drupal/Core/TypedData/ListInterface.php b/core/lib/Drupal/Core/TypedData/ListInterface.php index 4ce6af01ab3..f6f906be3c6 100644 --- a/core/lib/Drupal/Core/TypedData/ListInterface.php +++ b/core/lib/Drupal/Core/TypedData/ListInterface.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\Core\TypedData\ListInterface. + * Contains \Drupal\Core\TypedData\ListInterface. */ namespace Drupal\Core\TypedData; @@ -29,4 +29,12 @@ interface ListInterface extends ArrayAccess, Countable, Traversable { * TRUE if the list is empty, FALSE otherwise. */ public function isEmpty(); + + /** + * Gets the definition of a contained item. + * + * @return array + * The data definition of contained items. + */ + public function getItemDefinition(); } diff --git a/core/lib/Drupal/Core/TypedData/Type/Binary.php b/core/lib/Drupal/Core/TypedData/Type/Binary.php index 7ec5e6b5ec0..303b71c986e 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Binary.php +++ b/core/lib/Drupal/Core/TypedData/Type/Binary.php @@ -7,7 +7,7 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; use InvalidArgumentException; /** @@ -17,7 +17,7 @@ use InvalidArgumentException; * http://php.net/manual/en/language.types.resource.php. For setting the value * a PHP file resource or a (absolute) stream resource URI may be passed. */ -class Binary extends TypedData implements TypedDataInterface { +class Binary extends TypedData { /** * The file resource URI. diff --git a/core/lib/Drupal/Core/TypedData/Type/Boolean.php b/core/lib/Drupal/Core/TypedData/Type/Boolean.php index 5714599994b..c8797efb0ed 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Boolean.php +++ b/core/lib/Drupal/Core/TypedData/Type/Boolean.php @@ -7,7 +7,7 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; /** * The boolean data type. @@ -15,7 +15,7 @@ use Drupal\Core\TypedData\TypedDataInterface; * The plain value of a boolean is a regular PHP boolean. For setting the value * any PHP variable that casts to a boolean may be passed. */ -class Boolean extends TypedData implements TypedDataInterface { +class Boolean extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/Date.php b/core/lib/Drupal/Core/TypedData/Type/Date.php index 3c0f3a9d4bc..a5342801a1b 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Date.php +++ b/core/lib/Drupal/Core/TypedData/Type/Date.php @@ -8,7 +8,7 @@ namespace Drupal\Core\TypedData\Type; use Drupal\Core\Datetime\DrupalDateTime; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; use InvalidArgumentException; /** @@ -19,7 +19,7 @@ use InvalidArgumentException; * class will work, including a DateTime object, a timestamp, a string * date, or an array of date parts. */ -class Date extends TypedData implements TypedDataInterface { +class Date extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/Duration.php b/core/lib/Drupal/Core/TypedData/Type/Duration.php index 9fd8ceb98ab..d979dcfd798 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Duration.php +++ b/core/lib/Drupal/Core/TypedData/Type/Duration.php @@ -7,7 +7,7 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; use DateInterval; use InvalidArgumentException; @@ -19,7 +19,7 @@ use InvalidArgumentException; * supported by DateInterval::__construct, or an integer in seconds may be * passed. */ -class Duration extends TypedData implements TypedDataInterface { +class Duration extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/Float.php b/core/lib/Drupal/Core/TypedData/Type/Float.php index 798499c2fd2..3e8369bd51b 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Float.php +++ b/core/lib/Drupal/Core/TypedData/Type/Float.php @@ -7,7 +7,7 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; /** * The float data type. @@ -15,7 +15,7 @@ use Drupal\Core\TypedData\TypedDataInterface; * The plain value of a float is a regular PHP float. For setting the value * any PHP variable that casts to a float may be passed. */ -class Float extends TypedData implements TypedDataInterface { +class Float extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/Integer.php b/core/lib/Drupal/Core/TypedData/Type/Integer.php index 4e9b59ad2df..4303511a79b 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Integer.php +++ b/core/lib/Drupal/Core/TypedData/Type/Integer.php @@ -7,7 +7,7 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; /** * The integer data type. @@ -15,7 +15,7 @@ use Drupal\Core\TypedData\TypedDataInterface; * The plain value of an integer is a regular PHP integer. For setting the value * any PHP variable that casts to an integer may be passed. */ -class Integer extends TypedData implements TypedDataInterface { +class Integer extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/Language.php b/core/lib/Drupal/Core/TypedData/Type/Language.php index 67f4df822a1..50ae4b8eed9 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Language.php +++ b/core/lib/Drupal/Core/TypedData/Type/Language.php @@ -7,9 +7,8 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\ContextAwareInterface; -use Drupal\Core\TypedData\TypedDataInterface; use InvalidArgumentException; +use Drupal\Core\TypedData\ContextAwareTypedData; /** * Defines the 'language' data type. @@ -25,21 +24,7 @@ use InvalidArgumentException; * - langcode source: If used as computed property, the langcode property used * to load the language object. */ -class Language extends TypedData implements TypedDataInterface, ContextAwareInterface { - - /** - * The name. - * - * @var string - */ - protected $name; - - /** - * The parent data structure. - * - * @var mixed - */ - protected $parent; +class Language extends ContextAwareTypedData { /** * The language code of the language if no 'langcode source' is used. @@ -48,41 +33,15 @@ class Language extends TypedData implements TypedDataInterface, ContextAwareInte */ protected $langcode; - /** - * Implements ContextAwareInterface::getName(). - */ - public function getName() { - return $this->name; - } - - /** - * Implements ContextAwareInterface::setName(). - */ - public function setName($name) { - $this->name = $name; - } - - /** - * Implements ContextAwareInterface::getParent(). - */ - public function getParent() { - return $this->parent; - } - - /** - * Implements ContextAwareInterface::setParent(). - */ - public function setParent($parent) { - $this->parent = $parent; - } - /** * Implements TypedDataInterface::getValue(). */ public function getValue() { $source = $this->getLanguageCodeSource(); $langcode = $source ? $source->getValue() : $this->langcode; - return $langcode ? language_load($langcode) : NULL; + if ($langcode) { + return language_load($langcode); + } } /** diff --git a/core/lib/Drupal/Core/TypedData/Type/String.php b/core/lib/Drupal/Core/TypedData/Type/String.php index fef3248add3..92482398acd 100644 --- a/core/lib/Drupal/Core/TypedData/Type/String.php +++ b/core/lib/Drupal/Core/TypedData/Type/String.php @@ -7,7 +7,7 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; /** * The string data type. @@ -15,7 +15,7 @@ use Drupal\Core\TypedData\TypedDataInterface; * The plain value of a string is a regular PHP string. For setting the value * any PHP variable that casts to a string may be passed. */ -class String extends TypedData implements TypedDataInterface { +class String extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/Uri.php b/core/lib/Drupal/Core/TypedData/Type/Uri.php index 010fa032109..52b9c3fab7e 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Uri.php +++ b/core/lib/Drupal/Core/TypedData/Type/Uri.php @@ -7,14 +7,14 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedData; /** * The URI data type. * * The plain value of a URI is an absolute URI represented as PHP string. */ -class Uri extends TypedData implements TypedDataInterface { +class Uri extends TypedData { /** * The data value. diff --git a/core/lib/Drupal/Core/TypedData/Type/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php similarity index 90% rename from core/lib/Drupal/Core/TypedData/Type/TypedData.php rename to core/lib/Drupal/Core/TypedData/TypedData.php index 1e70c531b35..053bb709233 100644 --- a/core/lib/Drupal/Core/TypedData/Type/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -2,12 +2,10 @@ /** * @file - * Definition of Drupal\Core\TypedData\Type\TypedData. + * Contains \Drupal\Core\TypedData\TypedData. */ -namespace Drupal\Core\TypedData\Type; - -use Drupal\Core\TypedData\TypedDataInterface; +namespace Drupal\Core\TypedData; /** * The abstract base class for typed data. diff --git a/core/lib/Drupal/Core/TypedData/TypedDataFactory.php b/core/lib/Drupal/Core/TypedData/TypedDataFactory.php index d245157c916..14b05d6a653 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataFactory.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataFactory.php @@ -7,6 +7,7 @@ namespace Drupal\Core\TypedData; +use InvalidArgumentException; use Drupal\Component\Plugin\Factory\DefaultFactory; use Drupal\Component\Plugin\Exception\PluginException; @@ -25,28 +26,35 @@ class TypedDataFactory extends DefaultFactory { * The id of a plugin, i.e. the data type. * @param array $configuration * The plugin configuration, i.e. the data definition. + * @param string $name + * (optional) If a property or list item is to be created, the name of the + * property or the delta of the list item. + * @param mixed $parent + * (optional) If a property or list item is to be created, the parent typed + * data object implementing either the ListInterface or the + * ComplexDataInterface. * - * @return Drupal\Core\TypedData\TypedDataInterface + * @return \Drupal\Core\TypedData\TypedDataInterface */ - public function createInstance($plugin_id, array $configuration) { + public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) { $type_definition = $this->discovery->getDefinition($plugin_id); - // Allow per-data definition overrides of the used classes and generally - // default to the data type definition. - $definition = $configuration + $type_definition; + if (!isset($type_definition)) { + throw new InvalidArgumentException(format_string('Invalid data type %plugin_id has been given.', array('%plugin_id' => $plugin_id))); + } - if (empty($definition['list'])) { - if (empty($definition['class'])) { - throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id)); - } - $plugin_class = $definition['class']; + // Allow per-data definition overrides of the used classes. + $key = empty($configuration['list']) ? 'class' : 'list class'; + if (isset($configuration[$key])) { + $class = $configuration[$key]; } - else { - if (empty($definition['list class'])) { - throw new PluginException(sprintf('The plugin (%s) did not specify a list instance class.', $plugin_id)); - } - $plugin_class = $definition['list class']; + elseif (isset($type_definition[$key])) { + $class = $type_definition[$key]; } - return new $plugin_class($definition, $plugin_id, $this->discovery); + + if (!isset($class)) { + throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id)); + } + return new $class($configuration, $name, $parent); } } diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index a1522d80482..91367ad7f90 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -2,11 +2,12 @@ /** * @file - * Definition of Drupal\Core\TypedData\TypedDataManager. + * Contains \Drupal\Core\TypedData\TypedDataManager. */ namespace Drupal\Core\TypedData; +use InvalidArgumentException; use Drupal\Component\Plugin\PluginManagerBase; use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\HookDiscovery; @@ -16,6 +17,13 @@ use Drupal\Core\Plugin\Discovery\HookDiscovery; */ class TypedDataManager extends PluginManagerBase { + /** + * An array of typed data property prototypes. + * + * @var array + */ + protected $prototypes = array(); + public function __construct() { $this->discovery = new CacheDecorator(new HookDiscovery('data_type_info'), 'typed_data:types'); $this->factory = new TypedDataFactory($this->discovery); @@ -28,11 +36,18 @@ class TypedDataManager extends PluginManagerBase { * The id of a plugin, i.e. the data type. * @param array $configuration * The plugin configuration, i.e. the data definition. + * @param string $name + * (optional) If a property or list item is to be created, the name of the + * property or the delta of the list item. + * @param mixed $parent + * (optional) If a property or list item is to be created, the parent typed + * data object implementing either the ListInterface or the + * ComplexDataInterface. * - * @return Drupal\Core\TypedData\TypedDataInterface + * @return \Drupal\Core\TypedData\TypedDataInterface */ - public function createInstance($plugin_id, array $configuration) { - return $this->factory->createInstance($plugin_id, $configuration); + public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) { + return $this->factory->createInstance($plugin_id, $configuration, $name, $parent); } /** @@ -70,42 +85,126 @@ class TypedDataManager extends PluginManagerBase { * @param mixed $value * (optional) The data value. If set, it has to match one of the supported * data type format as documented for the data type classes. - * @param array $context - * (optional) An array describing the context of the data object, e.g. its - * name or parent data structure. The context should be passed if a typed - * data object is created as part of a data structure. The following keys - * are supported: - * - name: The name associated with the data. - * - parent: The parent object containing the data. Must be an instance of - * Drupal\Core\TypedData\ComplexDataInterface or - * Drupal\Core\TypedData\ListInterface. + * @param string $name + * (optional) If a property or list item is to be created, the name of the + * property or the delta of the list item. + * @param mixed $parent + * (optional) If a property or list item is to be created, the parent typed + * data object implementing either the ListInterface or the + * ComplexDataInterface. * - * @return Drupal\Core\TypedData\TypedDataInterface + * @return \Drupal\Core\TypedData\TypedDataInterface * * @see typed_data() - * @see Drupal\Core\TypedData\Type\Integer - * @see Drupal\Core\TypedData\Type\Float - * @see Drupal\Core\TypedData\Type\String - * @see Drupal\Core\TypedData\Type\Boolean - * @see Drupal\Core\TypedData\Type\Duration - * @see Drupal\Core\TypedData\Type\Date - * @see Drupal\Core\TypedData\Type\Uri - * @see Drupal\Core\TypedData\Type\Binary - * @see Drupal\Core\Entity\Field\EntityWrapper + * @see \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() + * @see \Drupal\Core\TypedData\Type\Integer + * @see \Drupal\Core\TypedData\Type\Float + * @see \Drupal\Core\TypedData\Type\String + * @see \Drupal\Core\TypedData\Type\Boolean + * @see \Drupal\Core\TypedData\Type\Duration + * @see \Drupal\Core\TypedData\Type\Date + * @see \Drupal\Core\TypedData\Type\Uri + * @see \Drupal\Core\TypedData\Type\Binary + * @see \Drupal\Core\Entity\Field\EntityWrapper */ - function create(array $definition, $value = NULL, array $context = array()) { - $wrapper = $this->createInstance($definition['type'], $definition); + public function create(array $definition, $value = NULL, $name = NULL, $parent = NULL) { + $wrapper = $this->factory->createInstance($definition['type'], $definition, $name, $parent); if (isset($value)) { $wrapper->setValue($value); } - if ($wrapper instanceof ContextAwareInterface) { - if (isset($context['name'])) { - $wrapper->setName($context['name']); - } - if (isset($context['parent'])) { - $wrapper->setParent($context['parent']); - } - } return $wrapper; } + + /** + * Implements \Drupal\Component\Plugin\PluginManagerInterface::getInstance(). + * + * @param array $options + * An array of options with the following keys: + * - object: The parent typed data object, implementing the + * ContextAwareInterface and either the ListInterface or the + * ComplexDataInterface. + * - property: The name of the property to instantiate, or the delta of the + * the list item to instantiate. + * - value: The value to set. If set, it has to match one of the supported + * data type formats as documented by the data type classes. + * + * @throws \InvalidArgumentException + * If the given property is not known, or the passed object does not + * implement the ListInterface or the ComplexDataInterface. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The new property instance. + * + * @see \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() + */ + public function getInstance(array $options) { + return $this->getPropertyInstance($options['object'], $options['property'], $options['value']); + } + + /** + * Get a typed data instance for a property of a given typed data object. + * + * This method will use prototyping for fast and efficient instantiation of + * many property objects with the same property path; e.g., + * when multiple comments are used comment_body.0.value needs to be + * instantiated very often. + * Prototyping is done by the root object's data type and the given + * property path, i.e. all property instances having the same property path + * and inheriting from the same data type are prototyped. + * + * @param \Drupal\Core\TypedData\ContextAwareInterface $object + * The parent typed data object, implementing the ContextAwareInterface and + * either the ListInterface or the ComplexDataInterface. + * @param string $property_name + * The name of the property to instantiate, or the delta of an list item. + * @param mixed $value + * (optional) The data value. If set, it has to match one of the supported + * data type formats as documented by the data type classes. + * + * @throws \InvalidArgumentException + * If the given property is not known, or the passed object does not + * implement the ListInterface or the ComplexDataInterface. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The new property instance. + * + * @see \Drupal\Core\TypedData\TypedDataManager::create() + */ + public function getPropertyInstance(ContextAwareInterface $object, $property_name, $value = NULL) { + $key = $object->getRoot()->getType() . ':' . $object->getPropertyPath() . '.'; + // If we are creating list items, we always use 0 in the key as all list + // items look the same. + $key .= is_numeric($property_name) ? 0 : $property_name; + + // Make sure we have a prototype. Then, clone the prototype and set object + // specific values, i.e. the value and the context. + if (!isset($this->prototypes[$key])) { + if ($object instanceof ComplexDataInterface) { + $definition = $object->getPropertyDefinition($property_name); + } + elseif ($object instanceof ListInterface) { + $definition = $object->getItemDefinition(); + } + else { + throw new InvalidArgumentException("The passed object has to either implement the ComplexDataInterface or the ListInterface."); + } + // Make sure we have got a valid definition. + if (!$definition) { + throw new InvalidArgumentException('Property ' . check_plain($property_name) . ' is unknown.'); + } + + $this->prototypes[$key] = $this->create($definition, NULL, $property_name, $object); + } + + $property = clone $this->prototypes[$key]; + // Update the parent relationship if necessary. + if ($property instanceof ContextAwareInterface) { + $property->setContext($property_name, $object); + } + // Set the passed data value. + if (isset($value)) { + $property->setValue($value); + } + return $property; + } } diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php index b35caf11ae2..b28653902a9 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php @@ -79,6 +79,14 @@ class JsonldEntityNormalizer extends JsonldNormalizerBase implements Denormalize $values['langcode'] = language(LANGUAGE_TYPE_CONTENT)->langcode; } $entity = entity_create($typed_data_ids['entity_type'], $values); + // Make sure all empty entity fields default to NULL, so that afterwards it + // is possible to determine which fields were part of the data (even if they + // are empty). + foreach ($entity as $name => $field) { + if ($field->isEmpty()) { + $field->setValue(NULL); + } + } // For each attribute in the JSON-LD, add the values as fields to the newly // created entity. It is assumed that the JSON attribute names are the same diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 6e41540fa0e..4e3b101caaf 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -2076,9 +2076,9 @@ abstract class WebTestBase extends TestBase { // Assert the definition of the wrapper. $this->assertTrue($data instanceof \Drupal\Core\TypedData\TypedDataInterface, 'Typed data object is an instance of the typed data interface.'); $definition = $data->getDefinition(); - $this->assertTrue(!empty($definition['label']), $definition['label'] . ' data definition was returned.'); + $this->assertTrue(!empty($definition['type']), format_string('!type data definition was returned.', array('!type' => $definition['type']))); // Assert that the correct type was constructed. - $this->assertEqual($data->getType(), $type, $definition['label'] . ' object returned type.'); + $this->assertEqual($data->getType(), $type, format_string('!type object returned type.', array('!type' => $definition['type']))); return $data; } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php index 3dd810c7df3..56f3873842e 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php @@ -221,10 +221,12 @@ class EntityFieldTest extends WebTestBase { $this->assertIdentical(count($entity->name), 0, 'Name field contains no items.'); $this->assertIdentical($entity->name->getValue(), array(), 'Name field value is an empty array.'); + $entity->name->value = 'foo'; + $this->assertTrue($entity->name->value, 'foo', 'Name field set.'); // Test removing all list items by setting it to NULL. $entity->name = NULL; $this->assertIdentical(count($entity->name), 0, 'Name field contains no items.'); - $this->assertIdentical($entity->name->getValue(), array(), 'Name field value is an empty array.'); + $this->assertNull($entity->name->getValue(), 'Name field value is NULL.'); // Test get and set field values. $entity->name = 'foo'; @@ -319,6 +321,30 @@ class EntityFieldTest extends WebTestBase { // @todo: Once the user entity has definitions, continue testing getting // them from the $userref_values['entity'] property. + + // 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); + + $field = $entity->user_id; + $this->assertIdentical($field->getRoot(), $entity, 'Entity is root object.'); + $this->assertEqual($field->getPropertyPath(), 'user_id'); + $this->assertEqual($field->getName(), 'user_id'); + $this->assertIdentical($field->getParent(), $entity, 'Parent object matches.'); + + $field_item = $field[0]; + $this->assertIdentical($field_item->getRoot(), $entity, 'Entity is root object.'); + $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->assertEqual($item_value->getPropertyPath(), 'user_id.0.entity'); + $this->assertEqual($item_value->getName(), 'entity'); + $this->assertIdentical($item_value->getParent(), $field_item, 'Parent object matches.'); } /** diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php index e3b45fa9827..d24ade85f9e 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php @@ -48,30 +48,38 @@ class EntityTestStorageController extends DatabaseStorageControllerNG { * An array of entity objects implementing the EntityInterface. */ protected function mapFromStorageRecords(array $records, $load_revision = FALSE) { - $records = parent::mapFromStorageRecords($records, $load_revision); + $property_values = $this->getPropertyValues($records, $load_revision); - // Load data of translatable properties. - $this->attachPropertyData($records, $load_revision); + foreach ($records as $id => $record) { + $values = isset($property_values[$id]) ? $property_values[$id] : array(); + + foreach ($record as $name => $value) { + $values[$name][LANGUAGE_DEFAULT][0]['value'] = $value; + } + $entity = new $this->entityClass($values, $this->entityType); + $records[$id] = $entity; + } return $records; } /** * Attaches property data in all languages for translatable properties. */ - protected function attachPropertyData(&$queried_entities, $load_revision = FALSE) { + protected function getPropertyValues($records, $load_revision = FALSE) { $query = db_select('entity_test_property_data', 'data', array('fetch' => PDO::FETCH_ASSOC)) ->fields('data') - ->condition('id', array_keys($queried_entities)) + ->condition('id', array_keys($records)) ->orderBy('data.id'); if ($load_revision) { // Get revision id's. $revision_ids = array(); - foreach ($queried_entities as $id => $entity) { - $revision_ids[] = $entity->get('revision_id')->value; + foreach ($records as $record) { + $revision_ids[] = $record->revision_id; } $query->condition('revision_id', $revision_ids); } $data = $query->execute(); + $property_values = array(); foreach ($data as $values) { $id = $values['id']; @@ -79,9 +87,10 @@ class EntityTestStorageController extends DatabaseStorageControllerNG { // LANGUAGE_DEFAULT as key. $langcode = empty($values['default_langcode']) ? $values['langcode'] : LANGUAGE_DEFAULT; - $queried_entities[$id]->name[$langcode][0]['value'] = $values['name']; - $queried_entities[$id]->user_id[$langcode][0]['value'] = $values['user_id']; + $property_values[$id]['name'][$langcode][0]['value'] = $values['name']; + $property_values[$id]['user_id'][$langcode][0]['value'] = $values['user_id']; } + return $property_values; } /** diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php index 9b4d3688d44..22ac2ec762c 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php @@ -23,7 +23,7 @@ use Drupal\Core\Annotation\Translation; * form_controller_class = { * "default" = "Drupal\entity_test\EntityTestFormController" * }, - * translation_controller_class = "Drupal\entity_test\EntityTestTranslationController", + * translation_controller_class = "Drupal\translation_entity\EntityTranslationControllerNG", * base_table = "entity_test", * data_table = "entity_test_property_data", * revision_table = "entity_test_property_revision", @@ -74,14 +74,12 @@ class EntityTest extends EntityNG { public $user_id; /** - * Overrides Entity::__construct(). + * Initialize the object. Invoked upon construction and wake up. */ - public function __construct(array $values, $entity_type) { - parent::__construct($values, $entity_type); - + protected function init() { + parent::init(); // We unset all defined properties, so magic getters apply. unset($this->id); - unset($this->langcode); unset($this->uuid); unset($this->revision_id); unset($this->name); diff --git a/core/modules/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index 9284c2d0a24..c73af6fe2df 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -8,7 +8,7 @@ namespace Drupal\text; use Drupal\Core\TypedData\ContextAwareInterface; -use Drupal\Core\TypedData\Type\String; +use Drupal\Core\TypedData\ContextAwareTypedData; use Drupal\Core\TypedData\ReadOnlyException; use InvalidArgumentException; @@ -18,7 +18,7 @@ use InvalidArgumentException; * Required settings (below the definition's 'settings' key) are: * - text source: The text property containing the to be processed text. */ -class TextProcessed extends String implements ContextAwareInterface { +class TextProcessed extends ContextAwareTypedData { /** * The text property. @@ -35,24 +35,10 @@ class TextProcessed extends String implements ContextAwareInterface { protected $format; /** - * The name. - * - * @var string + * Overrides ContextAwareTypedData::__construct(). */ - protected $name; - - /** - * The parent data structure. - * - * @var \Drupal\Core\Entity\Field\FieldItemInterface - */ - protected $parent; - - /** - * Implements TypedDataInterface::__construct(). - */ - public function __construct(array $definition) { - $this->definition = $definition; + public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) { + parent::__construct($definition, $name, $parent); if (!isset($definition['settings']['text source'])) { throw new InvalidArgumentException("The definition's 'source' key has to specify the name of the text property to be processed."); @@ -60,39 +46,18 @@ class TextProcessed extends String implements ContextAwareInterface { } /** - * Implements ContextAwareInterface::getName(). + * Overrides ContextAwareTypedData::setContext(). */ - public function getName() { - return $this->name; + public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { + parent::setContext($name, $parent); + if (isset($parent)) { + $this->text = $parent->get($this->definition['settings']['text source']); + $this->format = $parent->get('format'); + } } /** - * Implements ContextAwareInterface::setName(). - */ - public function setName($name) { - $this->name = $name; - } - - /** - * Implements ContextAwareInterface::getParent(). - * - * @return \Drupal\Core\Entity\Field\FieldItemInterface - */ - public function getParent() { - return $this->parent; - } - - /** - * Implements ContextAwareInterface::setParent(). - */ - public function setParent($parent) { - $this->parent = $parent; - $this->text = $parent->get($this->definition['settings']['text source']); - $this->format = $parent->get('format'); - } - - /** - * Implements TypedDataInterface::getValue(). + * Implements \Drupal\Core\TypedData\TypedDataInterface::getValue(). */ public function getValue($langcode = NULL) { @@ -104,21 +69,28 @@ class TextProcessed extends String implements ContextAwareInterface { $entity = $field->getParent(); $instance = field_info_instance($entity->entityType(), $field->getName(), $entity->bundle()); - if (!empty($instance['settings']['text_processing']) && $this->format->value) { - return check_markup($this->text->value, $this->format->value, $entity->language()->langcode); + if (!empty($instance['settings']['text_processing']) && $this->format->getValue()) { + return check_markup($this->text->getValue(), $this->format->getValue(), $entity->language()->langcode); } else { // If no format is available, still make sure to sanitize the text. - return check_plain($this->text->value); + return check_plain($this->text->getValue()); } } /** - * Implements TypedDataInterface::setValue(). + * Implements \Drupal\Core\TypedData\TypedDataInterface::setValue(). */ public function setValue($value) { if (isset($value)) { throw new ReadOnlyException('Unable to set a computed property.'); } } + + /** + * Implements \Drupal\Core\TypedData\TypedDataInterface::validate(). + */ + public function validate() { + // @todo: Implement. + } } diff --git a/core/modules/text/lib/Drupal/text/Type/TextItem.php b/core/modules/text/lib/Drupal/text/Type/TextItem.php index 07ae7f9f53f..2077b42ddf8 100644 --- a/core/modules/text/lib/Drupal/text/Type/TextItem.php +++ b/core/modules/text/lib/Drupal/text/Type/TextItem.php @@ -10,12 +10,12 @@ namespace Drupal\text\Type; use Drupal\Core\Entity\Field\FieldItemBase; /** - * Defines the 'text_item' and 'text_long_item' entity field items. + * Defines the 'text_field' and 'text_long_field' entity field items. */ class TextItem extends FieldItemBase { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * diff --git a/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php b/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php index b6438ff88d8..67624c4b29d 100644 --- a/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php +++ b/core/modules/text/lib/Drupal/text/Type/TextSummaryItem.php @@ -8,12 +8,12 @@ namespace Drupal\text\Type; /** - * Defines the 'text_with_summary' entity field item. + * Defines the 'text_with_summary_field' entity field item. */ class TextSummaryItem extends TextItem { /** - * Field definitions of the contained properties. + * Definitions of the contained properties. * * @see self::getPropertyDefinitions() * diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php similarity index 56% rename from core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php rename to core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php index e7ca0507b83..4c4e9e60ac4 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php @@ -2,21 +2,20 @@ /** * @file - * Definition of Drupal\translation_entity\EntityTranslationController. + * Contains \Drupal\translation_entity\EntityTranslationControllerNG. */ -namespace Drupal\entity_test; +namespace Drupal\translation_entity; use Drupal\Core\Entity\EntityInterface; -use Drupal\translation_entity\EntityTranslationController; /** * Test entity translation controller. */ -class EntityTestTranslationController extends EntityTranslationController { +class EntityTranslationControllerNG extends EntityTranslationController { /** - * Overrides EntityTranslationControllerInterface::removeTranslation(). + * Overrides EntityTranslationController::removeTranslation(). */ public function removeTranslation(EntityInterface $entity, $langcode) { $translation = $entity->getTranslation($langcode); @@ -24,5 +23,4 @@ class EntityTestTranslationController extends EntityTranslationController { $translation->$property_name = array(); } } - } diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php index eac0b7fa2c4..6ad80a3cee8 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php @@ -292,7 +292,7 @@ abstract class EntityTranslationUITest extends WebTestBase { * The translation object to act on. */ protected function getTranslation(EntityInterface $entity, $langcode) { - return $entity instanceof EntityNG ? $entity->getTranslation($langcode) : $entity; + return $entity instanceof EntityNG ? $entity->getTranslation($langcode, FALSE) : $entity; } /** diff --git a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php index a6503ec5a75..db6c90000e8 100644 --- a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -9,6 +9,7 @@ namespace Drupal\views_ui; use Drupal\views\ViewExecutable; use Drupal\Core\Database\Database; +use Drupal\Core\TypedData\ContextAwareInterface; use Drupal\views\Plugin\views\query\Sql; use Drupal\views\Plugin\Core\Entity\View; use Drupal\views\ViewStorageInterface; @@ -1080,4 +1081,52 @@ class ViewUI implements ViewStorageInterface { return $this->__call(__FUNCTION__, func_get_args()); } + /** + * Implements Drupal\Core\Entity\EntityInterface::getBCEntity(). + */ + public function getBCEntity() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::getOriginalEntity(). + */ + public function getOriginalEntity() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getName(). + */ + public function getName() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getRoot(). + */ + public function getRoot() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getPropertyPath(). + */ + public function getPropertyPath() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::getParent(). + */ + public function getParent() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * Implements \Drupal\Core\TypedData\ContextAwareInterface::setContext(). + */ + public function setContext($name = NULL, ContextAwareInterface $parent = NULL) { + return $this->__call(__FUNCTION__, func_get_args()); + } }