diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 177a495..c1aa050 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -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); } @@ -431,7 +431,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); } } @@ -703,7 +703,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; } } @@ -717,7 +717,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]); } @@ -772,7 +772,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(); diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php index 638979f..fde6af9 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php @@ -42,14 +42,15 @@ public function initTranslation($langcode); /** * Defines the base fields of the entity type. * + * Implementations may use the class \Drupal\Core\Field\FieldDefinition for + * creating the field definitions. + * * @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\EntityManager::getFieldDefinitions(), keyed by field - * name. + * @return \Drupal\Core\Field\FieldDefinitionInterface[] + * An array of entity field definitions, keyed by field name. * * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitions() */ diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index bdf5f49..43e3838 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -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\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManager; use Drupal\Core\Language\Language; @@ -437,9 +438,7 @@ public function getAdminRouteInfo($entity_type, $bundle) { /** * 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 @@ -448,16 +447,8 @@ public function getAdminRouteInfo($entity_type, $bundle) { * (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\EntityManager::getFieldDefinitionsByConstraints() @@ -470,6 +461,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); @@ -497,19 +490,22 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) { $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. + // Enforce field definitions to be objects. $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']))); + foreach ($this->entityFieldInfo[$entity_type][$key] as $field_name => &$definition) { + if (is_array($definition)) { + $definition = FieldDefinition::createFromOldStyleDefinition($definition); + } + // For manually defined fields, make sure the field name is set. + if ($definition instanceof FieldDefinition) { + $definition->setFieldName($field_name); } - if (!isset($definition['translatable'])) { - $definition['translatable'] = FALSE; + // Ensure ID and langcode fields are never made translatable. + if (isset($untranslatable_fields[$field_name]) && $definition->isFieldTranslatable()) { + throw new \LogicException(format_string('The @field field cannot be translatable.', array('@field' => $definition->getFieldLabel()))); } } } diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php index 5cf7973..ec909d9 100644 --- a/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php +++ b/core/lib/Drupal/Core/Entity/FieldableEntityStorageControllerBase.php @@ -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); diff --git a/core/lib/Drupal/Core/Field/Annotation/FieldType.php b/core/lib/Drupal/Core/Field/Annotation/FieldType.php index 90d0cf7..dc7e32b 100644 --- a/core/lib/Drupal/Core/Field/Annotation/FieldType.php +++ b/core/lib/Drupal/Core/Field/Annotation/FieldType.php @@ -117,6 +117,6 @@ class FieldType extends DataType { /** * {@inheritdoc} */ - public $list_class; + public $list_type = 'field_item_list'; } diff --git a/core/lib/Drupal/Core/Field/FieldDefinition.php b/core/lib/Drupal/Core/Field/FieldDefinition.php index 0755166..b785bed 100644 --- a/core/lib/Drupal/Core/Field/FieldDefinition.php +++ b/core/lib/Drupal/Core/Field/FieldDefinition.php @@ -8,28 +8,13 @@ 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 { - - /** - * The array holding values for all definition keys. - * - * @var array - */ - protected $definition = array(); - - /** - * Constructs a new FieldDefinition object. - * - * @param array $definition - * (optional) If given, a definition represented as array. - */ - public function __construct(array $definition = array()) { - $this->definition = $definition; - } +class FieldDefinition extends ListDefinition implements FieldDefinitionInterface, \ArrayAccess { /** * {@inheritdoc} @@ -56,8 +41,9 @@ 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]; } @@ -71,43 +57,61 @@ public function getFieldType() { * The object itself for chaining. */ public function setFieldType($type) { - $this->definition['type'] = 'field_item:' . $type; + $this->getItemDefinition()->setDataType('field_item:' . $type); return $this; } /** - * Sets a field setting. + * {@inheritdoc} + */ + public function getFieldSettings() { + return $this->getItemDefinition()->getSettings(); + } + + /** + * Sets field settings. * - * @param string $type - * The field type to set. + * @param array $settings + * The value to set. * * @return \Drupal\Core\Field\FieldDefinition * 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 \Drupal\Core\Field\FieldDefinition + * 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()); } /** @@ -135,42 +139,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,20 +175,40 @@ public function getFieldCardinality() { * {@inheritdoc} */ public function isFieldRequired() { - return !empty($this->definition['required']); + return $this->isRequired(); } /** * 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 * 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 \Drupal\Core\Field\FieldDefinition + * The object itself for chaining. + */ + public function setFieldQueryable($queryable) { + $this->definition['queryable'] = $queryable; return $this; } @@ -214,7 +224,9 @@ public function setFieldRequired($required) { * 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; } @@ -232,4 +244,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; + } + } } diff --git a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php index 3bab0d7..6137280 100644 --- a/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php +++ b/core/lib/Drupal/Core/Field/FieldDefinitionInterface.php @@ -8,14 +8,16 @@ namespace Drupal\Core\Field; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\TypedData\ListDefinitionInterface; /** * Defines an interface for entity field definitions. * * An entity field is a data object that holds the values of a particular field - * for a particular entity (see \Drupal\Core\Field\FieldItemListInterface). For - * example, $node_1->body and $node_2->body contain different data and therefore - * are different field objects. + * for a particular entity (see + * \Drupal\Core\Field\FieldItemListInterface). For example, $node_1->body + * and $node_2->body contain different data and therefore are different field + * objects. * * In contrast, an entity field *definition* is an object that returns * information *about* a field (e.g., its type and settings) rather than its @@ -36,10 +38,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 +52,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 { /** * Returns the machine name of the field. @@ -125,10 +126,19 @@ 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(); + + /** * Returns the human-readable label for the field. * * @return string @@ -143,8 +153,8 @@ public function getFieldLabel(); * descriptive information is helpful. For example, as help text below the * form element in entity edit forms. * - * @return string - * The field description. + * @return string|null + * The field description, or NULL if no description is available. */ public function getFieldDescription(); diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index 31c9e09..70ff483 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -8,6 +8,8 @@ namespace Drupal\Core\Field; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\TypedData\DataDefinition; +use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\Plugin\DataType\Map; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\user; @@ -23,9 +25,16 @@ abstract class FieldItemBase extends Map implements FieldItemInterface { /** + * The field's definition. + * + * @var \Drupal\Core\Field\FieldDefinitionInterface + */ + protected $definition; + + /** * Overrides \Drupal\Core\TypedData\TypedData::__construct(). */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { + public function __construct($definition, $name = NULL, TypedDataInterface $parent = NULL) { parent::__construct($definition, $name, $parent); // Initialize computed properties by default, such that they get cloned // with the whole item. diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php index 4024754..f9d9d16 100644 --- a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php +++ b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php @@ -53,13 +53,9 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac */ public function processDefinition(&$definition, $plugin_id) { parent::processDefinition($definition, $plugin_id); - if (!isset($definition['list_class'])) { - if ($definition['configurable']) { - $definition['list_class'] = '\Drupal\Core\Field\ConfigFieldItemList'; - } - else { - $definition['list_class'] = '\Drupal\Core\Field\FieldItemList'; - } + // For configurable fields, default to 'field_configurable_item_list'. + if ($definition['configurable'] && $definition['list_type'] == 'field_item_list') { + $definition['list_type'] = 'field_configurable_item_list'; } } diff --git a/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php b/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php index 381f69e..37283f6 100644 --- a/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php +++ b/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php @@ -67,6 +67,7 @@ public function getDefinitions() { $definition['provider'] = $module; $definition['configurable'] = TRUE; $definition['list_class'] = '\Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItemList'; + $definition['list_type'] = 'field_configurable_item_list'; $definitions[$plugin_id] = $definition; } } diff --git a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php b/core/lib/Drupal/Core/Field/Plugin/DataType/ConfigFieldItemList.php similarity index 83% rename from core/lib/Drupal/Core/Field/ConfigFieldItemList.php rename to core/lib/Drupal/Core/Field/Plugin/DataType/ConfigFieldItemList.php index 2e32802..5a470bd 100644 --- a/core/lib/Drupal/Core/Field/ConfigFieldItemList.php +++ b/core/lib/Drupal/Core/Field/Plugin/DataType/ConfigFieldItemList.php @@ -5,13 +5,19 @@ * Contains \Drupal\Core\Field\ConfigFieldItemList. */ -namespace Drupal\Core\Field; +namespace Drupal\Core\Field\Plugin\DataType; +use Drupal\Core\Field\ConfigFieldItemListInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\field\Field; /** - * Represents a configurable entity field item list. + * Represents a field item list of configurable fields. + * + * @DataType( + * id = "field_configurable_item_list", + * label = @Translation("Configurable field item list") + * ) */ class ConfigFieldItemList extends FieldItemList implements ConfigFieldItemListInterface { @@ -25,10 +31,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 +43,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 +54,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(); } } @@ -76,13 +85,6 @@ public function getConstraints() { /** * {@inheritdoc} */ - protected function getDefaultValue() { - return $this->getFieldDefinition()->getFieldDefaultValue($this->getEntity()); - } - - /** - * {@inheritdoc} - */ public function defaultValuesForm(array &$form, array &$form_state) { if (empty($this->getFieldDefinition()->default_value_function)) { // Place the input in a separate place in the submitted values tree. diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php similarity index 94% rename from core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php rename to core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php index 5e287f7..d2fc7fd 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/FieldItemDeriver.php +++ b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php @@ -2,10 +2,10 @@ /** * @file - * Contains \Drupal\Core\Entity\Plugin\DataType\Deriver\FieldItemDeriver. + * Contains \Drupal\Core\Field\Plugin\DataType\Deriver\FieldItemDeriver. */ -namespace Drupal\Core\Entity\Plugin\DataType\Deriver; +namespace Drupal\Core\Field\Plugin\DataType\Deriver; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface; diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/FieldItem.php b/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php similarity index 53% rename from core/lib/Drupal/Core/Entity/Plugin/DataType/FieldItem.php rename to core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php index d97ea30..7bbe225 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/FieldItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItem.php @@ -2,13 +2,11 @@ /** * @file - * Contains \Drupal\Core\Entity\Plugin\DataType\FieldItem. + * Contains \Drupal\Core\Field\Plugin\DataType\FieldItem. */ -namespace Drupal\Core\Entity\Plugin\DataType; +namespace Drupal\Core\Field\Plugin\DataType; -use Drupal\Core\TypedData\Annotation\DataType; -use Drupal\Core\Annotation\Translation; /** * Defines the base plugin for deriving data types for field types. @@ -19,8 +17,8 @@ * @DataType( * id = "field_item", * label = @Translation("Field item"), - * list_class = "\Drupal\Core\Field\FieldItemList", - * derivative = "Drupal\Core\Entity\Plugin\DataType\Deriver\FieldItemDeriver" + * list_class = "\Drupal\Core\Field\Plugin\DataType\FieldItemList", + * derivative = "Drupal\Core\Field\Plugin\DataType\Deriver\FieldItemDeriver" * ) */ abstract class FieldItem { diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItemList.php similarity index 75% rename from core/lib/Drupal/Core/Field/FieldItemList.php rename to core/lib/Drupal/Core/Field/Plugin/DataType/FieldItemList.php index 00dcaba..31ec4e1 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/Plugin/DataType/FieldItemList.php @@ -2,27 +2,29 @@ /** * @file - * Contains \Drupal\Core\Field\FieldItemList. + * Contains \Drupal\Core\Field\Plugin\DataType\FieldItemList. */ -namespace Drupal\Core\Field; +namespace Drupal\Core\Field\Plugin\DataType; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\TypedDataInterface; -use Drupal\Core\TypedData\ItemList; +use Drupal\Core\TypedData\Plugin\DataType\ItemList; use Drupal\Core\Language\Language; /** - * Represents an entity field; that is, a list of field item objects. + * Defines a data type plugin for entity fields, i.e. the list of field items. * - * An entity field is a list of field items, which contain only primitive - * properties or entity references. Note that even single-valued entity - * fields are represented as list of items, however for easy access to the - * contained item the entity field delegates __get() and __set() calls - * directly to the first item. + * An entity field is a list of field items, containing various field item + * properties. Note that even single-valued entity fields are represented as + * list of field items, however for easy access to the contained item the entity + * field delegates __get() and __set() calls directly to the first item. * - * Supported settings (below the definition's 'settings' key) are: - * - default_value: (optional) If set, the default value to apply to the field. + * @DataType( + * id = "field_item_list", + * label = @Translation("Field item list") + * ) */ class FieldItemList extends ItemList implements FieldItemListInterface { @@ -44,9 +46,8 @@ class FieldItemList extends ItemList implements FieldItemListInterface { /** * Overrides TypedData::__construct(). */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { + public function __construct($definition, $name = NULL, TypedDataInterface $parent = NULL) { parent::__construct($definition, $name, $parent); - $this->definition['field_name'] = $name; // Always initialize one empty item as most times a value for at least one // item will be present. That way prototypes created by // \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() will @@ -79,7 +80,7 @@ public function getLangcode() { * {@inheritdoc} */ public function getFieldDefinition() { - return new FieldDefinition($this->definition); + return $this->definition; } /** @@ -108,7 +109,7 @@ public function getValue($include_computed = FALSE) { } /** - * Overrides \Drupal\Core\TypedData\ItemList::setValue(). + * {@inheritdoc} */ public function setValue($values, $notify = TRUE) { if (!isset($values) || $values === array()) { @@ -213,10 +214,8 @@ public function defaultAccess($operation = 'view', AccountInterface $account = N * {@inheritdoc} */ public function applyDefaultValue($notify = TRUE) { - // @todo Remove getDefaultValue() and directly call - // FieldDefinition::getFieldDefaultValue() here, once - // https://drupal.org/node/2047229 is fixed. - $value = $this->getDefaultValue(); + $value = $this->getFieldDefinition()->getFieldDefaultValue($this->getEntity()); + // NULL or array() mean "no default value", but 0, '0' and the empty string // are valid default values. if (!isset($value) || (is_array($value) && empty($value))) { @@ -230,33 +229,6 @@ public function applyDefaultValue($notify = TRUE) { } /** - * Returns the default value for the field. - * - * @return array - * The default value for the field. - */ - protected function getDefaultValue() { - if (isset($this->definition['settings']['default_value'])) { - return $this->definition['settings']['default_value']; - } - } - - /** - * {@inheritdoc} - */ - public function getConstraints() { - // Constraints usually apply to the field item, but required does make - // sense on the field only. So we special-case it to apply to the field for - // now. - // @todo: Separate list and list item definitions to separate constraints. - $constraints = array(); - if (!empty($this->definition['required'])) { - $constraints[] = \Drupal::typedData()->getValidationConstraintManager()->create('NotNull', array()); - } - return $constraints; - } - - /** * {@inheritdoc} */ public function preSave() { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php index 27ca9b2..82f088a 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\TypedData\DataDefinition; /** * Defines the 'integer' entity field type. @@ -31,15 +32,13 @@ class IntegerItem extends FieldItemBase { static $propertyDefinitions; /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions(). + * {@inheritdoc} */ public function getPropertyDefinitions() { if (!isset(static::$propertyDefinitions)) { - static::$propertyDefinitions['value'] = array( - 'type' => 'integer', - 'label' => t('Integer value'), - ); + static::$propertyDefinitions['value'] = DataDefinition::create('integer') + ->setLabel(t('Integer value')); } return static::$propertyDefinitions; } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LegacyConfigFieldItemList.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LegacyConfigFieldItemList.php index 63889fd..b96598e 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LegacyConfigFieldItemList.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LegacyConfigFieldItemList.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; -use Drupal\Core\Field\ConfigFieldItemList; +use Drupal\Core\Field\Plugin\DataType\ConfigFieldItemList; use Drupal\field\FieldInstanceInterface; use Symfony\Component\Validator\ConstraintViolation; @@ -102,8 +102,8 @@ public function deleteRevision() { * The name of the hook, e.g. 'presave', 'validate'. */ protected function legacyCallback($hook, $args = array()) { - $definition = $this->getPluginDefinition(); - $module = $definition['provider']; + $type_definition = \Drupal::typedData()->getDefinition($this->getItemDefinition()->getDataType()); + $module = $type_definition['provider']; $callback = "{$module}_field_{$hook}"; if (function_exists($callback)) { // We need to remove the empty "prototype" item here. diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php index ab045f4..9c63b33 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/StringItem.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\TypedData\DataDefinition; /** * Defines the 'string' entity field type. @@ -31,15 +32,13 @@ class StringItem extends FieldItemBase { static $propertyDefinitions; /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions(). + * {@inheritdoc} */ public function getPropertyDefinitions() { if (!isset(static::$propertyDefinitions)) { - static::$propertyDefinitions['value'] = array( - 'type' => 'string', - 'label' => t('Text value'), - ); + static::$propertyDefinitions['value'] = DataDefinition::create('string') + ->setLabel(t('Text value')); } return static::$propertyDefinitions; } diff --git a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php index da2d300..a82dc14 100644 --- a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php +++ b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php @@ -25,8 +25,8 @@ * * Furthermore, lists of data items are represented by objects implementing the * \Drupal\Core\TypedData\ListInterface. A list contains items of the same data - * type, is ordered and may contain duplicates. The classed used for a list of - * items of a certain type may be specified using the 'list class' key. + * type, is ordered and may contain duplicates. The data type used for a list of + * items of a certain type may be specified using the 'list_type' property. * * @see \Drupal::typedData() * @see \Drupal\Core\TypedData\TypedDataManager::create() @@ -62,14 +62,6 @@ class DataType extends Plugin { public $description; /** - * The typed data class used for wrapping multiple data items of the type. - * Must implement the \Drupal\Core\TypedData\ListInterface. - * - * @var string - */ - public $list_class = '\Drupal\Core\TypedData\ItemList'; - - /** * The pre-defined primitive type that this data type maps to. * * If set, it must be a constant defined by \Drupal\Core\TypedData\Primitive @@ -88,4 +80,20 @@ class DataType extends Plugin { */ public $constraints; + /** + * The list type used by default. + * + * A list of items is represented using a list data type, whose class + * implements the \Drupal\Core\TypedData\ListInterface. + * The specified list data type serves as default for lists of items based on + * the declared data type, while data definitions may specify the data type, + * or a custom class to use instead. + * + * @see \Drupal\Core\TypedData\ListDefinitionInterface + * + * @var string + * The plugin ID of the list type. + */ + public $list_type = 'list'; + } diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php index 8acfaa3..2e3d0e7 100644 --- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php @@ -105,10 +105,6 @@ public function getPropertyDefinition($name); /** * Gets an array of property definitions of contained properties. * - * @param array $definition - * The definition of the container's property, e.g. the definition of an - * entity reference property. - * * @return array * An array of property definitions of contained properties, keyed by * property name. diff --git a/core/lib/Drupal/Core/TypedData/DataDefinition.php b/core/lib/Drupal/Core/TypedData/DataDefinition.php new file mode 100644 index 0000000..de17f46 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/DataDefinition.php @@ -0,0 +1,371 @@ +definition = $definition; + } + + /** + * {@inheritdoc} + */ + public function getDataType() { + return !empty($this->definition['type']) ? $this->definition['type'] : 'any'; + } + + /** + * Sets the data type. + * + * @param string $type + * The data type to set. + * + * @return \Drupal\Core\TypedData\DataDefinitionInterface + * The object itself for chaining. + */ + public function setDataType($type) { + $this->definition['type'] = $type; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return isset($this->definition['label']) ? $this->definition['label'] : NULL; + } + + /** + * Sets the human-readable label. + * + * @param string $label + * The label to set. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setLabel($label) { + $this->definition['label'] = $label; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return isset($this->definition['description']) ? $this->definition['description'] : NULL; + } + + /** + * Sets the human-readable description. + * + * @param string $description + * The description to set. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setDescription($description) { + $this->definition['description'] = $description; + return $this; + } + + /** + * {@inheritdoc} + */ + public function isList() { + return ($this instanceof ListDefinitionInterface); + } + + /** + * {@inheritdoc} + */ + public function isReadOnly() { + if (!isset($this->definition['read-only'])) { + // Default to read-only if the data value is computed. + return $this->isComputed(); + } + return !empty($this->definition['read-only']); + } + + /** + * Sets whether the data is read-only. + * + * @param bool $read-only + * Whether the data is read-only. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setReadOnly($read_only) { + $this->definition['read-only'] = $read_only; + return $this; + } + + /** + * {@inheritdoc} + */ + public function isComputed() { + return !empty($this->definition['computed']); + } + + /** + * Sets whether the data is computed. + * + * @param bool $computed + * Whether the data is computed. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setComputed($computed) { + $this->definition['computed'] = $computed; + return $this; + } + + /** + * {@inheritdoc} + */ + public function isRequired() { + return !empty($this->definition['required']); + } + + /** + * Sets whether the data is required. + * + * @param bool $required + * Whether the data is required. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setRequired($required) { + $this->definition['required'] = $required; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getClass() { + return isset($this->definition['class']) ? $this->definition['class'] : NULL; + } + + /** + * Sets the class used for creating the typed data object. + * + * @param string|null $class + * The class to use. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setClass($class) { + $this->definition['class'] = $class; + return $this; + } + + /** + * Returns the array of settings, as required by the used class. + * + * See the documentation of the class for supported or required settings. + * + * @return array + * The array of settings. + */ + public function getSettings() { + return isset($this->definition['settings']) ? $this->definition['settings'] : array(); + } + + /** + * Sets the array of settings, as required by the used class. + * + * @param array $settings + * The array of settings. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setSettings(array $settings) { + $this->definition['settings'] = $settings; + return $this; + } + + /** + * Returns an array of validation constraints. + * + * See \Drupal\Core\TypedData\TypedDataManager::getConstraints() for details. + * + * @return array + * Array of constraints, each being an instance of + * \Symfony\Component\Validator\Constraint. + */ + public function getConstraints() { + return isset($this->definition['constraints']) ? $this->definition['constraints'] : array(); + } + + /** + * Sets the array of validation constraints. + * + * See \Drupal\Core\TypedData\TypedDataManager::getConstraints() for details. + * + * @param array $constraints + * The array of constraints. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function setConstraints(array $constraints) { + $this->definition['constraints'] = $constraints; + return $this; + } + + /** + * Adds a validation constraint. + * + * See \Drupal\Core\TypedData\TypedDataManager::getConstraints() for details. + * + * @param string $constraint_name + * The name of the constraint to add, i.e. its plugin id. + * @param array|null $options + * The constraint options as required by the constraint plugin, or NULL. + * + * @return \Drupal\Core\TypedData\DataDefinition + * The object itself for chaining. + */ + public function addConstraint($constraint_name, $options = NULL) { + $this->definition['constraints'][$constraint_name] = $options; + return $this; + } + + /** + * {@inheritdoc} + * + * This is for BC support only. + * @todo: Remove once https://drupal.org/node/2112239 is in. + */ + public function offsetExists($offset) { + // PHP's array access does not work correctly with isset(), so we have to + // bake isset() in here. See https://bugs.php.net/bug.php?id=41727. + return array_key_exists($offset, $this->definition) && isset($this->definition[$offset]); + } + + /** + * {@inheritdoc} + * + * This is for BC support only. + * @todo: Remove once https://drupal.org/node/2112239 is in. + */ + public function &offsetGet($offset) { + 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) { + $this->definition[$offset] = $value; + } + + /** + * {@inheritdoc} + * + * This is for BC support only. + * @todo: Remove once https://drupal.org/node/2112239 is in. + */ + public function offsetUnset($offset) { + unset($this->definition[$offset]); + } + + /** + * Returns all definition values as array. + * + * @return array + */ + public function toArray() { + return $this->definition; + } + + /** + * Allows creating data definition objects from old style definition arrays. + * + * @todo: Remove once https://drupal.org/node/2112239 is in. + */ + public static function createFromOldStyleDefinition(array $definition) { + if (empty($definition['list'])) { + return new DataDefinition($definition); + } + + // If the definition describes a list, separate the list item definition + // from the list definition. + unset($definition['list']); + + $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']); + + $list_definition = new ListDefinition($list_definition); + if (isset($definition['list_class'])) { + $list_definition->setClass($definition['list_class']); + } + else { + $type_definition = \Drupal::typedData()->getDefinition($definition['type']); + if (isset($type_definition['list_class'])) { + $list_definition->setClass($type_definition['list_class']); + } + } + + // Take care of the item definition now. + // Required applies to the list definition only. + unset($definition['required']); + $item_definition = new DataDefinition($definition); + $list_definition->setItemDefinition($item_definition); + return $list_definition; + } +} diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php new file mode 100644 index 0000000..eb5dd67 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php @@ -0,0 +1,115 @@ +itemDefinition = DataDefinition::create(); + } + + /** + * {@inheritdoc} + * + * The default data type of a list definition is derived from the list item's + * data type. + */ + public function getDataType() { + if (!empty($this->definition['type'])) { + return $this->definition['type']; + } + // If a list definition is used but no data type has been specified, derive + // the default list type from the item type. + else { + $item_type_definition = \Drupal::typedData() + ->getDefinition($this->getItemDefinition()->getDataType()); + return isset($item_type_definition['list_type']) ? $item_type_definition['list_type'] : 'list'; + } + } + + /** + * {@inheritdoc} + */ + public function getClass() { + $class = isset($this->definition['class']) ? $this->definition['class'] : NULL; + if (!empty($class)) { + return $class; + } + else { + // For BC check for a defined list class. + // @todo: Remove once https://drupal.org/node/2112239 is in. + $item_type_definition = \Drupal::typedData() + ->getDefinition($this->getItemDefinition()->getDataType()); + if (isset($item_type_definition['list_class']) && !$this->getClass()) { + $this->setClass($item_type_definition['list_class']); + } + return isset($this->definition['class']) ? $this->definition['class'] : NULL; + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefinition() { + return $this->itemDefinition; + } + + /** + * Sets the item definition. + * + * @param \Drupal\Core\TypedData\DataDefinition $definition + * A list item's data definition. + * + * @return \Drupal\Core\TypedData\ListDefinition + * The object itself for chaining. + */ + public function setItemDefinition(DataDefinitionInterface $definition) { + $this->itemDefinition = $definition; + return $this; + } +} diff --git a/core/lib/Drupal/Core/TypedData/ListDefinitionInterface.php b/core/lib/Drupal/Core/TypedData/ListDefinitionInterface.php new file mode 100644 index 0000000..c07d60e --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ListDefinitionInterface.php @@ -0,0 +1,26 @@ + FALSE) + $this->definition; + return $this->definition->getItemDefinition(); } /** diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index 430ed2a..acfa4ea 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -20,7 +20,7 @@ /** * The data definition. * - * @var array + * @var \Drupal\Core\TypedData\DataDefinitionInterface */ protected $definition; @@ -41,7 +41,7 @@ /** * Constructs a TypedData object given its definition and context. * - * @param array $definition + * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition * The data definition. * @param string $name * (optional) The name of the created property, or NULL if it is the root @@ -52,7 +52,7 @@ * * @see \Drupal\Core\TypedData\TypedDataManager::create() */ - public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) { + public function __construct($definition, $name = NULL, TypedDataInterface $parent = NULL) { $this->definition = $definition; $this->parent = $parent; $this->name = $name; diff --git a/core/lib/Drupal/Core/TypedData/TypedDataInterface.php b/core/lib/Drupal/Core/TypedData/TypedDataInterface.php index bbdf4ea..bef5a36 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataInterface.php @@ -18,7 +18,9 @@ * Gets the data definition. * * @return array - * The data definition array. + * The data definition represented as array. + * + * @see \Drupal\Core\TypedData\DataDefinition */ public function getDefinition(); diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index 1428822..7e30812 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -52,77 +52,49 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac } /** - * Implements \Drupal\Component\Plugin\PluginManagerInterface::createInstance(). + * Instantiates a typed data object. * - * @param string $plugin_id - * The id of a plugin, i.e. the data type. + * @param string $data_type + * The data type, for which a typed object should be instantiated. * @param array $configuration - * The plugin configuration, i.e. the data definition. - * @param string $name - * (optional) If a property or list item is to be created, the name of the - * property or the delta of the list item. - * @param mixed $parent - * (optional) If a property or list item is to be created, the parent typed - * data object implementing either the ListInterface or the - * ComplexDataInterface. + * The plugin configuration array, i.e. an array with the following keys: + * - data definition: The data definition object, i.e. an instance of + * \Drupal\Core\TypedData\DataDefinitionInterface. + * - name: (optional) If a property or list item is to be created, the name + * of the property or the delta of the list item. + * - parent: (optional) If a property or list item is to be created, the + * parent typed data object implementing either the ListInterface or the + * ComplexDataInterface. * * @return \Drupal\Core\TypedData\TypedDataInterface * The instantiated typed data object. */ - public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) { - $type_definition = $this->getDefinition($plugin_id); + public function createInstance($data_type, array $configuration) { + $data_definition = $configuration['data_definition']; + $type_definition = $this->getDefinition($data_type); if (!isset($type_definition)) { - throw new \InvalidArgumentException(format_string('Invalid data type %plugin_id has been given.', array('%plugin_id' => $plugin_id))); + throw new \InvalidArgumentException(format_string('Invalid data type %plugin_id has been given.', array('%plugin_id' => $data_type))); } // Allow per-data definition overrides of the used classes, i.e. take over - // classes specified in the data definition. - $key = empty($configuration['list']) ? 'class' : 'list_class'; - if (isset($configuration[$key])) { - $class = $configuration[$key]; - } - elseif (isset($type_definition[$key])) { - $class = $type_definition[$key]; - } + // classes specified in the type definition. + $class = $data_definition->getClass(); + $class = isset($class) ? $class : $type_definition['class']; if (!isset($class)) { - throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id)); + throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $data_type)); } - return new $class($configuration, $name, $parent); + return new $class($data_definition, $configuration['name'], $configuration['parent']); } /** * Creates a new typed data object instance. * - * @param array $definition - * The data definition array with the following array keys and values: - * - type: The data type of the data to wrap. Required. - * - label: A human readable label. - * - description: A human readable description. - * - list: Whether the data is multi-valued, i.e. a list of data items. - * Defaults to FALSE. - * - computed: A boolean specifying whether the data value is computed by - * the object, e.g. depending on some other values. - * - read-only: A boolean specifying whether the data is read-only. Defaults - * to TRUE for computed properties, to FALSE otherwise. - * - class: If set and 'list' is FALSE, the class to use for creating the - * typed data object; otherwise the default class of the data type will be - * used. - * - list_class: If set and 'list' is TRUE, the class to use for creating - * the typed data object; otherwise the default list class of the data - * type will be used. - * - settings: An array of settings, as required by the used 'class'. See - * the documentation of the class for supported or required settings. - * - list_settings: An array of settings as required by the used - * 'list_class'. See the documentation of the list class for support or - * required settings. - * - constraints: An array of validation constraints. See - * \Drupal\Core\TypedData\TypedDataManager::getConstraints() for details. - * - required: A boolean specifying whether a non-NULL value is mandatory. - * Further keys may be supported in certain usages, e.g. for further keys - * supported for entity field definitions see - * \Drupal\Core\Entity\StorageControllerInterface::getPropertyDefinitions(). + * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition + * The data definition of the typed data object. For backwards-compatibility + * an array representation of the data definition may be passed also. + * @todo: Need to remove array support in https://drupal.org/node/2112239. * @param mixed $value * (optional) The data value. If set, it has to match one of the supported * data type format as documented for the data type classes. @@ -148,12 +120,20 @@ public function createInstance($plugin_id, array $configuration, $name = NULL, $ * @see \Drupal\Core\TypedData\Plugin\DataType\Uri * @see \Drupal\Core\TypedData\Plugin\DataType\Binary */ - public function create(array $definition, $value = NULL, $name = NULL, $parent = NULL) { - $wrapper = $this->createInstance($definition['type'], $definition, $name, $parent); + public function create($definition, $value = NULL, $name = NULL, $parent = NULL) { + // @todo: Remove array support once https://drupal.org/node/2112239 is in. + if (is_array($definition)) { + $definition = DataDefinition::createFromOldStyleDefinition($definition); + } + $typed_data = $this->createInstance($definition->getDataType(), array( + 'data_definition' => $definition, + 'name' => $name, + 'parent' => $parent, + )); if (isset($value)) { - $wrapper->setValue($value, FALSE); + $typed_data->setValue($value, FALSE); } - return $wrapper; + return $typed_data; } /** @@ -335,18 +315,20 @@ public function getValidationConstraintManager() { * * @see \Drupal\Core\Validation\ConstraintManager * - * @param array $definition - * A data definition array. + * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition + * A data definition. * * @return array * Array of constraints, each being an instance of * \Symfony\Component\Validator\Constraint. + * + * @todo: Having this as well as $definition->getConstraints() is confusing. */ - public function getConstraints($definition) { + public function getConstraints(DataDefinitionInterface $definition) { $constraints = array(); $validation_manager = $this->getValidationConstraintManager(); - $type_definition = $this->getDefinition($definition['type']); + $type_definition = $this->getDefinition($definition->getDataType()); // Auto-generate a constraint for data types implementing a primitive // interface. if (is_subclass_of($type_definition['class'], '\Drupal\Core\TypedData\PrimitiveInterface')) { @@ -363,19 +345,21 @@ public function getConstraints($definition) { } } // Add any constraints specified as part of the data definition. - if (isset($definition['constraints'])) { - foreach ($definition['constraints'] as $name => $options) { - $constraints[] = $validation_manager->create($name, $options); - } + $defined_constraints = $definition->getConstraints(); + foreach ($defined_constraints as $name => $options) { + $constraints[] = $validation_manager->create($name, $options); } // Add the NotNull constraint for required data. - if (!empty($definition['required']) && empty($definition['constraints']['NotNull'])) { + if ($definition->isRequired() && !isset($defined_constraints['NotNull'])) { $constraints[] = $validation_manager->create('NotNull', array()); } // If the definition does not provide a class use the class from the type // definition for performing interface checks. - $class = isset($definition['class']) ? $definition['class'] : $type_definition['class']; + $class = $definition->getClass(); + if (!$class) { + $class = $type_definition['class']; + } // Check if the class provides allowed values. if (array_key_exists('Drupal\Core\TypedData\AllowedValuesInterface', class_implements($class))) { $constraints[] = $validation_manager->create('AllowedValues', array()); diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php index ba827e6..647188d 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php @@ -19,6 +19,9 @@ class ValidReferenceConstraintValidator extends ConstraintValidator { * {@inheritdoc} */ public function validate($value, Constraint $constraint) { + if (!isset($value)) { + return; + } $id = $value->get('target_id')->getValue(); // '0' or NULL are considered valid empty references. if (empty($id)) { diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc index 6d02b9d..37e4a6b 100644 --- a/core/modules/content_translation/content_translation.admin.inc +++ b/core/modules/content_translation/content_translation.admin.inc @@ -101,7 +101,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$ // translation. // @todo Remove this special casing as soon as configurable and // base field definitions are "unified". - if (!empty($definition['configurable']) && ($field = FieldService::fieldInfo()->getField($entity_type, $field_name))) { + if ($definition->isFieldConfigurable() && ($field = FieldService::fieldInfo()->getField($entity_type, $field_name))) { $instance = FieldService::fieldInfo()->getInstance($entity_type, $bundle, $field_name); $form['settings'][$entity_type][$bundle]['fields'][$field_name] = array( '#label' => $instance->getFieldLabel(), diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index 661aac9..7c0d1a2 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -145,7 +145,15 @@ function content_translation_entity_field_info_alter(&$info, $entity_type) { foreach ($fields as $name => $translatable) { foreach ($keys as $key) { if (isset($info[$key][$name])) { - $info[$key][$name]['translatable'] = (bool) $translatable; + $translatable = (bool) $translatable; + // @todo: Remove array check after all base fields have been + // converted to object in https://drupal.org/node/2112239 + if (is_array($info[$key][$name])) { + $info[$key][$name]['translatable'] = $translatable; + } + elseif (is_object($info[$key][$name])) { + $info[$key][$name]->setTranslatable($translatable); + } break; } } @@ -631,7 +639,7 @@ function content_translation_form_alter(array &$form, array &$form_state) { if (!$entity->isNew() && (!isset($translations[$form_langcode]) || count($translations) > 1)) { foreach ($entity->getPropertyDefinitions() as $property_name => $definition) { if (isset($form[$property_name])) { - $form[$property_name]['#multilingual'] = !empty($definition['translatable']); + $form[$property_name]['#multilingual'] = $definition->isFieldTranslatable(); } } } diff --git a/core/modules/datetime/lib/Drupal/datetime/DateTimeComputed.php b/core/modules/datetime/lib/Drupal/datetime/DateTimeComputed.php index c0f6123..d6954b9 100644 --- a/core/modules/datetime/lib/Drupal/datetime/DateTimeComputed.php +++ b/core/modules/datetime/lib/Drupal/datetime/DateTimeComputed.php @@ -29,7 +29,7 @@ class DateTimeComputed extends TypedData { /** * {@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['settings']['date source'])) { throw new \InvalidArgumentException("The definition's 'date source' key has to specify the name of the date property to be computed."); diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module index 339e255..2ae7c02 100644 --- a/core/modules/edit/edit.module +++ b/core/modules/edit/edit.module @@ -186,7 +186,7 @@ function edit_preprocess_field(&$variables) { // Fields that are not part of the entity (i.e. dynamically injected "pseudo // fields") and computed fields are not editable. $definition = $entity->getPropertyDefinition($element['#field_name']); - if ($definition && empty($definition['computed'])) { + if ($definition && !$definition->isComputed()) { $variables['attributes']['data-edit-id'] = $entity->entityType() . '/' . $entity->id() . '/' . $element['#field_name'] . '/' . $element['#language'] . '/' . $element['#view_mode']; } } diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 6364102..7807fc6 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -550,7 +550,7 @@ function _editor_get_processed_text_fields(ContentEntityInterface $entity) { // Find all configurable fields, because only they could have a // text_processing setting. $configurable_fields = array_keys(array_filter($properties, function ($definition) { - return isset($definition['configurable']) && $definition['configurable'] === TRUE; + return $definition->isFieldConfigurable(); })); if (empty($configurable_fields)) { return array(); diff --git a/core/modules/email/email.module b/core/modules/email/email.module index bee295c..ccade35 100644 --- a/core/modules/email/email.module +++ b/core/modules/email/email.module @@ -33,7 +33,7 @@ function email_help($path, $arg) { function email_field_info_alter(&$info) { $info['email']['configurable'] = TRUE; $info['email']['class'] = '\Drupal\email\ConfigurableEmailItem'; - $info['email']['list_class'] = '\Drupal\Core\Field\ConfigFieldItemList'; + $info['email']['list_type'] = 'field_configurable_item_list'; $info['email']['default_widget'] = 'email_default'; if (\Drupal::moduleHandler()->moduleExists('text')) { $info['email']['default_formatter'] = 'text_plain'; diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module index 508af8a..f45f9ee 100644 --- a/core/modules/entity_reference/entity_reference.module +++ b/core/modules/entity_reference/entity_reference.module @@ -18,7 +18,7 @@ function entity_reference_field_info_alter(&$info) { // Make the entity reference field configurable. $info['entity_reference']['configurable'] = TRUE; $info['entity_reference']['class'] = '\Drupal\entity_reference\ConfigurableEntityReferenceItem'; - $info['entity_reference']['list_class'] = '\Drupal\Core\Field\ConfigFieldItemList'; + $info['entity_reference']['list_type'] = 'field_configurable_item_list'; $info['entity_reference']['settings']['target_type'] = \Drupal::moduleHandler()->moduleExists('node') ? 'node' : 'user'; $info['entity_reference']['instance_settings']['handler'] = 'default'; $info['entity_reference']['instance_settings']['handler_settings'] = array(); @@ -27,34 +27,6 @@ function entity_reference_field_info_alter(&$info) { } /** - * Implements hook_entity_field_info_alter(). - * - * Set the "target_type" property definition for entity reference fields. - * - * @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::getPropertyDefinitions() - * - * @param array $info - * The property info array as returned by hook_entity_field_info(). - * @param string $entity_type - * The entity type for which entity properties are defined. - */ -function entity_reference_entity_field_info_alter(&$info, $entity_type) { - foreach (field_info_instances($entity_type) as $bundle_name => $instances) { - foreach ($instances as $field_name => $instance) { - if ($instance->getFieldType() != 'entity_reference') { - continue; - } - if (isset($info['definitions'][$field_name])) { - $info['definitions'][$field_name]['settings']['target_type'] = $instance->getFieldSetting('target_type'); - } - elseif (isset($info['optional'][$field_name])) { - $info['optional'][$field_name]['settings']['target_type'] = $instance->getFieldSetting('target_type'); - } - } - } -} - -/** * Implements hook_field_widget_info_alter(). */ function entity_reference_field_widget_info_alter(&$info) { diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc index db6f339..547aa72 100644 --- a/core/modules/field/field.deprecated.inc +++ b/core/modules/field/field.deprecated.inc @@ -613,7 +613,7 @@ function field_attach_form_validate(ContentEntityInterface $entity, $form, &$for $has_violations = FALSE; foreach ($entity as $field_name => $field) { $definition = $field->getDefinition(); - if (!empty($definition['configurable']) && (empty($options['field_name']) || $options['field_name'] == $field_name)) { + if ($definition->isFieldConfigurable() && (empty($options['field_name']) || $options['field_name'] == $field_name)) { $field_violations = $field->validate(); if (count($field_violations)) { $has_violations = TRUE; diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 2f0de84..260411c 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -199,16 +199,16 @@ function field_entity_field_info($entity_type) { foreach (field_info_instances($entity_type) as $bundle_name => $instances) { $optional = $bundle_name != $entity_type; + // @todo: Improve hook_entity_field_info() so that it can take field + // instance also. foreach ($instances as $field_name => $instance) { - $definition = _field_generate_entity_field_definition($instance->getField()); - if ($optional) { - $property_info['optional'][$field_name] = $definition; + $property_info['optional'][$field_name] = $instance->getField(); $property_info['bundle map'][$bundle_name][] = $field_name; } else { - $property_info['definitions'][$field_name] = $definition; + $property_info['definitions'][$field_name] = $instance->getField(); } } } @@ -217,33 +217,6 @@ function field_entity_field_info($entity_type) { } /** - * Generates an entity field definition for a configurable field. - * - * @param \Drupal\field\FieldInterface $field - * The field definition. - * @param \Drupal\field\FieldInstanceInterface $instance - * (Optional) The field instance definition. - * - * @return array - * The entity field definition. - */ -function _field_generate_entity_field_definition(FieldInterface $field, FieldInstanceInterface $instance = NULL) { - // @todo: Allow for adding field type settings. - $definition = array( - 'label' => t('Field !name', array('!name' => $field->name)), - 'type' => 'field_item:' . $field->type, - 'list' => TRUE, - 'configurable' => TRUE, - 'translatable' => !empty($field->translatable), - ); - if ($instance) { - $definition['instance'] = $instance; - } - - return $definition; -} - -/** * Implements hook_entity_bundle_create(). */ function field_entity_bundle_create($entity_type, $bundle) { diff --git a/core/modules/field/lib/Drupal/field/Entity/Field.php b/core/modules/field/lib/Drupal/field/Entity/Field.php index 6fb8ab7..98d3112 100644 --- a/core/modules/field/lib/Drupal/field/Entity/Field.php +++ b/core/modules/field/lib/Drupal/field/Entity/Field.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\TypedData\DataDefinition; use Drupal\field\FieldException; use Drupal\field\FieldInterface; @@ -197,6 +198,13 @@ class Field extends ConfigEntityBase implements FieldInterface { public $original = NULL; /** + * The data definition of a field item. + * + * @var \Drupal\Core\TypedData\DataDefinition + */ + protected $itemDefinition; + + /** * Constructs a Field object. * * @param array $values @@ -570,6 +578,20 @@ public function isFieldTranslatable() { } /** + * Sets whether the field is translatable. + * + * @param bool $translatable + * Whether the field is translatable. + * + * @return \Drupal\field\Entity\Field + * The object itself for chaining. + */ + public function setTranslatable($translatable) { + $this->translatable = $translatable; + return $this; + } + + /** * {@inheritdoc} */ public function getFieldLabel() { @@ -580,7 +602,7 @@ public function getFieldLabel() { * {@inheritdoc} */ public function getFieldDescription() { - return ''; + return NULL; } /** @@ -647,6 +669,109 @@ public function hasData() { } /** + * {@inheritdoc} + */ + public function isFieldQueryable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getDataType() { + // Derive list type from the item type. + $item_type_definition = \Drupal::typedData() + ->getDefinition($this->getItemDefinition()->getDataType()); + return isset($item_type_definition['list_type']) ? $item_type_definition['list_type'] : 'field_configurable_item_list'; + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return $this->label(); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->getFieldDescription(); + } + + /** + * {@inheritdoc} + */ + public function isList() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function isReadOnly() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isComputed() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isRequired() { + return $this->isFieldRequired(); + } + + /** + * {@inheritdoc} + */ + public function getClass() { + // For BC check for a defined list class. + // @todo: Remove once https://drupal.org/node/2112239 is in. + $item_type_definition = \Drupal::typedData() + ->getDefinition($this->getItemDefinition()->getDataType()); + + if (isset($item_type_definition['list_class'])) { + return $item_type_definition['list_class']; + } + } + + /** + * {@inheritdoc} + */ + public function getSettings() { + // This should actually return the settings for field item list, which are + // not the field settings. However, there is no harm in returning field + // settings here, so we do that to avoid confusion for now. + // @todo: Unify with getFieldSettings() or remove once typed data moved + // to the adapter approach. + return $this->getFieldSettings(); + } + + /** + * {@inheritdoc} + */ + public function getConstraints() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getItemDefinition() { + if (!isset($this->itemDefinition)) { + $this->itemDefinition = DataDefinition::create('field_item:' . $this->type) + ->setSettings($this->getFieldSettings()); + } + return $this->itemDefinition; + } + + /** * Implements the magic __sleep() method. * * Using the Serialize interface and serialize() / unserialize() methods diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php index 65013c0..19e2ab9 100644 --- a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php +++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php @@ -10,6 +10,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityInterface; use Drupal\field\FieldException; +use Drupal\Core\TypedData\DataDefinition; use Drupal\field\FieldInstanceInterface; /** @@ -207,6 +208,13 @@ class FieldInstance extends ConfigEntityBase implements FieldInstanceInterface { public $original = NULL; /** + * The data definition of a field item. + * + * @var \Drupal\Core\TypedData\DataDefinition + */ + protected $itemDefinition; + + /** * Constructs a FieldInstance object. * * @param array $values @@ -611,6 +619,13 @@ public function isFieldConfigurable() { return TRUE; } + /** + * {@inheritdoc} + */ + public function isFieldQueryable() { + return TRUE; + } + /* * Implements the magic __sleep() method. * @@ -632,4 +647,92 @@ public function __wakeup() { $this->__construct($values); } + public function getDataType() { + // Derive list type from the item type. + $item_type_definition = \Drupal::typedData() + ->getDefinition($this->getItemDefinition()->getDataType()); + return isset($item_type_definition['list_type']) ? $item_type_definition['list_type'] : 'field_configurable_item_list'; + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return $this->label(); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->getFieldDescription(); + } + + /** + * {@inheritdoc} + */ + public function isList() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function isReadOnly() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isComputed() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isRequired() { + // Only field instances can be required. + return $this->isFieldRequired(); + } + + /** + * {@inheritdoc} + */ + public function getClass() { + // For BC check for a defined list class. + // @todo: Remove once all list_class usages have been converted. + $item_type_definition = \Drupal::typedData() + ->getDefinition($this->getItemDefinition()->getDataType()); + + if (isset($item_type_definition['list_class'])) { + return $item_type_definition['list_class']; + } + } + + /** + * {@inheritdoc} + */ + public function getSettings() { + return $this->getFieldSettings(); + } + + /** + * {@inheritdoc} + */ + public function getConstraints() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getItemDefinition() { + if (!isset($this->itemDefinition)) { + $this->itemDefinition = DataDefinition::create('field_item:' . $this->field->type) + ->setSettings($this->getFieldSettings()); + } + return $this->itemDefinition; + } } diff --git a/core/modules/file/lib/Drupal/file/Plugin/Field/FieldType/FileFieldItemList.php b/core/modules/file/lib/Drupal/file/Plugin/Field/FieldType/FileFieldItemList.php index c9a88ab..7e06b89 100644 --- a/core/modules/file/lib/Drupal/file/Plugin/Field/FieldType/FileFieldItemList.php +++ b/core/modules/file/lib/Drupal/file/Plugin/Field/FieldType/FileFieldItemList.php @@ -7,7 +7,7 @@ namespace Drupal\file\Plugin\Field\FieldType; -use Drupal\Core\Field\ConfigFieldItemList; +use Drupal\Core\Field\Plugin\DataType\ConfigFieldItemList; /** * Represents a configurable entity file field. diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php index 17aacc0..593a501 100644 --- a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php @@ -64,8 +64,8 @@ public function normalize($field_item, $format = NULL, array $context = array()) */ protected function constructValue($data, $context) { $field_item = $context['target_instance']; - $field_definition = $field_item->getDefinition(); - $target_type = $field_definition['settings']['target_type']; + $field_definition = $field_item->getFieldDefinition(); + $target_type = $field_definition->getFieldSetting('target_type'); if ($id = $this->entityResolver->resolve($this, $data, $target_type)) { return array('target_id' => $id); } diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php index 008f20e..2d60eb6 100644 --- a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php @@ -57,8 +57,8 @@ public function denormalize($data, $class, $format = NULL, array $context = arra if (isset($data['lang'])) { $langcode = $data['lang']; unset($data['lang']); - $field_definition = $field_item->getDefinition(); - if ($field_definition['translatable']) { + $field_definition = $field_item->getFieldDefinition(); + if ($field_definition->isFieldTranslatable()) { $field_item = $this->createTranslatedInstance($field_item, $langcode); } } diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php index bd3472f..b433c79 100644 --- a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php @@ -33,11 +33,11 @@ public function normalize($field, $format = NULL, array $context = array()) { // Get the field definition. $entity = $field->getEntity(); $field_name = $field->getName(); - $field_definition = $entity->getPropertyDefinition($field_name); + $field_definition = $field->getFieldDefinition(); // If this field is not translatable, it can simply be normalized without // separating it into different translations. - if (empty($field_definition['translatable'])) { + if (!$field_definition->isFieldTranslatable()) { $normalized_field_items = $this->normalizeFieldItems($field, $format, $context); } // Otherwise, the languages have to be extracted from the entity and passed diff --git a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php index 5d6b200..4abf5d3 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php @@ -11,6 +11,7 @@ use Drupal\Core\TypedData\ContextAwareInterface; use Drupal\Core\Config\Schema\Element; use Drupal\Core\Config\Schema\ArrayElement; +use Drupal\Core\TypedData\DataDefinitionInterface; /** * Defines the locale configuration wrapper object. @@ -41,7 +42,7 @@ class LocaleTypedConfig extends Element { /** * Constructs a configuration wrapper object. * - * @param array $definition + * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition * The data definition. * @param string $name * The configuration object name. @@ -50,7 +51,7 @@ class LocaleTypedConfig extends Element { * @param \Drupal\locale\LocaleConfigManager $localeConfig; * The locale configuration manager object. */ - public function __construct(array $definition, $name, $langcode, \Drupal\locale\LocaleConfigManager $localeConfig) { + public function __construct($definition, $name, $langcode, \Drupal\locale\LocaleConfigManager $localeConfig) { parent::__construct($definition, $name); $this->langcode = $langcode; $this->localeConfig = $localeConfig; diff --git a/core/modules/serialization/tests/Drupal/serialization/Tests/Normalizer/ListNormalizerTest.php b/core/modules/serialization/tests/Drupal/serialization/Tests/Normalizer/ListNormalizerTest.php index f61c675..6c8315d 100644 --- a/core/modules/serialization/tests/Drupal/serialization/Tests/Normalizer/ListNormalizerTest.php +++ b/core/modules/serialization/tests/Drupal/serialization/Tests/Normalizer/ListNormalizerTest.php @@ -9,8 +9,7 @@ use Drupal\Tests\UnitTestCase; use Drupal\serialization\Normalizer\ListNormalizer; -use Drupal\Core\TypedData\ItemList; -use Drupal\Core\TypedData\Plugin\DataType\Integer; +use Drupal\Core\TypedData\Plugin\DataType\ItemList; /** * Tests the ListNormalizer class. diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php index 296c429..14cda3c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php @@ -353,21 +353,19 @@ public function testIntrospection() { */ protected function checkIntrospection($entity_type) { // Test getting metadata upfront. - // @todo: Make this work without having to create entity objects. - $entity = entity_create($entity_type, array()); - $definitions = $entity->getPropertyDefinitions(); - $this->assertEqual($definitions['name']['type'], 'field_item:string', $entity_type .': Name field found.'); - $this->assertEqual($definitions['user_id']['type'], 'field_item:entity_reference', $entity_type .': User field found.'); - $this->assertEqual($definitions['field_test_text']['type'], 'field_item:text', $entity_type .': Test-text-field field found.'); + $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type); + $this->assertEqual($definitions['name']->getFieldType(), 'string', $entity_type .': Name field found.'); + $this->assertEqual($definitions['user_id']->getFieldType(), 'entity_reference', $entity_type .': User field found.'); + $this->assertEqual($definitions['field_test_text']->getFieldType(), 'text', $entity_type .': Test-text-field field found.'); // Test introspecting an entity object. // @todo: Add bundles and test bundles as well. $entity = entity_create($entity_type, array()); $definitions = $entity->getPropertyDefinitions(); - $this->assertEqual($definitions['name']['type'], 'field_item:string', $entity_type .': Name field found.'); - $this->assertEqual($definitions['user_id']['type'], 'field_item:entity_reference', $entity_type .': User field found.'); - $this->assertEqual($definitions['field_test_text']['type'], 'field_item:text', $entity_type .': Test-text-field field found.'); + $this->assertEqual($definitions['name']->getFieldType(), 'string', $entity_type .': Name field found.'); + $this->assertEqual($definitions['user_id']->getFieldType(), 'entity_reference', $entity_type .': User field found.'); + $this->assertEqual($definitions['field_test_text']->getFieldType(), 'text', $entity_type .': Test-text-field field found.'); $name_properties = $entity->name->getPropertyDefinitions(); $this->assertEqual($name_properties['value']['type'], 'string', $entity_type .': String value property of the name found.'); diff --git a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php index 8258a40..570370f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\TypedData; use Drupal\Component\Utility\String; +use Drupal\Core\TypedData\ListDefinition; use Drupal\simpletest\DrupalUnitTestBase; use Drupal\Core\Datetime\DrupalDateTime; @@ -313,10 +314,9 @@ public function testGetAndSet() { public function testTypedDataLists() { // Test working with an existing list of strings. $value = array('one', 'two', 'three'); - $typed_data = $this->createTypedData(array( - 'type' => 'string', - 'list' => TRUE, - ), $value); + $list_definition = ListDefinition::create(); + $list_definition->getItemDefinition()->setDataType('string'); + $typed_data = $this->createTypedData($list_definition, $value); $this->assertEqual($typed_data->getValue(), $value, 'List value has been set.'); // Test iterating. $count = 0; diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php index 561572c..74f296d 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php @@ -11,7 +11,9 @@ use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Field\FieldDefinition; use Drupal\Core\Language\Language; +use Drupal\Core\TypedData\DataDefinition; use Drupal\taxonomy\TermInterface; /** @@ -201,67 +203,66 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $ * {@inheritdoc} */ public static function baseFieldDefinitions($entity_type) { - $properties['tid'] = array( - 'label' => t('Term ID'), - 'description' => t('The term ID.'), - 'type' => 'integer_field', - 'read-only' => TRUE, - ); - $properties['uuid'] = array( - 'label' => t('UUID'), - 'description' => t('The term UUID.'), - 'type' => 'uuid_field', - 'read-only' => TRUE, - ); - $properties['vid'] = array( - 'label' => t('Vocabulary ID'), - 'description' => t('The ID of the vocabulary to which the term is assigned.'), - 'type' => 'string_field', - ); - $properties['langcode'] = array( - 'label' => t('Language code'), - 'description' => t('The term language code.'), - 'type' => 'language_field', - ); - $properties['name'] = array( - 'label' => t('Name'), - 'description' => t('The term name.'), - 'type' => 'string_field', - ); - $properties['description'] = array( - 'label' => t('Description'), - 'description' => t('A description of the term'), - 'type' => 'string_field', - ); + + $fields['tid'] = FieldDefinition::create() + ->setLabel(t('Term ID')) + ->setDescription(t('The term ID.')) + ->setFieldType('integer') + ->setReadOnly(TRUE); + + $fields['uuid'] = FieldDefinition::create() + ->setLabel(t('UUID')) + ->setDescription(t('The term UUID.')) + ->setFieldType('uuid') + ->setReadOnly(TRUE); + + $fields['vid'] = FieldDefinition::create() + ->setLabel(t('Vocabulary ID')) + ->setDescription(t('The ID of the vocabulary to which the term is assigned.')) + ->setFieldType('string'); + + $fields['langcode'] = FieldDefinition::create() + ->setLabel(t('Language code')) + ->setDescription(t('The term language code.')) + ->setFieldType('language'); + + $fields['name'] = FieldDefinition::create() + ->setLabel(t('Name')) + ->setDescription(t('The term name.')) + ->setFieldType('string'); + + $fields['description'] = FieldDefinition::create() + ->setLabel(t('Description')) + ->setDescription(t('A description of the term.')) + ->setFieldType('string'); + // @todo Combine with description. - $properties['format'] = array( - 'label' => t('Description format'), - 'description' => t('The filter format ID of the description.'), - 'type' => 'string_field', - ); - $properties['weight'] = array( - 'label' => t('Weight'), - 'description' => t('The weight of this term in relation to other terms.'), - 'type' => 'integer_field', - 'settings' => array('default_value' => 0), - ); - $properties['parent'] = array( - 'label' => t('Term Parents'), - 'description' => t('The parents of this term.'), - 'type' => 'integer_field', + $fields['format'] = FieldDefinition::create() + ->setLabel(t('Description format')) + ->setDescription(t('The filter format ID of the description.')) + ->setFieldType('string'); + + $fields['weight'] = FieldDefinition::create() + ->setLabel(t('Weight')) + ->setDescription(t('The weight of this term in relation to other terms.')) + ->setFieldType('integer') + ->setFieldSetting('default_value', 0); + + $fields['parent'] = FieldDefinition::create() + ->setLabel(t('Term Parents')) + ->setDescription(t('The parents of this term.')) + ->setFieldType('integer') // Save new terms with no parents by default. - 'settings' => array('default_value' => 0), - 'computed' => TRUE, - ); - $properties['changed'] = array( - 'label' => t('Changed'), - 'description' => t('The time that the term was last edited.'), - 'type' => 'integer_field', - 'property_constraints' => array( - 'value' => array('EntityChanged' => array()), - ), - ); - return $properties; + ->setFieldSetting('default_value', 0) + ->setComputed(TRUE); + + $fields['changed'] = FieldDefinition::create() + ->setLabel(t('Changed')) + ->setDescription(t('The time that the term was last edited.')) + ->setFieldType('integer') + ->setPropertyConstraints('value', array('EntityChanged' => array())); + + return $fields; } /** diff --git a/core/modules/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index bbf0fd0..dd1fe8e 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -29,7 +29,7 @@ class TextProcessed extends TypedData { /** * Overrides TypedData::__construct(). */ - 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['settings']['text source'])) { diff --git a/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php b/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php index 3d60feb..3b14d87 100644 --- a/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php @@ -26,6 +26,34 @@ public static function getInfo() { } /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Prepare a container with a mock typed data object, that returns no + // type definitions. + // @todo: Overhaul this once field definitions to not use the container to + /// get the manager any more. + $typed_data = $this->getMockBuilder('Drupal\Core\TypedData\TypedDataManager') + ->disableOriginalConstructor() + ->getMock(); + + $typed_data + ->expects($this->any()) + ->method('getDefinition') + ->will($this->returnValue(NULL)); + + $container = $this->getMock('Drupal\Core\DependencyInjection\Container'); + $container + ->expects($this->any()) + ->method('get') + ->will($this->returnValue($typed_data)); + + \Drupal::setContainer($container); + } + + /** * Tests field name methods. */ public function testFieldName() {