Commit f6161fad authored by alexpott's avatar alexpott

Issue #2002134 by fago, yched, amateescu, effulgentsia: Move TypedData...

Issue #2002134 by fago, yched, amateescu, effulgentsia: Move TypedData metadata introspection from data objects to definition objects.
parent 1747f674
......@@ -108,7 +108,13 @@ public function setPropertyValues($values) {
}
/**
* Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
* Gets the definition of a contained property.
*
* @param string $name
* The name of property.
*
* @return array|FALSE
* The definition of the property or FALSE if the property does not exist.
*/
public function getPropertyDefinition($name) {
if (isset($this->definition['mapping'][$name])) {
......@@ -120,7 +126,11 @@ public function getPropertyDefinition($name) {
}
/**
* Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
* Gets an array of property definitions of contained properties.
*
* @return \Drupal\Core\TypedData\DataDefinitionInterface[]
* An array of property definitions of contained properties, keyed by
* property name.
*/
public function getPropertyDefinitions() {
$list = array();
......
......@@ -9,9 +9,9 @@
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Language\Language;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\TypedDataInterface;
/**
......@@ -66,7 +66,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
/**
* Local cache for field definitions.
*
* @see ContentEntityBase::getPropertyDefinitions()
* @see ContentEntityBase::getFieldDefinitions()
*
* @var array
*/
......@@ -211,16 +211,12 @@ public function preSaveRevision(EntityStorageControllerInterface $storage_contro
/**
* {@inheritdoc}
*/
public function getDefinition() {
// @todo: This does not make much sense, so remove once TypedDataInterface
// is removed. See https://drupal.org/node/2002138.
public function getDataDefinition() {
$definition = EntityDataDefinition::create($this->getEntityTypeId());
if ($this->bundle() != $this->getEntityTypeId()) {
$type = 'entity:' . $this->getEntityTypeId() . ':' . $this->bundle();
$definition->setBundles(array($this->bundle()));
}
else {
$type = 'entity:' . $this->getEntityTypeId();
}
return DataDefinition::create($type);
return $definition;
}
/**
......@@ -378,7 +374,7 @@ public function uuid() {
* {@inheritdoc}
*/
public function hasField($field_name) {
return (bool) $this->getPropertyDefinition($field_name);
return (bool) $this->getFieldDefinition($field_name);
}
/**
......@@ -404,7 +400,7 @@ protected function getTranslatedField($name, $langcode) {
// Populate $this->fields to speed-up further look-ups and to keep track of
// fields objects, possibly holding changes to field values.
if (!isset($this->fields[$name][$langcode])) {
$definition = $this->getPropertyDefinition($name);
$definition = $this->getFieldDefinition($name);
if (!$definition) {
throw new \InvalidArgumentException('Field ' . String::checkPlain($name) . ' is unknown.');
}
......@@ -454,7 +450,7 @@ public function set($name, $value, $notify = TRUE) {
*/
public function getProperties($include_computed = FALSE) {
$properties = array();
foreach ($this->getPropertyDefinitions() as $name => $definition) {
foreach ($this->getFieldDefinitions() as $name => $definition) {
if ($include_computed || !$definition->isComputed()) {
$properties[$name] = $this->get($name);
}
......@@ -472,9 +468,9 @@ public function getIterator() {
/**
* {@inheritdoc}
*/
public function getPropertyDefinition($name) {
public function getFieldDefinition($name) {
if (!isset($this->fieldDefinitions)) {
$this->getPropertyDefinitions();
$this->getFieldDefinitions();
}
if (isset($this->fieldDefinitions[$name])) {
return $this->fieldDefinitions[$name];
......@@ -487,7 +483,7 @@ public function getPropertyDefinition($name) {
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
public function getFieldDefinitions() {
if (!isset($this->fieldDefinitions)) {
$bundle = $this->bundle != $this->entityTypeId ? $this->bundle : NULL;
$this->fieldDefinitions = \Drupal::entityManager()->getFieldDefinitions($this->entityTypeId, $bundle);
......@@ -566,7 +562,7 @@ public function language() {
*/
protected function setDefaultLangcode() {
// Get the language code if the property exists.
if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
if ($this->hasField('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
$this->defaultLangcode = $item->language->id;
}
if (empty($this->defaultLangcode)) {
......@@ -611,7 +607,7 @@ public function onChange($name) {
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Entity\EntityInterface
* @return \Drupal\Core\Entity\ContentEntityInterface
*/
public function getTranslation($langcode) {
// Ensure we always use the default language code when dealing with the
......@@ -734,7 +730,7 @@ public function addTranslation($langcode, array $values = array()) {
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
$translation = $this->getTranslation($langcode);
$definitions = $translation->getPropertyDefinitions();
$definitions = $translation->getFieldDefinitions();
foreach ($values as $name => $value) {
if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
......@@ -750,7 +746,7 @@ public function addTranslation($langcode, array $values = array()) {
*/
public function removeTranslation($langcode) {
if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
foreach ($this->getPropertyDefinitions() as $name => $definition) {
foreach ($this->getFieldDefinitions() as $name => $definition) {
if ($definition->isTranslatable()) {
unset($this->values[$name][$langcode]);
unset($this->fields[$name][$langcode]);
......@@ -804,7 +800,7 @@ public function updateOriginalValues() {
if (!$this->fields) {
return;
}
foreach ($this->getPropertyDefinitions() as $name => $definition) {
foreach ($this->getFieldDefinitions() as $name => $definition) {
if (!$definition->isComputed() && !empty($this->fields[$name])) {
foreach ($this->fields[$name] as $langcode => $item) {
$item->filterEmptyItems();
......@@ -826,9 +822,9 @@ public function &__get($name) {
if (isset($this->fields[$name][$this->activeLangcode])) {
return $this->fields[$name][$this->activeLangcode];
}
// Inline getPropertyDefinition() to speed up things.
// Inline getFieldDefinition() to speed up things.
if (!isset($this->fieldDefinitions)) {
$this->getPropertyDefinitions();
$this->getFieldDefinitions();
}
if (isset($this->fieldDefinitions[$name])) {
$return = $this->getTranslatedField($name, $this->activeLangcode);
......@@ -930,7 +926,7 @@ public function __clone() {
// Avoid deep-cloning when we are initializing a translation object, since
// it will represent the same entity, only with a different active language.
if (!$this->translationInitialize) {
$definitions = $this->getPropertyDefinitions();
$definitions = $this->getFieldDefinitions();
foreach ($this->fields as $name => $values) {
$this->fields[$name] = array();
// Untranslatable fields may have multiple references for the same field
......
......@@ -139,7 +139,8 @@ public function buildEntity(array $form, array &$form_state) {
// edited by this form. Values of fields handled by field API are copied
// by field_attach_extract_form_values() below.
$values_excluding_fields = $entity_type->isFieldable() ? array_diff_key($form_state['values'], field_info_instances($entity_type_id, $entity->bundle())) : $form_state['values'];
$definitions = $entity->getPropertyDefinitions();
$definitions = $entity->getFieldDefinitions();
foreach ($values_excluding_fields as $key => $value) {
if (isset($definitions[$key])) {
$entity->$key = $value;
......
......@@ -72,4 +72,25 @@ public static function baseFieldDefinitions($entity_type);
*/
public function hasField($field_name);
/**
* Gets the definition of a contained field.
*
* @param string $name
* The name of the field.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface|false
* The definition of the field or FALSE if the field does not exist.
*/
public function getFieldDefinition($name);
/**
* Gets an array of field definitions of all contained fields.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of field definitions, keyed by field name.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
*/
public function getFieldDefinitions();
}
......@@ -371,14 +371,6 @@ public function getFieldDefinitions($entity_type_id, $bundle = NULL) {
}
}
/**
* {@inheritdoc}
*/
public function getFieldDefinitionsByConstraints($entity_type, array $constraints) {
// @todo: Add support for specifying multiple bundles.
return $this->getFieldDefinitions($entity_type, isset($constraints['Bundle']) ? $constraints['Bundle'] : NULL);
}
/**
* {@inheritdoc}
*/
......
......@@ -38,7 +38,6 @@ public function getEntityTypeLabels();
* An array of entity field definitions, keyed by field name.
*
* @see \Drupal\Core\TypedData\TypedDataManager::create()
* @see \Drupal\Core\Entity\EntityManager::getFieldDefinitionsByConstraints()
*/
public function getFieldDefinitions($entity_type_id, $bundle = NULL);
......@@ -69,30 +68,6 @@ public function getAccessController($entity_type);
*/
public function getAdminRouteInfo($entity_type_id, $bundle);
/**
* Gets an array of entity field definitions based on validation constraints.
*
* @param string $entity_type
* The entity type to get field definitions for.
* @param array $constraints
* An array of entity constraints as used for entities in typed data
* definitions, i.e. an array optionally including a 'Bundle' key.
* For example the constraints used by an entity reference could be:
*
* @code
* array(
* 'Bundle' => 'article',
* )
* @endcode
*
* @return array
* An array of field definitions of entity fields, keyed by field
* name.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
*/
public function getFieldDefinitionsByConstraints($entity_type, array $constraints);
/**
* Creates a new storage controller instance.
*
......
......@@ -667,7 +667,7 @@ protected function savePropertyData(EntityInterface $entity, $table_key = 'data_
protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'base_table') {
$record = new \stdClass();
$values = array();
$definitions = $entity->getPropertyDefinitions();
$definitions = $entity->getFieldDefinitions();
$schema = drupal_get_schema($this->entityType->get($table_key));
$is_new = $entity->isNew();
......
......@@ -17,7 +17,8 @@
* 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"
* derivative = "\Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver",
* definition_class = "\Drupal\Core\Entity\TypedData\EntityDataDefinition"
* )
*/
abstract class Entity {
......
......@@ -18,16 +18,22 @@
*
* 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.
* or the entity ID may be passed.
*
* 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.
* Note that the definition of the referenced entity's type is required, whereas
* defining referencable entity bundle(s) is optional. A reference defining the
* type and bundle of the referenced entity can be created as following:
* @code
* $definition = \Drupal\Core\Entity\EntityDefinition::create($entity_type)
* ->addConstraint('Bundle', $bundle);
* \Drupal\Core\TypedData\DataReferenceDefinition::create('entity')
* ->setTargetDefinition($definition);
* @endcode
*
* @DataType(
* id = "entity_reference",
* label = @Translation("Entity reference")
* label = @Translation("Entity reference"),
* definition_class = "\Drupal\Core\TypedData\DataReferenceDefinition"
* )
*/
class EntityReference extends DataReferenceBase {
......@@ -40,19 +46,13 @@ class EntityReference extends DataReferenceBase {
protected $id;
/**
* {@inheritdoc}
* Returns the definition of the referenced entity.
*
* @return \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface
* The reference target's definition.
*/
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;
return $this->definition->getTargetDefinition();
}
/**
......@@ -62,7 +62,7 @@ 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->getConstraint('EntityType'), $this->id);
$this->target = entity_load($this->getTargetDefinition()->getEntityTypeId(), $this->id);
}
return $this->target;
}
......@@ -99,7 +99,7 @@ public function setValue($value, $notify = TRUE) {
if (!isset($value) || $value instanceof EntityInterface) {
$this->target = $value;
}
elseif (!is_scalar($value) || (($constraints = $this->definition->getConstraints()) && empty($constraints['EntityType']))) {
elseif (!is_scalar($value) || $this->getTargetDefinition()->getEntityTypeId() === NULL) {
throw new \InvalidArgumentException('Value is not a valid entity.');
}
else {
......
......@@ -29,7 +29,7 @@ public function validate($value, Constraint $constraint) {
}
$referenced_entity = $value->get('entity')->getTarget();
if (!$referenced_entity) {
$type = $value->getDefinition()->getSetting('target_type');
$type = $value->getFieldDefinition()->getSetting('target_type');
$this->context->addViolation($constraint->message, array('%type' => $type, '%id' => $id));
}
}
......
......@@ -144,7 +144,7 @@ public function addField($field, $type, $langcode) {
$entity = $entity_manager
->getStorageController($entity_type_id)
->create($values);
$propertyDefinitions = $entity->$field_name->getPropertyDefinitions();
$propertyDefinitions = $entity->$field_name->getFieldDefinition()->getPropertyDefinitions();
// If the column is not yet known, ie. the
// $node->field_image->entity case then use first property as
......@@ -198,14 +198,14 @@ public function addField($field, $type, $langcode) {
$entity = $entity_manager
->getStorageController($entity_type_id)
->create($values);
$propertyDefinitions = $entity->$specifier->getPropertyDefinitions();
$propertyDefinitions = $entity->$specifier->getFieldDefinition()->getPropertyDefinitions();
$relationship_specifier = $specifiers[$key + 1];
$next_index_prefix = $relationship_specifier;
}
// Check for a valid relationship.
if (isset($propertyDefinitions[$relationship_specifier]) && $entity->get($specifier)->first()->get('entity') instanceof EntityReference) {
// If it is, use the entity type.
$entity_type_id = $propertyDefinitions[$relationship_specifier]->getConstraint('EntityType');
$entity_type_id = $propertyDefinitions[$relationship_specifier]->getTargetDefinition()->getEntityTypeId();
$entity_type = $entity_manager->getDefinition($entity_type_id);
// Add the new entity base table using the table and sql column.
$join_condition= '%alias.' . $entity_type->getKey('id') . " = $table.$sql_column";
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\TypedData\EntityDataDefinition.
*/
namespace Drupal\Core\Entity\TypedData;
use Drupal\Core\TypedData\ComplexDataDefinitionBase;
/**
* A typed data definition class for describing entities.
*/
class EntityDataDefinition extends ComplexDataDefinitionBase implements EntityDataDefinitionInterface {
/**
* Creates a new entity definition.
*
* @param string $entity_type_id
* (optional) The ID of the entity type, or NULL if the entity type is
* unknown. Defaults to NULL.
*
* @return static
*/
public static function create($entity_type_id = NULL) {
$definition = new static(array());
// Set the passed entity type.
if (isset($entity_type_id)) {
$definition->setEntityTypeId($entity_type_id);
}
return $definition;
}
/**
* {@inheritdoc}
*/
public static function createFromDataType($data_type) {
$parts = explode(':', $data_type);
if ($parts[0] != 'entity') {
throw new \InvalidArgumentException('Data type must be in the form of "entity:ENTITY_TYPE:BUNDLE."');
}
$definition = static::create();
// Set the passed entity type and bundle.
if (isset($parts[1])) {
$definition->setEntityTypeId($parts[1]);
}
if (isset($parts[2])) {
$definition->setBundles(array($parts[2]));
}
return $definition;
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions() {
if (!isset($this->propertyDefinitions)) {
if ($entity_type_id = $this->getEntityTypeId()) {
// @todo: Add support for handling multiple bundles.
// See https://drupal.org/node/2169813.
$bundles = $this->getBundles();
$bundle = is_array($bundles) && count($bundles) == 1 ? reset($bundles) : NULL;
$this->propertyDefinitions = \Drupal::entityManager()->getFieldDefinitions($entity_type_id, $bundle);
}
else {
// No entity type given.
$this->propertyDefinitions = array();
}
}
return $this->propertyDefinitions;
}
/**
* {@inheritdoc}
*/
public function getDataType() {
$type = 'entity';
if ($entity_type = $this->getEntityTypeId()) {
$type .= ':' . $entity_type;
// Append the bundle only if we know it for sure.
if (($bundles = $this->getBundles()) && count($bundles) == 1) {
$type .= ':' . reset($bundles);
}
}
return $type;
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->getConstraint('EntityType');
}
/**
* {@inheritdoc}
*/
public function setEntityTypeId($entity_type_id) {
return $this->addConstraint('EntityType', $entity_type_id);
}
/**
* {@inheritdoc}
*/
public function getBundles() {
$bundle = $this->getConstraint('Bundle');
return is_string($bundle) ? array($bundle) : $bundle;
}
/**
* {@inheritdoc}
*/
public function setBundles(array $bundles = NULL) {
if (isset($bundles)) {
$this->addConstraint('Bundle', $bundles);
}
else {
// Remove the constraint.
$constraints = $this->getConstraints();
unset($constraints['Bundle']);
$this->setConstraints($constraints);
}
return $this;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface.
*/
namespace Drupal\Core\Entity\TypedData;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
/**
* Interface for typed data entity definitions.
*/
interface EntityDataDefinitionInterface extends ComplexDataDefinitionInterface {
/**
* Returns the entity type ID.
*
* @return string|null
* The entity type ID, or NULL if the entity type is unknown.
*/
public function getEntityTypeId();
/**
* Sets the entity type ID.
*
* @param string $entity_type_id
* The entity type to set.
*
* @return $this
*/
public function setEntityTypeId($entity_type_id);
/**
* Returns the array of possible entity bundles.
*
* @return array|null
* The array of possible bundles, or NULL for any.
*/
public function getBundles();
/**
* Sets the array of possible entity bundles.
*
* @param array|null $bundles
* The array of possible bundles, or NULL for any.
*
* @return $this
*/
public function setBundles(array $bundles = NULL);
}
......@@ -8,14 +8,30 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\ListDefinition;
use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
use Drupal\Core\TypedData\ListDataDefinition;
use Drupal\field\FieldException;
/**
* A class for defining entity fields.
*/
class FieldDefinition extends ListDefinition implements FieldDefinitionInterface {
class FieldDefinition extends ListDataDefinition implements FieldDefinitionInterface {
/**
* The field type.
*
* @var string
*/
protected $type;
/**
* An array of field property definitions.
*
* @var \Drupal\Core\TypedData\DataDefinitionInterface[]
*
* @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
*/
protected $propertyDefinitions;
/**
* The field schema.
......@@ -39,15 +55,25 @@ class FieldDefinition extends ListDefinition implements FieldDefinitionInterface
* A new field definition object.
*/
public static function create($type) {
$field_definition = new static(array());
$field_definition->type = $type;
$field_definition->itemDefinition = FieldItemDataDefinition::create($field_definition);
// Create a definition for the items, and initialize it with the default
// settings for the field type.
// @todo Cleanup in https://drupal.org/node/2116341.
$item_definition = DataDefinition::create('field_item:' . $type);
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$default_settings = $field_type_manager->getDefaultSettings($type) + $field_type_manager->getDefaultInstanceSettings($type);
$item_definition->setSettings($default_settings);
$field_definition->itemDefinition->setSettings($default_settings);
return $field_definition;
}
return new static(array(), $item_definition);
/**
* {@inheritdoc}
*/
public static function createFromItemType($item_type) {
// The data type of a field item is in the form of "field_item:$field_type".
$parts = explode(':', $item_type, 2);
return static::create($parts[1]);
}
/**
......@@ -75,10 +101,7 @@ public function setName($name) {
* {@inheritdoc}
*/
public function getType() {
$data_type = $this->getItemDefinition()->getDataType();
// Cut of the leading field_item: prefix from 'field_item:FIELD_TYPE'.
$parts = explode(':', $data_type);
return $parts[1];
return $this->type;
}
/**
......@@ -125,13 +148,6 @@ public function setSetting($setting_name, $value) {
return $this;
}
/**
* {@inheritdoc}
*/
public function getPropertyNames() {
return array_keys(\Drupal::typedDataManager()->create($this->getItemDefinition())->getPropertyDefinitions());
}
/**
* {@inheritdoc}