Commit bba539c6 authored by catch's avatar catch

#1869562 by fago, berdir, dawehner: Avoid instantiating EntityNG field value objects by default.

parent 288f240c
......@@ -89,7 +89,7 @@ public function get($property_name, $langcode = NULL) {
* EntityInterface::set() implements support for fieldable entities, but
* configuration entities are not fieldable.
*/
public function set($property_name, $value, $langcode = NULL) {
public function set($property_name, $value, $langcode = NULL, $notify = TRUE) {
// @todo: Add support for translatable properties being not fields.
$this->{$property_name} = $value;
}
......
......@@ -7,12 +7,12 @@
namespace Drupal\Core\Config\Schema;
use Drupal\Core\TypedData\ContextAwareTypedData;
use Drupal\Core\TypedData\TypedData;
/**
* Defines a generic configuration element.
*/
abstract class Element extends ContextAwareTypedData {
abstract class Element extends TypedData {
/**
* The configuration value.
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use \InvalidArgumentException;
/**
......@@ -51,7 +52,11 @@ public function get($property_name) {
/**
* Implements Drupal\Core\TypedData\ComplexDataInterface::set().
*/
public function set($property_name, $value) {
public function set($property_name, $value, $notify = TRUE) {
// Notify the parent of any changes to be made.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
// Set the data into the configuration array but behave according to the
// interface specification when we've got a null value.
if (isset($value)) {
......@@ -122,4 +127,14 @@ public function isEmpty() {
return empty($this->value);
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
*/
public function onChange($property_name) {
// Notify the parent of changes.
if (isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
}
......@@ -40,4 +40,13 @@ public function getItemDefinition() {
return $this->definition['sequence'][0];
}
/**
* Implements \Drupal\Core\TypedData\ListInterface::onChange().
*/
public function onChange($delta) {
// Notify the parent of changes.
if (isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
}
......@@ -109,7 +109,7 @@ public function create(array $values) {
// Assign a new UUID if there is none yet.
if ($this->uuidKey && !isset($entity->{$this->uuidKey}->value)) {
$uuid = new Uuid();
$entity->{$this->uuidKey}->value = $uuid->generate();
$entity->{$this->uuidKey} = $uuid->generate();
}
// Modules might need to add or change the data initially held by the new
......
......@@ -9,7 +9,7 @@
use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Language\Language;
use Drupal\Core\TypedData\ContextAwareInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use IteratorAggregate;
/**
......@@ -191,7 +191,7 @@ public function get($property_name, $langcode = NULL) {
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
*/
public function set($property_name, $value) {
public function set($property_name, $value, $notify = TRUE) {
// @todo: Replace by EntityNG implementation once all entity types have been
// converted to use the entity field API.
$this->{$property_name} = $value;
......@@ -251,6 +251,7 @@ public function isEmpty() {
public function getIterator() {
// @todo: Replace by EntityNG implementation once all entity types have been
// converted to use the entity field API.
return new \ArrayIterator(array());
}
/**
......@@ -406,37 +407,103 @@ public function getNGEntity() {
}
/**
* Implements \Drupal\Core\TypedData\ContextAwareInterface::getName().
* Implements \Drupal\Core\TypedData\TypedDataInterface::getType().
*/
public function getType() {
// @todo: Incorporate the entity type here by making entities proper
// typed data. See http://drupal.org/node/1868004.
return 'entity';
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getDefinition().
*/
public function getDefinition() {
return array(
'type' => $this->getType()
);
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getValue().
*/
public function getValue() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::setValue().
*/
public function setValue($value, $notify = TRUE) {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getString().
*/
public function getString() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getConstraints().
*/
public function getConstraints() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
return array();
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::validate().
*/
public function validate() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
*/
public function onChange($property_name) {
// Nothing to do.
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getName().
*/
public function getName() {
return NULL;
}
/**
* Implements \Drupal\Core\TypedData\ContextAwareInterface::getRoot().
* Implements \Drupal\Core\TypedData\TypedDataInterface::getRoot().
*/
public function getRoot() {
return $this;
}
/**
* Implements \Drupal\Core\TypedData\ContextAwareInterface::getPropertyPath().
* Implements \Drupal\Core\TypedData\TypedDataInterface::getPropertyPath().
*/
public function getPropertyPath() {
return '';
}
/**
* Implements \Drupal\Core\TypedData\ContextAwareInterface::getParent().
* Implements \Drupal\Core\TypedData\TypedDataInterface::getParent().
*/
public function getParent() {
return NULL;
}
/**
* Implements \Drupal\Core\TypedData\ContextAwareInterface::setContext().
* Implements \Drupal\Core\TypedData\TypedDataInterface::setContext().
*/
public function setContext($name = NULL, ContextAwareInterface $parent = NULL) {
public function setContext($name = NULL, TypedDataInterface $parent = NULL) {
// As entities are always the root of the tree of typed data, we do not need
// to set any parent or name.
}
......
......@@ -9,7 +9,7 @@
use IteratorAggregate;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\ContextAwareInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Provides backwards compatible (BC) access to entity fields.
......@@ -229,7 +229,7 @@ public function get($property_name) {
/**
* Forwards the call to the decorated entity.
*/
public function set($property_name, $value) {
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);
......@@ -419,6 +419,55 @@ 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.
*/
......@@ -450,7 +499,7 @@ public function getParent() {
/**
* Forwards the call to the decorated entity.
*/
public function setContext($name = NULL, ContextAwareInterface $parent = NULL) {
public function setContext($name = NULL, TypedDataInterface $parent = NULL) {
$this->decorated->setContext($name, $parent);
}
......@@ -461,11 +510,17 @@ 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();
}
}
......@@ -8,7 +8,6 @@
namespace Drupal\Core\Entity;
use Drupal\Core\TypedData\AccessibleInterface;
use Drupal\Core\TypedData\ContextAwareInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TranslatableInterface;
......@@ -28,7 +27,7 @@
* @see \Drupal\Core\TypedData\TypedDataManager
* @see \Drupal\Core\Field\FieldInterface
*/
interface EntityInterface extends ContextAwareInterface, ComplexDataInterface, AccessibleInterface, TranslatableInterface {
interface EntityInterface extends ComplexDataInterface, AccessibleInterface, TranslatableInterface {
/**
* Returns the entity identifier (the entity's machine name or numeric ID).
......
......@@ -8,7 +8,6 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Language\Language;
use Drupal\Core\TypedData\ContextAwareInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Component\Uuid\Uuid;
use ArrayIterator;
......@@ -170,6 +169,7 @@ protected function getTranslatedField($property_name, $langcode) {
if (isset($this->values[$property_name][$langcode])) {
$value = $this->values[$property_name][$langcode];
}
// @todo: Make entities implement the TypedDataInterface.
$this->fields[$property_name][$langcode] = typed_data()->getPropertyInstance($this, $property_name, $value);
}
}
......@@ -179,8 +179,8 @@ protected function getTranslatedField($property_name, $langcode) {
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
*/
public function set($property_name, $value) {
$this->get($property_name)->setValue($value);
public function set($property_name, $value, $notify = TRUE) {
$this->get($property_name)->setValue($value, FALSE);
}
/**
......@@ -318,9 +318,7 @@ public function getTranslation($langcode, $strict = TRUE) {
);
$translation = typed_data()->create($translation_definition, $fields);
$translation->setStrictMode($strict);
if ($translation instanceof ContextAwareInterface) {
$translation->setContext('@' . $langcode, $this);
}
$translation->setContext('@' . $langcode, $this);
return $translation;
}
......@@ -435,7 +433,7 @@ public function &__get($name) {
*/
public function __set($name, $value) {
// Support setting values via property objects.
if ($value instanceof TypedDataInterface) {
if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) {
$value = $value->getValue();
}
// If this is an entity field, handle it accordingly. We first check whether
......@@ -508,9 +506,7 @@ public function __clone() {
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]->setContext($name, $this);
}
$this->fields[$name][$langcode]->setContext($name, $this);
}
}
}
......
......@@ -8,9 +8,7 @@
namespace Drupal\Core\Entity\Field;
use Drupal\Core\TypedData\AccessibleInterface;
use Drupal\Core\TypedData\ContextAwareInterface;
use Drupal\Core\TypedData\ListInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Interface for fields, being lists of field items.
......@@ -27,7 +25,7 @@
* When implementing this interface which extends Traversable, make sure to list
* IteratorAggregate or Iterator before this interface in the implements clause.
*/
interface FieldInterface extends ListInterface, AccessibleInterface, ContextAwareInterface, TypedDataInterface {
interface FieldInterface extends ListInterface, AccessibleInterface {
/**
* Gets a property object from the first field item.
......
......@@ -7,13 +7,10 @@
namespace Drupal\Core\Entity\Field;
use Drupal\Core\TypedData\ContextAwareTypedData;
use Drupal\Core\TypedData\ContextAwareInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\Type\Map;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\user;
use ArrayIterator;
use IteratorAggregate;
use InvalidArgumentException;
/**
* An entity field item.
......@@ -23,61 +20,20 @@
*
* @see \Drupal\Core\Entity\Field\FieldItemInterface
*/
abstract class FieldItemBase extends ContextAwareTypedData implements IteratorAggregate, FieldItemInterface {
abstract class FieldItemBase extends Map implements FieldItemInterface {
/**
* The array of properties, each implementing the TypedDataInterface.
*
* Field objects are instantiated during object construction and cannot be
* replaced by others, so computed properties can safely store references on
* other properties.
*
* @var array
*/
protected $properties = array();
/**
* Holds any non-property values that get set on the object.
*
* @todo: Remove or refactor once EntityNG conversion is complete.
*
* @var array
*/
protected $extraValues = array();
/**
* Overrides ContextAwareTypedData::__construct().
* Overrides \Drupal\Core\TypedData\TypedData::__construct().
*/
public function __construct(array $definition, $name = NULL, ContextAwareInterface $parent = NULL) {
public function __construct(array $definition, $name = NULL, TypedDataInterface $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
// references on non-computed properties during construction.
$step2 = array();
// Initialize computed properties by default, such that they get cloned
// with the whole item.
foreach ($this->getPropertyDefinitions() as $name => $definition) {
if (empty($definition['computed'])) {
if (!empty($definition['computed'])) {
$this->properties[$name] = typed_data()->getPropertyInstance($this, $name);
}
else {
$step2[] = $name;
}
}
foreach ($step2 as $name) {
$this->properties[$name] = typed_data()->getPropertyInstance($this, $name);
}
}
/**
* Overrides \Drupal\Core\TypedData\TypedData::getValue().
*/
public function getValue() {
$values = array();
foreach ($this->getProperties() as $name => $property) {
$values[$name] = $property->getValue();
}
return $values + $this->extraValues;
}
/**
......@@ -86,66 +42,59 @@ public function getValue() {
* @param array|null $values
* An array of property values.
*/
public function setValue($values) {
public function setValue($values, $notify = TRUE) {
// Treat the values as property value of the first property, if no array is
// given.
if (!is_array($values)) {
$keys = array_keys($this->properties);
if (isset($values) && !is_array($values)) {
$keys = array_keys($this->getPropertyDefinitions());
$values = array($keys[0] => $values);
}
// Notify the parent of any changes to be made.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
$this->values = $values;
// Update any existing property objects.
foreach ($this->properties as $name => $property) {
$value = NULL;
if (isset($values[$name])) {
$property->setValue($values[$name]);
}
else {
$property->setValue(NULL);
$value = $values[$name];
}
unset($values[$name]);
$property->setValue($value, FALSE);
unset($this->values[$name]);
}
// @todo: Throw an exception for invalid values once conversion is
// totally completed.
$this->extraValues = $values;
}
/**
* Overrides \Drupal\Core\TypedData\TypedData::getString().
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__get().
*/
public function getString() {
$strings = array();
foreach ($this->getProperties() as $property) {
$strings[] = $property->getString();
public function __get($name) {
// There is either a property object or a plain value - possibly for a
// not-defined property. If we have a plain value, directly return it.
if (isset($this->values[$name])) {
return $this->values[$name];
}
return implode(', ', array_filter($strings));
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
*/
public function get($property_name) {
if (!isset($this->properties[$property_name])) {
throw new InvalidArgumentException('Field ' . check_plain($property_name) . ' is unknown.');
elseif (isset($this->properties[$name])) {
return $this->properties[$name]->getValue();
}
return $this->properties[$property_name];
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
* Overrides \Drupal\Core\TypedData\Type\Map::set().
*/
public function set($property_name, $value) {
$this->get($property_name)->setValue($value);
}
/**
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__get().
*/
public function __get($name) {
if (isset($this->properties[$name])) {
return $this->properties[$name]->getValue();
public function set($property_name, $value, $notify = TRUE) {
// Notify the parent of any changes to be made.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
// For defined properties there is either a property object or a plain
// value that needs to be updated.
if (isset($this->properties[$property_name])) {
$this->properties[$property_name]->setValue($value, FALSE);
}
// The property is unknown, so try to get it from the extra values.
elseif (isset($this->extraValues[$name])) {
return $this->extraValues[$name];
// Allow setting plain values for not-defined properties also.
else {
$this->values[$property_name] = $value;
}
}
......@@ -153,105 +102,38 @@ public function __get($name) {
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__set().
*/
public function __set($name, $value) {
// Support setting values via property objects.
if ($value instanceof TypedDataInterface) {
// Support setting values via property objects, but take care in as the
// value of the 'entity' property is typed data also.
if ($value instanceof TypedDataInterface && !($value instanceof EntityInterface)) {
$value = $value->getValue();
}
if (isset($this->properties[$name])) {
$this->properties[$name]->setValue($value);
}
else {
// The property is unknown, so set it to the extra values.
$this->extraValues[$name] = $value;
}
$this->set($name, $value);
}
/**
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__isset().
*/
public function __isset($name) {
return isset($this->properties[$name]) && $this->properties[$name]->getValue() !== NULL;
return isset($this->values[$name]) || (isset($this->properties[$name]) && $this->properties[$name]->getValue() !== NULL);
}
/**
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__unset().
*/
public function __unset($name) {
if (isset($this->properties[$name])) {
$this->properties[$name]->setValue(NULL);
}
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties().
*/
public function getProperties($include_computed = FALSE) {
$properties = array();
foreach ($this->getPropertyDefinitions() as $name => $definition) {
if ($include_computed || empty($definition['computed'])) {
$properties[$name] = $this->properties[$name];
}
}
return $properties;
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
*/
public function getPropertyValues() {
return $this->getValue();
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
*/
public function setPropertyValues($values) {
foreach ($values as $name => $value) {
$this->get($name)->setValue($value);
}
$this->set($name, NULL);
}
/**
* Implements \IteratorAggregate::getIterator().
* Overrides \Drupal\Core\TypedData\Map::onChange().
*/
public function getIterator() {
return new ArrayIterator($this->getProperties());
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
*/
public function getPropertyDefinition($name) {
$definitions = $this->getPropertyDefinitions();
if (isset($definitions[$name])) {
return $definitions[$name];
}
else {
return FALSE;
}
}
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
*/
public function isEmpty() {
foreach ($this->getProperties() as $property) {