Skip to content
EntityBCDecorator.php 14.9 KiB
Newer Older
<?php

/**
 * @file
 * Contains \Drupal\Core\Entity\EntityBCDecorator.
 */

namespace Drupal\Core\Entity;

use Drupal\Core\Language\Language;
use IteratorAggregate;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\TypedDataInterface;
 * Provides backwards compatible (BC) access to entity fields.
 * Allows using entities converted to the new Entity Field API with the previous
 * way of accessing fields or properties. For example, via the backwards
 * compatible (BC) decorator you can do:
 * @code
 *   $node->title = $value;
 *   $node->body[LANGUAGE_NONE][0]['value'] = $value;
 * @endcode
 * Without the BC decorator the same assignment would have to look like this:
 * @code
 *   $node->title->value = $value;
 *   $node->body->value = $value;
 * @endcode
 * Without the BC decorator the language always default to the entity language,
 * whereas a specific translation can be access via the getTranslation() method.
 * The BC decorator should be only used during conversion to the new entity
 * field API, such that existing code can be converted iteratively. Any new code
 * should directly use the new entity field API and avoid using the
 * EntityBCDecorator, if possible.
 *
 * @todo: Remove once everything is converted to use the new entity field API.
 */
class EntityBCDecorator implements IteratorAggregate, EntityInterface {

  /**
   * The EntityInterface object being decorated.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  protected $decorated;

  /**
   * Local cache for field definitions.
   *
   * @var array
   */
  protected $definitions;

  /**
   * Constructs a Drupal\Core\Entity\EntityCompatibilityDecorator object.
   *
   * @param \Drupal\Core\Entity\EntityInterface $decorated
   *   The decorated entity.
   * @param array &$definitions
   *   An array of field definitions.
  function __construct(EntityNG $decorated, array &$definitions) {
    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) {
    // Directly return the original property.
    if ($name == 'original') {
      return $this->decorated->values[$name];
    }

    // We access the protected 'values' and 'fields' properties of the decorated
    // entity via the magic getter - which returns them by reference for us. We
    // do so, as providing references to these arrays would make $entity->values
    // and $entity->fields reference themselves, which is problematic during
    // __clone() (this is something we cannot work-a-round easily as an unset()
    // on the variable is problematic in conjunction with the magic
    // getter/setter).

    if (!empty($this->decorated->fields[$name])) {
      // Any field value set via the new Entity Field API will be stored inside
      // the field objects managed by the entity, thus we need to ensure
      // $this->decorated->values reflects the latest values first.
      foreach ($this->decorated->fields[$name] as $langcode => $field) {
        // Only set if it's not empty, otherwise there can be ghost values.
        if (!$field->isEmpty()) {
          $this->decorated->values[$name][$langcode] = $field->getValue();
        }
      // The returned values might be changed by reference, so we need to remove
      // the field object to avoid the field object and the value getting out of
      // sync. That way, the next field object instantiated by EntityNG will
      // receive the possibly updated value.
      unset($this->decorated->fields[$name]);
    }
    // When accessing values for entity properties that have been converted to
    // an entity field, provide direct access to the plain value. This makes it
    // possible to use the BC-decorator with properties; e.g., $node->title.
    if (isset($this->definitions[$name]) && empty($this->definitions[$name]['configurable'])) {
      if (!isset($this->decorated->values[$name][Language::LANGCODE_DEFAULT])) {
        $this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]['value'] = NULL;
      if (is_array($this->decorated->values[$name][Language::LANGCODE_DEFAULT])) {
        // This will work with all defined properties that have a single value.
        // We need to ensure the key doesn't matter. Mostly it's 'value' but
        // e.g. EntityReferenceItem uses target_id.
        if (isset($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]) && count($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0]) == 1) {
          return $this->decorated->values[$name][Language::LANGCODE_DEFAULT][0][key($this->decorated->values[$name][Language::LANGCODE_DEFAULT][0])];
      return $this->decorated->values[$name][Language::LANGCODE_DEFAULT];
    else {
      // Allow accessing field values in an entity default language other than
      // Language::LANGCODE_DEFAULT by mapping the values to
      // Language::LANGCODE_DEFAULT. This is necessary as EntityNG always keys
      // default language values with Language::LANGCODE_DEFAULT while field API
      // expects them to be keyed by langcode.
      $langcode = $this->decorated->language()->langcode;
      if ($langcode != Language::LANGCODE_DEFAULT && isset($this->decorated->values[$name]) && is_array($this->decorated->values[$name])) {
        if (isset($this->decorated->values[$name][Language::LANGCODE_DEFAULT]) && !isset($this->decorated->values[$name][$langcode])) {
          $this->decorated->values[$name][$langcode] = &$this->decorated->values[$name][Language::LANGCODE_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) {
    $defined = isset($this->definitions[$name]);
    // When updating values for entity properties that have been converted to
    // an entity field, directly write to the plain value. This makes it
    // possible to use the BC-decorator with properties; e.g., $node->title.
    if ($defined && empty($this->definitions[$name]['configurable'])) {
      $this->decorated->values[$name][Language::LANGCODE_DEFAULT] = $value;
    }
    else {
      if ($defined && is_array($value)) {
        // If field API sets a value with a langcode in entity language, move it
        // to Language::LANGCODE_DEFAULT.
        // This is necessary as EntityNG always keys default language values
        // with Language::LANGCODE_DEFAULT while field API expects them to be
        // keyed by langcode.
        foreach ($value as $langcode => $data) {
          if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->decorated->language()->langcode) {
            $value[Language::LANGCODE_DEFAULT] = $data;
      $this->decorated->values[$name] = $value;
    // Remove the field object to avoid the field object and the value getting
    // out of sync. That way, the next field object instantiated by EntityNG
    // will hold the updated 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) {
  /**
   * Implements the magic method for clone().
   */
  function __clone() {
    $this->decorated = clone $this->decorated;
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) {
    return $this->decorated->access($operation, $account);
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function get($property_name) {
    // Ensure this works with not yet defined fields.
    if (!isset($this->definitions[$property_name])) {
      return $this->__get($property_name);
    }
    return $this->decorated->get($property_name);
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function set($property_name, $value, $notify = TRUE) {
    // Ensure this works with not yet defined fields.
    if (!isset($this->definitions[$property_name])) {
      return $this->__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 getType() {
    return $this->decorated->getType();
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function getDefinition() {
    return $this->decorated->getDefinition();
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function getValue() {
    return $this->decorated->getValue();
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function setValue($value, $notify = TRUE) {
    return $this->decorated->setValue($value, $notify);
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function getString() {
    return $this->decorated->getString();
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function getConstraints() {
    return $this->decorated->getConstraints();
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function validate() {
    return $this->decorated->validate();
  }

  /**
   * 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, TypedDataInterface $parent = NULL) {
    $this->decorated->setContext($name, $parent);
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function getExportProperties() {
    $this->decorated->getExportProperties();
  }
  /**
   * Forwards the call to the decorated entity.
   */
  public function onChange($property_name) {
    $this->decorated->onChange($property_name);
  }

  /**
   * Forwards the call to the decorated entity.
   */
  public function isTranslatable() {
    return $this->decorated->isTranslatable();
  }