Issue #1869250 by fago, Berdir, das-peter, YesCT, mradcliffe, fubhy: Various EntityNG and TypedData API improvements.

8.0.x
catch 2013-01-02 11:51:19 +00:00
parent 85c8d12ec9
commit c96966f079
46 changed files with 1346 additions and 661 deletions

View File

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

View File

@ -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];
}
/**

View File

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

View File

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

View File

@ -0,0 +1,368 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\EntityBCDecorator.
*/
namespace Drupal\Core\Entity;
use IteratorAggregate;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\ContextAwareInterface;
/**
* Implements a decorator providing backwards compatible entity field access.
*
* Allows using entities converted to the new Entity Field API with the Drupal 7
* way of accessing fields or properties.
*
* Note: We access the protected 'values' and 'fields' properties of the entity
* via the magic getter - which returns them by reference for us. We do so, as
* providing references to this arrays makes $entity->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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\ContextAwareTypedData.
*/
namespace Drupal\Core\TypedData;
/**
* An abstract base class for context aware typed data.
*
* This implementation requires parent typed data objects to implement the
* ContextAwareInterface also, such that the context can be derived from the
* parents.
*
* Classes deriving from this base class have to declare $value
* or override getValue() or setValue().
*/
abstract class ContextAwareTypedData extends TypedData implements ContextAwareInterface {
/**
* The property name.
*
* @var string
*/
protected $name;
/**
* The parent typed data object.
*
* @var \Drupal\Core\TypedData\ContextAwareInterface
*/
protected $parent;
/**
* Constructs a TypedData object given its definition and context.
*
* @param array $definition
* The data definition.
* @param string $name
* (optional) The name of the created property, 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.
*
* @see Drupal\Core\TypedData\TypedDataManager::create()
*/
public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) {
$this->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.
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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