Commit 9bcaedec authored by webchick's avatar webchick

Issue #2047229 by fago, smiletrl, Berdir, effulgentsia, amateescu: Make use of...

Issue #2047229 by fago, smiletrl, Berdir, effulgentsia, amateescu: Make use of classes for entity field and data definitions.
parent 66f8fb82
......@@ -399,7 +399,7 @@ protected function getTranslatedField($property_name, $langcode) {
}
// Non-translatable fields are always stored with
// Language::LANGCODE_DEFAULT as key.
if ($langcode != Language::LANGCODE_DEFAULT && empty($definition['translatable'])) {
if ($langcode != Language::LANGCODE_DEFAULT && !$definition->isFieldTranslatable()) {
if (!isset($this->fields[$property_name][Language::LANGCODE_DEFAULT])) {
$this->fields[$property_name][Language::LANGCODE_DEFAULT] = $this->getTranslatedField($property_name, Language::LANGCODE_DEFAULT);
}
......@@ -437,7 +437,7 @@ public function set($property_name, $value, $notify = TRUE) {
public function getProperties($include_computed = FALSE) {
$properties = array();
foreach ($this->getPropertyDefinitions() as $name => $definition) {
if ($include_computed || empty($definition['computed'])) {
if ($include_computed || !$definition->isComputed()) {
$properties[$name] = $this->get($name);
}
}
......@@ -710,7 +710,7 @@ public function addTranslation($langcode, array $values = array()) {
$definitions = $translation->getPropertyDefinitions();
foreach ($values as $name => $value) {
if (isset($definitions[$name]) && !empty($definitions[$name]['translatable'])) {
if (isset($definitions[$name]) && $definitions[$name]->isFieldTranslatable()) {
$translation->$name = $value;
}
}
......@@ -724,7 +724,7 @@ public function addTranslation($langcode, array $values = array()) {
public function removeTranslation($langcode) {
if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
foreach ($this->getPropertyDefinitions() as $name => $definition) {
if (!empty($definition['translatable'])) {
if ($definition->isFieldTranslatable()) {
unset($this->values[$name][$langcode]);
unset($this->fields[$name][$langcode]);
}
......@@ -779,7 +779,7 @@ public function updateOriginalValues() {
return;
}
foreach ($this->getPropertyDefinitions() as $name => $definition) {
if (empty($definition['computed']) && !empty($this->fields[$name])) {
if (!$definition->isComputed() && !empty($this->fields[$name])) {
foreach ($this->fields[$name] as $langcode => $field) {
$field->filterEmptyValues();
$this->values[$name][$langcode] = $field->getValue();
......@@ -911,7 +911,7 @@ public function __clone() {
// object keyed by language. To avoid creating different field objects
// we retain just the original value, as references will be recreated
// later as needed.
if (empty($definitions[$name]['translatable']) && count($values) > 1) {
if (!$definitions[$name]->isFieldTranslatable() && count($values) > 1) {
$values = array_intersect_key($values, array(Language::LANGCODE_DEFAULT => TRUE));
}
foreach ($values as $langcode => $items) {
......
......@@ -42,14 +42,20 @@ public function initTranslation($langcode);
/**
* Defines the base fields of the entity type.
*
* Implementations typically use the class \Drupal\Core\Field\FieldDefinition
* for creating the field definitions; for example a 'name' field could be
* defined as the following:
* @code
* $fields['name'] = FieldDefinition::create('string')
* ->setLabel(t('Name'));
* @endcode
*
* @param string $entity_type
* The entity type to return properties for. Useful when a single class is
* used for multiple, possibly dynamic entity types.
*
* @return array
* An array of entity field definitions as specified by
* \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions(), keyed by field
* name.
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of entity field definitions, keyed by field name.
*
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
*/
......
......@@ -10,6 +10,7 @@
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Field\FieldDefinition;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManager;
......@@ -333,6 +334,8 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) {
$this->entityFieldInfo[$entity_type] = $cache->data;
}
else {
// @todo: Refactor to allow for per-bundle overrides.
// See https://drupal.org/node/2114707.
$class = $this->factory->getPluginClass($entity_type, $this->getDefinition($entity_type));
$base_definitions = $class::baseFieldDefinitions($entity_type);
......@@ -357,22 +360,31 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) {
$result = $this->moduleHandler->invokeAll('entity_field_info', array($entity_type));
$this->entityFieldInfo[$entity_type] = NestedArray::mergeDeep($this->entityFieldInfo[$entity_type], $result);
// Enforce field definitions to be objects.
foreach (array('definitions', 'optional') as $key) {
foreach ($this->entityFieldInfo[$entity_type][$key] as $field_name => &$definition) {
if (is_array($definition)) {
$definition = FieldDefinition::createFromOldStyleDefinition($definition);
}
// Automatically set the field name for non-configurable fields.
if ($definition instanceof FieldDefinition) {
$definition->setFieldName($field_name);
}
}
}
// Invoke alter hooks.
$hooks = array('entity_field_info', $entity_type . '_field_info');
$this->moduleHandler->alter($hooks, $this->entityFieldInfo[$entity_type], $entity_type);
// Enforce fields to be multiple and untranslatable by default.
// Ensure all basic fields are not defined as translatable.
$entity_info = $this->getDefinition($entity_type);
$keys = array_intersect_key(array_filter($entity_info['entity_keys']), array_flip(array('id', 'revision', 'uuid', 'bundle')));
$untranslatable_fields = array_flip(array('langcode') + $keys);
foreach (array('definitions', 'optional') as $key) {
foreach ($this->entityFieldInfo[$entity_type][$key] as $name => &$definition) {
$definition['list'] = TRUE;
// Ensure ids and langcode fields are never made translatable.
if (isset($untranslatable_fields[$name]) && !empty($definition['translatable'])) {
throw new \LogicException(format_string('The @field field cannot be translatable.', array('@field' => $definition['label'])));
}
if (!isset($definition['translatable'])) {
$definition['translatable'] = FALSE;
foreach ($this->entityFieldInfo[$entity_type][$key] as $field_name => &$definition) {
if (isset($untranslatable_fields[$field_name]) && $definition->isFieldTranslatable()) {
throw new \LogicException(format_string('The @field field cannot be translatable.', array('@field' => $definition->getFieldLabel())));
}
}
}
......
......@@ -25,9 +25,7 @@ public function getEntityTypeLabels();
/**
* Gets an array of content entity field definitions.
*
* If a bundle is passed, fields specific to this bundle are included. Entity
* fields are always multi-valued, so 'list' is TRUE for each returned field
* definition.
* If a bundle is passed, fields specific to this bundle are included.
*
* @param string $entity_type
* The entity type to get field definitions for. Only entity types that
......@@ -36,19 +34,11 @@ public function getEntityTypeLabels();
* (optional) The entity bundle for which to get field definitions. If NULL
* is passed, no bundle-specific fields are included. Defaults to NULL.
*
* @return array
* An array of field definitions of entity fields, keyed by field
* name. In addition to the typed data definition keys as described at
* \Drupal\Core\TypedData\TypedDataManager::create() the following keys are
* supported:
* - queryable: Whether the field is queryable via QueryInterface.
* Defaults to TRUE if 'computed' is FALSE or not set, to FALSE otherwise.
* - translatable: Whether the field is translatable. Defaults to FALSE.
* - configurable: A boolean indicating whether the field is configurable
* via field.module. Defaults to FALSE.
* @return \Drupal\Core\Field\FieldDefinitionInterface[]
* An array of entity field definitions, keyed by field name.
*
* @see \Drupal\Core\TypedData\TypedDataManager::create()
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitionsByConstraints()
* @see \Drupal\Core\Entity\EntityManager::getFieldDefinitionsByConstraints()
*/
public function getFieldDefinitions($entity_type, $bundle = NULL);
......
......@@ -263,9 +263,7 @@ public function onBundleDelete($bundle) { }
*/
public function onFieldItemsPurge(EntityInterface $entity, FieldInstanceInterface $instance) {
if ($values = $this->readFieldItemsToPurge($entity, $instance)) {
$field = $instance->getField();
$definition = _field_generate_entity_field_definition($field, $instance);
$items = \Drupal::typedData()->create($definition, $values, $field->getFieldName(), $entity);
$items = \Drupal::typedData()->create($instance, $values, $instance->getFieldName(), $entity);
$items->delete();
}
$this->purgeFieldItems($entity, $instance);
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\Field;
use Drupal\Core\Field\Plugin\DataType\FieldInstanceInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\field\Field;
......@@ -25,10 +26,11 @@ class ConfigFieldItemList extends FieldItemList implements ConfigFieldItemListIn
/**
* {@inheritdoc}
*/
public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) {
public function __construct($definition, $name = NULL, TypedDataInterface $parent = NULL) {
parent::__construct($definition, $name, $parent);
if (isset($definition['instance'])) {
$this->instance = $definition['instance'];
// Definition can be the field config or field instance.
if ($definition instanceof FieldInstanceInterface) {
$this->instance = $definition;
}
}
......@@ -36,6 +38,10 @@ public function __construct(array $definition, $name = NULL, TypedDataInterface
* {@inheritdoc}
*/
public function getFieldDefinition() {
// Configurable fields have the field_config entity injected as definition,
// but we want to return the more specific field instance here.
// @todo: Overhaul this once we have per-bundle field definitions injected,
// see https://drupal.org/node/2114707.
if (!isset($this->instance)) {
$entity = $this->getEntity();
$instances = Field::fieldInfo()->getBundleInstances($entity->entityType(), $entity->bundle());
......@@ -43,9 +49,7 @@ public function getFieldDefinition() {
$this->instance = $instances[$this->getName()];
}
else {
// For base fields, fall back to the parent implementation.
// @todo: Inject the field definition with
// https://drupal.org/node/2047229.
// For base fields, fall back to return the general definition.
return parent::getFieldDefinition();
}
}
......@@ -73,13 +77,6 @@ public function getConstraints() {
return $constraints;
}
/**
* {@inheritdoc}
*/
protected function getDefaultValue() {
return $this->getFieldDefinition()->getFieldDefaultValue($this->getEntity());
}
/**
* {@inheritdoc}
*/
......
......@@ -8,27 +8,25 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\ListDefinition;
/**
* A class for defining entity fields.
*/
class FieldDefinition implements FieldDefinitionInterface {
class FieldDefinition extends ListDefinition implements FieldDefinitionInterface, \ArrayAccess {
/**
* The array holding values for all definition keys.
* Creates a new field definition.
*
* @var array
*/
protected $definition = array();
/**
* Constructs a new FieldDefinition object.
* @param string $type
* The type of the field.
*
* @param array $definition
* (optional) If given, a definition represented as array.
* @return \Drupal\Core\Field\FieldDefinition
* A new field definition object.
*/
public function __construct(array $definition = array()) {
$this->definition = $definition;
public static function create($type) {
return new static(array(), DataDefinition::create('field_item:' . $type));
}
/**
......@@ -44,7 +42,7 @@ public function getFieldName() {
* @param string $name
* The field name to set.
*
* @return \Drupal\Core\Field\FieldDefinition
* @return self
* The object itself for chaining.
*/
public function setFieldName($name) {
......@@ -56,58 +54,63 @@ public function setFieldName($name) {
* {@inheritdoc}
*/
public function getFieldType() {
$data_type = $this->getItemDefinition()->getDataType();
// Cut of the leading field_item: prefix from 'field_item:FIELD_TYPE'.
$parts = explode(':', $this->definition['type']);
$parts = explode(':', $data_type);
return $parts[1];
}
/**
* Sets the field type.
*
* @param string $type
* The field type to set.
*
* @return \Drupal\Core\Field\FieldDefinition
* The object itself for chaining.
* {@inheritdoc}
*/
public function setFieldType($type) {
$this->definition['type'] = 'field_item:' . $type;
return $this;
public function getFieldSettings() {
return $this->getItemDefinition()->getSettings();
}
/**
* Sets a field setting.
* Sets field settings.
*
* @param string $type
* The field type to set.
* @param array $settings
* The value to set.
*
* @return \Drupal\Core\Field\FieldDefinition
* @return self
* The object itself for chaining.
*/
public function setFieldSetting($setting_name, $value) {
$this->definition['settings'][$setting_name] = $value;
public function setFieldSettings(array $settings) {
$this->getItemDefinition()->setSettings($settings);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFieldSettings() {
return $this->definition['settings'];
public function getFieldSetting($setting_name) {
$settings = $this->getFieldSettings();
return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
}
/**
* {@inheritdoc}
* Sets a field setting.
*
* @param string $setting_name
* The field setting to set.
* @param mixed $value
* The value to set.
*
* @return self
* The object itself for chaining.
*/
public function getFieldSetting($setting_name) {
return isset($this->definition['settings'][$setting_name]) ? $this->definition['settings'][$setting_name] : NULL;
public function setFieldSetting($setting_name, $value) {
$settings = $this->getFieldSettings();
$settings[$setting_name] = $value;
return $this->setFieldSettings($settings);
}
/**
* {@inheritdoc}
*/
public function getFieldPropertyNames() {
return array_keys(\Drupal::typedData()->create($this->definition['type'])->getPropertyDefinitions());
return array_keys(\Drupal::typedData()->create($this->getItemDefinition())->getPropertyDefinitions());
}
/**
......@@ -123,7 +126,7 @@ public function isFieldTranslatable() {
* @param bool $translatable
* Whether the field is translatable.
*
* @return \Drupal\Core\Field\FieldDefinition
* @return self
* The object itself for chaining.
*/
public function setTranslatable($translatable) {
......@@ -135,42 +138,28 @@ public function setTranslatable($translatable) {
* {@inheritdoc}
*/
public function getFieldLabel() {
return $this->definition['label'];
return $this->getLabel();
}
/**
* Sets the field label.
*
* @param string $label
* The field label to set.
*
* @return \Drupal\Core\Field\FieldDefinition
* The object itself for chaining.
* {@inheritdoc}
*/
public function setFieldLabel($label) {
$this->definition['label'] = $label;
return $this;
return $this->setLabel($label);
}
/**
* {@inheritdoc}
*/
public function getFieldDescription() {
return $this->definition['description'];
return $this->getDescription();
}
/**
* Sets the field label.
*
* @param string $description
* The field label to set.
*
* @return \Drupal\Core\Field\FieldDefinition
* The object itself for chaining.
* {@inheritdoc}
*/
public function setFieldDescription($description) {
$this->definition['description'] = $description;
return $this;
return $this->setDescription($description);
}
/**
......@@ -185,7 +174,7 @@ public function getFieldCardinality() {
* {@inheritdoc}
*/
public function isFieldRequired() {
return !empty($this->definition['required']);
return $this->isRequired();
}
/**
......@@ -200,13 +189,33 @@ public function isFieldMultiple() {
* Sets whether the field is required.
*
* @param bool $required
* TRUE if the field is required, FALSE otherwise.
* Whether the field is required.
*
* @return \Drupal\Core\Field\FieldDefinition
* @return self
* The object itself for chaining.
*/
public function setFieldRequired($required) {
$this->definition['required'] = $required;
return $this->setRequired($required);
}
/**
* {@inheritdoc}
*/
public function isFieldQueryable() {
return isset($this->definition['queryable']) ? $this->definition['queryable'] : !$this->isComputed();
}
/**
* Sets whether the field is queryable.
*
* @param bool $queryable
* Whether the field is queryable.
*
* @return self
* The object itself for chaining.
*/
public function setFieldQueryable($queryable) {
$this->definition['queryable'] = $queryable;
return $this;
}
......@@ -218,11 +227,13 @@ public function setFieldRequired($required) {
* @param array $constraints
* The constraints to set.
*
* @return \Drupal\Core\Field\FieldDefinition
* @return self
* The object itself for chaining.
*/
public function setPropertyConstraints($name, array $constraints) {
$this->definition['item_definition']['constraints']['ComplexData'][$name] = $constraints;
$item_constraints = $this->getItemDefinition()->getConstraints();
$item_constraints['ComplexData'][$name] = $constraints;
$this->getItemDefinition()->setConstraints($item_constraints);
return $this;
}
......@@ -240,4 +251,77 @@ public function getFieldDefaultValue(EntityInterface $entity) {
return $this->getFieldSetting('default_value');
}
/**
* Allows creating field definition objects from old style definition arrays.
*
* @todo: Remove once https://drupal.org/node/2112239 is in.
*/
public static function createFromOldStyleDefinition(array $definition) {
unset($definition['list']);
// Separate the list item definition from the list definition.
$list_definition = $definition;
unset($list_definition['type']);
// Constraints, class and settings apply to the list item.
unset($list_definition['constraints']);
unset($list_definition['class']);
unset($list_definition['settings']);
$field_definition = new FieldDefinition($list_definition);
if (isset($definition['list_class'])) {
$field_definition->setClass($definition['list_class']);
}
else {
$type_definition = \Drupal::typedData()->getDefinition($definition['type']);
if (isset($type_definition['list_class'])) {
$field_definition->setClass($type_definition['list_class']);
}
}
if (isset($definition['translatable'])) {
$field_definition->setTranslatable($definition['translatable']);
unset($definition['translatable']);
}
// Take care of the item definition now.
// Required applies to the field definition only.
unset($definition['required']);
$item_definition = new DataDefinition($definition);
$field_definition->setItemDefinition($item_definition);
return $field_definition;
}
/**
* {@inheritdoc}
*
* This is for BC support only.
* @todo: Remove once https://drupal.org/node/2112239 is in.
*/
public function &offsetGet($offset) {
if ($offset == 'type') {
// What previously was "type" is now the type of the list item.
$type = &$this->itemDefinition->offsetGet('type');
return $type;
}
if (!isset($this->definition[$offset])) {
$this->definition[$offset] = NULL;
}
return $this->definition[$offset];
}
/**
* {@inheritdoc}
*
* This is for BC support only.
* @todo: Remove once https://drupal.org/node/2112239 is in.
*/
public function offsetSet($offset, $value) {
if ($offset == 'type') {
// What previously was "type" is now the type of the list item.
$this->itemDefinition->setDataType($value);
}
else {
$this->definition[$offset] = $value;
}
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\ListDefinitionInterface;
/**
* Defines an interface for entity field definitions.
......@@ -36,10 +37,9 @@
*
* However, entity base fields, such as $node->title, are not managed by
* field.module and its "field_entity"/"field_instance" configuration entities.
* Therefore, their definitions are provided by different objects that implement
* this interface.
* @todo That is still in progress: https://drupal.org/node/1949932. Update this
* documentation with details when that's implemented.
* Therefore, their definitions are provided by different objects based on the
* class \Drupal\Core\Field\FieldDefinition, which implements this
* interface as well.
*
* Field definitions may fully define a concrete data object (e.g.,
* $node_1->body), or may provide a best-guess definition for a data object that
......@@ -51,7 +51,7 @@
* based on that abstract definition, even though that abstract definition can
* differ from the concrete definition of any particular node's body field.
*/
interface FieldDefinitionInterface {
interface FieldDefinitionInterface extends ListDefinitionInterface {
/**
* Value indicating a field accepts an unlimited number of values.
......@@ -130,9 +130,18 @@ public function isFieldTranslatable();
* Determines whether the field is configurable via field.module.
*
* @return bool
* TRUE if the field is configurable.
*/
public function isFieldConfigurable();
/**
* Determines whether the field is queryable via QueryInterface.
*
* @return bool
* TRUE if the field is queryable.
*/
public function isFieldQueryable();
/**