Commit 5fc86b00 authored by alexpott's avatar alexpott

Issue #1868004 by fago, das-peter, Berdir, EclipseGc, fubhy the cat, dixon_:...

Issue #1868004 by fago, das-peter, Berdir, EclipseGc, fubhy the cat, dixon_: Improve the TypedData API usage of EntityNG.
parent 1c9bd127
......@@ -53,15 +53,11 @@ public function get($property_name) {
* Implements Drupal\Core\TypedData\ComplexDataInterface::set().
*/
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)) {
$this->value[$property_name] = $value;
return $this->get($property_name);
$property = $this->get($property_name);
}
else {
// In these objects, when clearing the value, the property is gone.
......@@ -69,8 +65,12 @@ public function set($property_name, $value, $notify = TRUE) {
$property = $this->get($property_name);
unset($this->value[$property_name]);
$property->setValue($value);
return $property;
}
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
return $property;
}
/**
......
......@@ -11,7 +11,6 @@
use Drupal\Core\Language\Language;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\user\UserInterface;
use IteratorAggregate;
use Drupal\Core\Session\AccountInterface;
......@@ -425,62 +424,66 @@ public function getNGEntity() {
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getType().
* {@inheritdoc}
*/
public function getType() {
// @todo: Incorporate the entity type here by making entities proper
// typed data. See http://drupal.org/node/1868004.
return 'entity';
// @todo: This does not make much sense, so remove once TypedDataInterface
// is removed. See https://drupal.org/node/2002138.
if ($this->bundle() != $this->entityType()) {
return 'entity:' . $this->entityType() . ':' . $this->bundle();
}
return 'entity:' . $this->entityType();
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getDefinition().
* {@inheritdoc}
*/
public function getDefinition() {
// @todo: This does not make much sense, so remove once TypedDataInterface
// is removed. See https://drupal.org/node/2002138.
return array(
'type' => $this->getType()
);
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getValue().
* {@inheritdoc}
*/
public function getValue() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
// @todo: This does not make much sense, so remove once TypedDataInterface
// is removed. See https://drupal.org/node/2002138.
return $this->getPropertyValues();
}
/**
* 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.
// @todo: This does not make much sense, so remove once TypedDataInterface
// is removed. See https://drupal.org/node/2002138.
$this->setPropertyValues($value);
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getString().
* {@inheritdoc}
*/
public function getString() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
return $this->label();
}
/**
* Implements \Drupal\Core\TypedData\TypedDataInterface::getConstraints().
* {@inheritdoc}
*/
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().
* {@inheritdoc}
*/
public function validate() {
// @todo: Implement by making entities proper typed data. See
// http://drupal.org/node/1868004.
// @todo: Add the typed data manager as proper dependency.
return \Drupal::typedData()->getValidator()->validate($this);
}
/**
......
......@@ -184,6 +184,7 @@ public function __set($name, $value) {
// out of sync. That way, the next field object instantiated by EntityNG
// will hold the updated value.
unset($this->decorated->fields[$name]);
$this->decorated->onChange($name);
}
/**
......
......@@ -9,6 +9,7 @@
use Drupal\Core\TypedData\AccessibleInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\IdentifiableInterface;
use Drupal\Core\TypedData\TranslatableInterface;
/**
......@@ -27,16 +28,7 @@
* @see \Drupal\Core\TypedData\TypedDataManager
* @see \Drupal\Core\Field\FieldInterface
*/
interface EntityInterface extends ComplexDataInterface, AccessibleInterface, TranslatableInterface {
/**
* Returns the entity identifier (the entity's machine name or numeric ID).
*
* @return
* The identifier of the entity, or NULL if the entity does not yet have
* an identifier.
*/
public function id();
interface EntityInterface extends IdentifiableInterface, ComplexDataInterface, AccessibleInterface, TranslatableInterface {
/**
* Returns the entity UUID (Universally Unique Identifier).
......
......@@ -167,15 +167,6 @@ public function __construct(array $values, $entity_type, $bundle = FALSE, $trans
$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.
*/
......@@ -458,7 +449,7 @@ protected function getDefaultLanguage() {
}
if (empty($this->language)) {
// Make sure we return a proper language object.
$this->language = new Language(array('id' => Language::LANGCODE_NOT_SPECIFIED));
$this->language = new Language(array('id' => Language::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE));
}
}
return $this->language;
......@@ -841,12 +832,4 @@ public function label($langcode = NULL) {
return $label;
}
/**
* {@inheritdoc}
*/
public function validate() {
// @todo: Add the typed data manager as proper dependency.
return \Drupal::typedData()->getValidator()->validate($this);
}
}
......@@ -77,10 +77,6 @@ public function getValue($include_computed = FALSE) {
* Overrides \Drupal\Core\TypedData\ItemList::setValue().
*/
public function setValue($values, $notify = TRUE) {
// Notify the parent of any changes to be made.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
if (!isset($values) || $values === array()) {
$this->list = $values;
}
......@@ -108,6 +104,10 @@ public function setValue($values, $notify = TRUE) {
}
}
}
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
/**
......
......@@ -49,10 +49,6 @@ public function setValue($values, $notify = TRUE) {
$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) {
......@@ -63,6 +59,10 @@ public function setValue($values, $notify = TRUE) {
$property->setValue($value, FALSE);
unset($this->values[$name]);
}
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
/**
......@@ -83,10 +83,6 @@ public function __get($name) {
* {@inheritdoc}
*/
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])) {
......@@ -97,6 +93,10 @@ public function set($property_name, $value, $notify = TRUE) {
else {
$this->values[$property_name] = $value;
}
// Directly notify ourselves.
if ($notify) {
$this->onChange($property_name);
}
}
/**
......@@ -135,7 +135,9 @@ public function onChange($property_name) {
}
// Remove the plain value, such that any further __get() calls go via the
// updated property object.
unset($this->values[$property_name]);
if (isset($this->properties[$property_name])) {
unset($this->values[$property_name]);
}
}
/**
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver.
*/
namespace Drupal\Core\Entity\Plugin\DataType\Deriver;
use Drupal\Component\Plugin\Derivative\DerivativeInterface;
/**
* Provides data type plugins for each existing entity type and bundle.
*/
class EntityDeriver implements DerivativeInterface {
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
if (isset($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions(array $base_plugin_definition) {
// Also keep the 'entity' defined as is.
$this->derivatives[''] = $base_plugin_definition;
// Add definitions for each entity type and bundle.
foreach (entity_get_info() as $entity_type => $info) {
$this->derivatives[$entity_type] = array(
'label' => $info['label'],
'class' => $info['class'],
'constraints' => array('EntityType' => $entity_type),
) + $base_plugin_definition;
// Incorporate the bundles as entity:$entity_type:$bundle, if any.
foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
if ($bundle !== $entity_type) {
$this->derivatives[$entity_type . ':' . $bundle] = array(
'label' => $bundle_info['label'],
'class' => $info['class'],
'constraints' => array(
'EntityType' => $entity_type,
'Bundle' => $bundle,
),
) + $base_plugin_definition;
}
}
}
return $this->derivatives;
}
}
......@@ -2,17 +2,17 @@
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\FieldDataTypeDerivative.
* Contains \Drupal\Core\Entity\Plugin\DataType\Deriver\FieldItemDeriver.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
namespace Drupal\Core\Entity\Plugin\DataType\Deriver;
use Drupal\Component\Plugin\Derivative\DerivativeInterface;
/**
* Provides data type plugins for each existing field type plugin.
*/
class FieldDataTypeDerivative implements DerivativeInterface {
class FieldItemDeriver implements DerivativeInterface {
/**
* List of derivative definitions.
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\Entity.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
/**
* Defines the base plugin for deriving data types for entity types.
*
* Note that the class only registers the plugin, and is actually never used.
* \Drupal\Core\Entity\Entity is available for use as base class.
*
* @DataType(
* id = "entity",
* label = @Translation("Entity"),
* description = @Translation("All kind of entities, e.g. nodes, comments or users."),
* derivative = "\Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver"
* )
*/
abstract class Entity {
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityReference.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\TypedData\DataReferenceBase;
/**
* Defines an 'entity_reference' data type.
*
* This serves as 'entity' property of entity reference field items and gets
* its value set from the parent, i.e. LanguageItem.
*
* The plain value of this reference is the entity object, i.e. an instance of
* \Drupal\Core\Entity\EntityInterface. For setting the value the entity object
* or the entity ID may be passed, whereas passing the ID is only supported if
* an 'entity type' constraint is specified.
*
* Some supported constraints (below the definition's 'constraints' key) are:
* - EntityType: The entity type. Required.
* - Bundle: (optional) The bundle or an array of possible bundles.
*
* @DataType(
* id = "entity_reference",
* label = @Translation("Entity reference")
* )
*/
class EntityReference extends DataReferenceBase {
/**
* The entity ID.
*
* @var integer|string
*/
protected $id;
/**
* {@inheritdoc}
*/
public function getTargetDefinition() {
$definition = array(
'type' => 'entity',
);
if (isset($this->definition['constraints']['EntityType'])) {
$definition['type'] .= ':' . $this->definition['constraints']['EntityType'];
}
if (isset($this->definition['constraints']['Bundle']) && is_string($this->definition['constraints']['Bundle'])) {
$definition['type'] .= ':' . $this->definition['constraints']['Bundle'];
}
return $definition;
}
/**
* {@inheritdoc}
*/
public function getTarget() {
if (!isset($this->target) && isset($this->id)) {
// If we have a valid reference, return the entity object which is typed
// data itself.
$this->target = entity_load($this->definition['constraints']['EntityType'], $this->id);
}
return $this->target;
}
/**
* {@inheritdoc}
*/
public function getTargetIdentifier() {
if (isset($this->id)) {
return $this->id;
}
elseif ($entity = $this->getValue()) {
return $entity->id();
}
}
/**
* {@inheritdoc}
*/
public function getValue() {
// Entities are already typed data, so just return that.
return $this->getTarget();
}
/**
* {@inheritdoc}
*/
public function setValue($value, $notify = TRUE) {
unset($this->target);
unset($this->id);
// Both the entity ID and the entity object may be passed as value. The
// reference may also be unset by passing NULL as value.
if (!isset($value) || $value instanceof EntityInterface) {
// Ensure we reference a NG Entity object.
if (isset($value)) {
$value = $value->getNGEntity();
}
$this->target = $value;
}
elseif (!is_scalar($value) || empty($this->definition['constraints']['EntityType'])) {
throw new \InvalidArgumentException('Value is not a valid entity.');
}
else {
$this->id = $value;
}
// Notify the parent of any changes.
if ($notify && isset($this->parent)) {
$this->parent->onChange($this->name);
}
}
/**
* {@inheritdoc}
*/
public function getString() {
if ($entity = $this->getValue()) {
return $entity->label();
}
return '';
}
}
......@@ -13,10 +13,13 @@
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Defines the 'entity_reference' entity field item.
* Defines the 'entity_reference_item' entity field item.
*
* Required settings (below the definition's 'settings' key) are:
* - target_type: The entity type to reference.
* Supported settings (below the definition's 'settings' key) are:
* - target_type: The entity type to reference. Required.
* - target_bundle: (optional): If set, restricts the entity bundles which may
* may be referenced. May be set to an single bundle, or to an array of
* allowed bundles.
*
* @DataType(
* id = "entity_reference_field",
......@@ -40,11 +43,12 @@ class EntityReferenceItem extends FieldItemBase {
* Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
*/
public function getPropertyDefinitions() {
// Definitions vary by entity type, so key them by entity type.
$target_type = $this->definition['settings']['target_type'];
// Definitions vary by entity type and bundle, so key them accordingly.
$key = $this->definition['settings']['target_type'] . ':';
$key .= isset($this->definition['settings']['target_bundle']) ? $this->definition['settings']['target_bundle'] : '';
if (!isset(self::$propertyDefinitions[$target_type])) {
static::$propertyDefinitions[$target_type]['target_id'] = array(
if (!isset(static::$propertyDefinitions[$key])) {
static::$propertyDefinitions[$key]['target_id'] = array(
// @todo: Lookup the entity type's ID data type and use it here.
'type' => 'integer',
'label' => t('Entity ID'),
......@@ -52,20 +56,22 @@ public function getPropertyDefinitions() {
'Range' => array('min' => 0),
),
);
static::$propertyDefinitions[$target_type]['entity'] = array(
'type' => 'entity',
static::$propertyDefinitions[$key]['entity'] = array(
'type' => 'entity_reference',
'constraints' => array(
'EntityType' => $target_type,
'EntityType' => $this->definition['settings']['target_type'],
),
'label' => t('Entity'),
'description' => t('The referenced entity'),
// The entity object is computed out of the entity ID.
'computed' => TRUE,
'read-only' => FALSE,
'settings' => array('id source' => 'target_id'),
);
if (isset($this->definition['settings']['target_bundle'])) {
static::$propertyDefinitions[$key]['entity']['constraints']['Bundle'] = $this->definition['settings']['target_bundle'];
}
}
return static::$propertyDefinitions[$target_type];
return static::$propertyDefinitions[$key];
}
/**
......@@ -96,12 +102,14 @@ public function __isset($property_name) {
* Overrides \Drupal\Core\Entity\Field\FieldItemBase::get().
*/
public function setValue($values, $notify = TRUE) {
// Treat the values as value of the entity property, if no array is
// given as this handles entity IDs and objects.
if (isset($values) && !is_array($values)) {
// Directly update the property instead of invoking the parent, so that
// the entity property can take care of updating the ID property.
// Directly update the property instead of invoking the parent, so it can
// handle objects and IDs.
$this->properties['entity']->setValue($values, $notify);
// If notify was FALSE, ensure the target_id property gets synched.
if (!$notify) {
$this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE);
}
}
else {
// Make sure that the 'entity' property gets set as 'target_id'.
......@@ -111,4 +119,18 @@ public function setValue($values, $notify = TRUE) {
parent::setValue($values, $notify);
}
}
/**
* {@inheritdoc}
*/
public function onChange($property_name) {
// Make sure that the target ID and the target property stay in sync.
if ($property_name == 'target_id') {
$this->properties['entity']->setValue($this->target_id, FALSE);
}
elseif ($property_name == 'entity') {
$this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE);
}
parent::onChange($property_name);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityWrapper.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TypedData;
use Drupal\Core\TypedData\TypedDataInterface;
use ArrayIterator;
use IteratorAggregate;
use InvalidArgumentException;
/**
* Defines an 'entity' data type, e.g. the computed 'entity' property of entity references.
*
* This object wraps the regular entity object and implements the
* ComplexDataInterface by forwarding most of its methods to the wrapped entity
* (if set).
*
* The plain value of this wrapper is the entity object, i.e. an instance of
* Drupal\Core\Entity\EntityInterface. For setting the value the entity object
* or the entity ID may be passed, whereas passing the ID is only supported if
* an 'entity type' constraint is specified.
*
* Supported constraints (below the definition's 'constraints' key) are:
* - EntityType: The entity type.
* - Bundle: The bundle or an array of possible bundles.
*
* Supported settings (below the definition's 'settings' key) are:
* - id source: If used as computed property, the ID property used to load
* the entity object.
*
* @DataType(
* id = "entity",
* label = @Translation("Entity"),
* description = @Translation("All kind of entities, e.g. nodes, comments or users.")
* )
*/
class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataInterface {
/**
* The referenced entity type.
*
* @var string
*/
protected $entityType;
/**
* The entity ID if no 'id source' is used.
*
* @var string
*/
protected $id;
/**
* If set, a new entity to create and reference.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $newEntity;
/**
* Overrides TypedData::__construct().
*/
public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) {
parent::__construct($definition, $name, $parent);
$this->entityType = isset($this->definition['constraints']['EntityType']) ? $this->definition['constraints']['EntityType'] : NULL;
}
/**
* Overrides \Drupal\Core\TypedData\TypedData::getValue().
*/
public function getValue() {
if (iss