diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 67e5690..d4b5bbb 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\Entity\Field\FieldDefinition; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManager; use Drupal\Core\Language\Language; @@ -455,15 +456,8 @@ public function getAdminRouteInfo($entity_type, $bundle) { * 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. + * An array of entity field definitions, keyed by field name. See + * \Drupal\Core\Entity\Field\FieldDefinitionInterface. * * @see \Drupal\Core\TypedData\TypedDataManager::create() * @see \Drupal\Core\Entity\EntityManager::getFieldDefinitionsByConstraints() @@ -476,6 +470,7 @@ public function getFieldDefinitions($entity_type, $bundle = NULL) { $this->entityFieldInfo[$entity_type] = $cache->data; } else { + // @todo: Refactor to allow for per-bundle overrides. $class = $this->factory->getPluginClass($entity_type, $this->getDefinition($entity_type)); $this->entityFieldInfo[$entity_type] = array( 'definitions' => $class::baseFieldDefinitions($entity_type), @@ -495,12 +490,16 @@ 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 by default. - foreach ($this->entityFieldInfo[$entity_type]['definitions'] as &$definition) { - $definition['list'] = TRUE; + // Enforce field definitions to be objects. + foreach ($this->entityFieldInfo[$entity_type]['definitions'] as $field_name => &$definition) { + if (is_array($definition)) { + $definition = FieldDefinition::createFromOldStyleDefinition($field_name, $definition); + } } - foreach ($this->entityFieldInfo[$entity_type]['optional'] as &$definition) { - $definition['list'] = TRUE; + foreach ($this->entityFieldInfo[$entity_type]['optional'] as $field_name => &$definition) { + if (is_array($definition)) { + $definition = FieldDefinition::createFromOldStyleDefinition($field_name, $definition); + } } $this->cache->set($cid, $this->entityFieldInfo[$entity_type], CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE, 'entity_field_info' => TRUE)); } diff --git a/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php index ef79967..42dcdc3 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php @@ -6,34 +6,20 @@ */ namespace Drupal\Core\Entity\Field; + use Drupal\Core\Entity\EntityInterface; +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} */ public function getFieldName() { + // @todo: Should "name" be part of the data definition also? return $this->definition['field_name']; } @@ -56,7 +42,7 @@ public function setFieldName($name) { */ public function getFieldType() { // Cut of the leading field_item: prefix from 'field_item:FIELD_TYPE'. - $parts = explode(':', $this->definition['type']); + $parts = explode(':', $this->getItemDefinition()->getDataType()); return $parts[1]; } @@ -70,43 +56,60 @@ 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\Entity\Field\FieldDefinition * The object itself for chaining. */ - public function setFieldSetting($setting_name, $value) { - $this->definition['settings'][$setting_name] = $value; - return $this; + public function setFieldSettings(array $settings) { + return $this->getItemDefinition()->setSettings($settings); } /** * {@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\Entity\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()); } /** @@ -134,42 +137,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\Entity\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\Entity\Field\FieldDefinition - * The object itself for chaining. + * {@inheritdoc} */ public function setFieldDescription($description) { - $this->definition['description'] = $description; - return $this; + return $this->setDescription($description); } /** @@ -184,21 +173,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. + * @param boolean $required + * Whether the field is required. * * @return \Drupal\Core\Entity\Field\FieldDefinition * The object itself for chaining. */ public function setFieldRequired($required) { - $this->definition['required'] = $required; - return $this; + 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 boolean $queryable + * Whether the field is queryable. + * + * @return \Drupal\Core\Entity\Field\FieldDefinition + * The object itself for chaining. + */ + public function setFieldQueryable($queryable) { + return $this->setRequired($queryable); } /** @@ -213,7 +221,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; } @@ -231,4 +241,41 @@ public function getFieldDefaultValue(EntityInterface $entity) { return $this->getFieldSetting('default_value'); } + /** + * Allows creating field definition objects from old style definition arrays. + * + * @todo: Remove once no old-style definition arrays need to be supported. + */ + public static function createFromOldStyleDefinition($field_name, array $definition) { + unset($definition['list']); + + $list_definition = $definition; + $list_definition['type'] = 'entity_field'; + unset($list_definition['constraints']); + unset($list_definition['settings']); + + $new = new FieldDefinition($list_definition); + if (isset($definition['list_class'])) { + $new->setClass($definition['list_class']); + } + else { + $type_definition = \Drupal::typedData()->getDefinition($definition['type']); + if (isset($type_definition['list_class'])) { + $new->setClass($type_definition['list_class']); + } + } + + // Take care of the item definition. + $new->setItemDefinition(new FieldDefinition($definition)); + $new->setFieldName($field_name); + $new->setFieldSettings($definition['settings']); + if (isset($definition['constraints'])) { + $new->getItemDefinition()->setConstraints($definition['constraints']); + } + if (isset($definition['class'])) { + $new->getItemDefinition()->setClass($definition['class']); + } + return $new; + } + } diff --git a/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php index b8dc354..c2bc4ce 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php @@ -13,9 +13,10 @@ * 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\Entity\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\Entity\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 +37,9 @@ * * However, entity base fields, such as $node->title, are not managed by * field.module and its "field_entity"/"field_instance" configuration entities. - * Therefore, their definitions are provided by different objects that implement - * this interface. - * @todo That is still in progress: https://drupal.org/node/1949932. Update this - * documentation with details when that's implemented. + * Therefore, their definitions are provided by different objects based on the + * class \Drupal\Core\Entity\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 @@ -50,6 +50,8 @@ * abstractly, and present Views configuration options to the administrator * based on that abstract definition, even though that abstract definition can * differ from the concrete definition of any particular node's body field. + * + * @todo Extends this from DataDefinitionInterface. */ interface FieldDefinitionInterface { @@ -129,6 +131,13 @@ public function isFieldTranslatable(); public function isFieldConfigurable(); /** + * Determines whether the field is queryable via QueryInterface. + * + * @return bool + */ + public function isFieldQueryable(); + + /** * Returns the human-readable label for the field. * * @return string diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php index 15744c0..d459c49 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php @@ -8,6 +8,8 @@ namespace Drupal\Core\Entity\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\Entity\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/Entity/Field/FieldItemList.php b/core/lib/Drupal/Core/Entity/Field/FieldItemList.php index 4b4e1eb..cf4e3aa 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemList.php @@ -10,7 +10,7 @@ use Drupal\Core\Entity\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; /** @@ -45,7 +45,7 @@ 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 @@ -109,7 +109,7 @@ public function getValue($include_computed = FALSE) { } /** - * Overrides \Drupal\Core\TypedData\ItemList::setValue(). + * Overrides \Drupal\Core\TypedData\Plugin\DataType\ItemList::setValue(). */ public function setValue($values, $notify = TRUE) { if (!isset($values) || $values === array()) { diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/FieldItemList.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/FieldItemList.php new file mode 100644 index 0000000..9a8225b --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/FieldItemList.php @@ -0,0 +1,26 @@ +definition = $definition; + } + + /** + * {@inheritdoc} + */ + public function getDataType() { + return $this->definition['type']; + } + + /** + * Sets the data type. + * + * @param string $type + * The data type to set. + * + * @return \Drupal\Core\TypedData\DataDefinition + * 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 $this->definition['read-only']; + } + + /** + * Sets whether the data is read-only. + * + * @param boolean $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 boolean $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 boolean $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} + */ + public function offsetExists($offset) { + return array_key_exists($offset, $this->definition); + } + + /** + * {@inheritdoc} + */ + public function &offsetGet($offset) { + if (!isset($this->definition[$offset])) { + $this->definition[$offset] = NULL; + } + return $this->definition[$offset]; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) { + $this->definition[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) { + unset($this->definition[$offset]); + } + + /** + * Returns all definition values as array. + * + * @return array + */ + public function toArray() { + return $this->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..d866d15 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php @@ -0,0 +1,115 @@ +list)) { - $values = array(); - foreach ($this->list as $delta => $item) { - $values[$delta] = $item->getValue(); - } - return $values; - } - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::setValue(). - * - * @param array|null $values - * An array of values of the field items, or NULL to unset the field. - */ - public function setValue($values, $notify = TRUE) { - if (!isset($values) || $values === array()) { - $this->list = $values; - } - else { - if (!is_array($values)) { - throw new \InvalidArgumentException('Cannot set a list with a non-array value.'); - } - - // Clear the values of properties for which no value has been passed. - if (isset($this->list)) { - $this->list = array_intersect_key($this->list, $values); - } - - // Set the values. - foreach ($values as $delta => $value) { - if (!is_numeric($delta)) { - throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.'); - } - elseif (!isset($this->list[$delta])) { - $this->list[$delta] = $this->createItem($delta, $value); - } - else { - $this->list[$delta]->setValue($value); - } - } - } - // Notify the parent of any changes. - if ($notify && isset($this->parent)) { - $this->parent->onChange($this->name); - } - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::getString(). - */ - public function getString() { - $strings = array(); - if (isset($this->list)) { - foreach ($this->list as $item) { - $strings[] = $item->getString(); - } - // Remove any empty strings resulting from empty items. - return implode(', ', array_filter($strings, 'drupal_strlen')); - } - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::getConstraints(). - */ - public function getConstraints() { - // Apply the constraints to the list items only. - return array(); - } - - /** - * Implements \ArrayAccess::offsetExists(). - */ - public function offsetExists($offset) { - return isset($this->list) && array_key_exists($offset, $this->list) && $this->offsetGet($offset)->getValue() !== NULL; - } - - /** - * Implements \ArrayAccess::offsetUnset(). - */ - public function offsetUnset($offset) { - if (isset($this->list)) { - unset($this->list[$offset]); - } - } - - /** - * Implements \ArrayAccess::offsetGet(). - */ - public function offsetGet($offset) { - if (!is_numeric($offset)) { - throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.'); - } - // Allow getting not yet existing items as well. - // @todo: Maybe add a public createItem() method in addition? - elseif (!isset($this->list[$offset])) { - $this->list[$offset] = $this->createItem($offset); - } - return $this->list[$offset]; - } - - /** - * Helper for creating a list item object. - * - * @return \Drupal\Core\TypedData\TypedDataInterface - */ - protected function createItem($offset = 0, $value = NULL) { - return \Drupal::typedData()->getPropertyInstance($this, $offset, $value); - } - - /** - * Implements \Drupal\Core\TypedData\ListInterface::getItemDefinition(). - */ - public function getItemDefinition() { - return array('list' => FALSE) + $this->definition; - } - - /** - * Implements \ArrayAccess::offsetSet(). - */ - public function offsetSet($offset, $value) { - if (!isset($offset)) { - // The [] operator has been used so point at a new entry. - $offset = $this->list ? max(array_keys($this->list)) + 1 : 0; - } - if (is_numeric($offset)) { - // Support setting values via typed data objects. - if ($value instanceof TypedDataInterface) { - $value = $value->getValue(); - } - $this->offsetGet($offset)->setValue($value); - } - else { - throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.'); - } - } - - /** - * Implements \IteratorAggregate::getIterator(). - */ - public function getIterator() { - if (isset($this->list)) { - return new \ArrayIterator($this->list); - } - return new \ArrayIterator(array()); - } - - /** - * Implements \Countable::count(). - */ - public function count() { - return isset($this->list) ? count($this->list) : 0; - } - - /** - * Implements \Drupal\Core\TypedData\ListInterface::isEmpty(). - */ - public function isEmpty() { - if (isset($this->list)) { - foreach ($this->list as $item) { - if ($item instanceof ComplexDataInterface || $item instanceof ListInterface) { - if (!$item->isEmpty()) { - return FALSE; - } - } - // Other items are treated as empty if they have no value only. - elseif ($item->getValue() !== NULL) { - return FALSE; - } - } - } - return TRUE; - } - - /** - * Implements \Drupal\Core\TypedData\ListInterface::onChange(). - */ - public function onChange($delta) { - // Notify the parent of changes. - if (isset($this->parent)) { - $this->parent->onChange($this->name); - } - } - - /** - * Magic method: Implements a deep clone. - */ - public function __clone() { - if (isset($this->list)) { - foreach ($this->list as $delta => $item) { - $this->list[$delta] = clone $item; - $this->list[$delta]->setContext($delta, $this); - } - } - } -} diff --git a/core/lib/Drupal/Core/TypedData/ListDefinition.php b/core/lib/Drupal/Core/TypedData/ListDefinition.php new file mode 100644 index 0000000..fc6b557 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ListDefinition.php @@ -0,0 +1,44 @@ +itemDefinition = new DataDefinition(); + } + + /** + * {@inheritdoc} + */ + public function getItemDefinition() { + return $this->itemDefinition; + } + + /** + * Sets the item definition. + */ + 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..0bf8aaf --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ListDefinitionInterface.php @@ -0,0 +1,26 @@ +list)) { + $values = array(); + foreach ($this->list as $delta => $item) { + $values[$delta] = $item->getValue(); + } + return $values; + } + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::setValue(). + * + * @param array|null $values + * An array of values of the field items, or NULL to unset the field. + */ + public function setValue($values, $notify = TRUE) { + if (!isset($values) || $values === array()) { + $this->list = $values; + } + else { + if (!is_array($values)) { + throw new \InvalidArgumentException('Cannot set a list with a non-array value.'); + } + + // Clear the values of properties for which no value has been passed. + if (isset($this->list)) { + $this->list = array_intersect_key($this->list, $values); + } + + // Set the values. + foreach ($values as $delta => $value) { + if (!is_numeric($delta)) { + throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.'); + } + elseif (!isset($this->list[$delta])) { + $this->list[$delta] = $this->createItem($delta, $value); + } + else { + $this->list[$delta]->setValue($value); + } + } + } + // Notify the parent of any changes. + if ($notify && isset($this->parent)) { + $this->parent->onChange($this->name); + } + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::getString(). + */ + public function getString() { + $strings = array(); + if (isset($this->list)) { + foreach ($this->list as $item) { + $strings[] = $item->getString(); + } + // Remove any empty strings resulting from empty items. + return implode(', ', array_filter($strings, 'drupal_strlen')); + } + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::getConstraints(). + */ + public function getConstraints() { + if (isset($this->definition['item_definition']) && isset($this->definition['constraints'])) { + return $this->definition['constraints']; + } + // BC: If no item definition is specified, we apply possibly specified + // constraints to the list items only. + return array(); + } + + /** + * Implements \ArrayAccess::offsetExists(). + */ + public function offsetExists($offset) { + return isset($this->list) && array_key_exists($offset, $this->list) && $this->offsetGet($offset)->getValue() !== NULL; + } + + /** + * Implements \ArrayAccess::offsetUnset(). + */ + public function offsetUnset($offset) { + if (isset($this->list)) { + unset($this->list[$offset]); + } + } + + /** + * Implements \ArrayAccess::offsetGet(). + */ + public function offsetGet($offset) { + if (!is_numeric($offset)) { + throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.'); + } + // Allow getting not yet existing items as well. + // @todo: Maybe add a public createItem() method in addition? + elseif (!isset($this->list[$offset])) { + $this->list[$offset] = $this->createItem($offset); + } + return $this->list[$offset]; + } + + /** + * Helper for creating a list item object. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + */ + protected function createItem($offset = 0, $value = NULL) { + return \Drupal::typedData()->getPropertyInstance($this, $offset, $value); + } + + /** + * Implements \Drupal\Core\TypedData\ListInterface::getItemDefinition(). + */ + public function getItemDefinition() { + return $this->definition->getItemDefinition(); + } + + /** + * Implements \ArrayAccess::offsetSet(). + */ + public function offsetSet($offset, $value) { + if (!isset($offset)) { + // The [] operator has been used so point at a new entry. + $offset = $this->list ? max(array_keys($this->list)) + 1 : 0; + } + if (is_numeric($offset)) { + // Support setting values via typed data objects. + if ($value instanceof TypedDataInterface) { + $value = $value->getValue(); + } + $this->offsetGet($offset)->setValue($value); + } + else { + throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.'); + } + } + + /** + * Implements \IteratorAggregate::getIterator(). + */ + public function getIterator() { + if (isset($this->list)) { + return new \ArrayIterator($this->list); + } + return new \ArrayIterator(array()); + } + + /** + * Implements \Countable::count(). + */ + public function count() { + return isset($this->list) ? count($this->list) : 0; + } + + /** + * Implements \Drupal\Core\TypedData\ListInterface::isEmpty(). + */ + public function isEmpty() { + if (isset($this->list)) { + foreach ($this->list as $item) { + if ($item instanceof ComplexDataInterface || $item instanceof ListInterface) { + if (!$item->isEmpty()) { + return FALSE; + } + } + // Other items are treated as empty if they have no value only. + elseif ($item->getValue() !== NULL) { + return FALSE; + } + } + } + return TRUE; + } + + /** + * Implements \Drupal\Core\TypedData\ListInterface::onChange(). + */ + public function onChange($delta) { + // Notify the parent of changes. + if (isset($this->parent)) { + $this->parent->onChange($this->name); + } + } + + /** + * Magic method: Implements a deep clone. + */ + public function __clone() { + if (isset($this->list)) { + foreach ($this->list as $delta => $item) { + $this->list[$delta] = clone $item; + $this->list[$delta]->setContext($delta, $this); + } + } + } +} diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index b97587b..8f8fb07 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 bbc0d7f..38d69e6 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -58,6 +58,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac * The id of a plugin, i.e. the data type. * @param array $configuration * The plugin configuration, i.e. the data definition. + * //TODO: update docs * @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. @@ -69,7 +70,8 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac * @return \Drupal\Core\TypedData\TypedDataInterface * The instantiated typed data object. */ - public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) { + public function createInstance($plugin_id, array $configuration) { + $data_definition = $configuration['data_definition']; $type_definition = $this->getDefinition($plugin_id); if (!isset($type_definition)) { @@ -78,51 +80,21 @@ public function createInstance($plugin_id, array $configuration, $name = NULL, $ // 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]; - } + $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)); } - 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. * @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. @@ -147,14 +119,20 @@ public function createInstance($plugin_id, array $configuration, $name = NULL, $ * @see \Drupal\Core\TypedData\Plugin\DataType\Date * @see \Drupal\Core\TypedData\Plugin\DataType\Uri * @see \Drupal\Core\TypedData\Plugin\DataType\Binary - * @see \Drupal\Core\Entity\Field\EntityWrapper */ - 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) { + if (is_array($definition)) { + $definition = new DataDefinition($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; } /** @@ -336,7 +314,7 @@ public function getValidationConstraintManager() { * * @see \Drupal\Core\Validation\ConstraintManager * - * @param array $definition + * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition * A data definition array. * * @return 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/field/lib/Drupal/field/Entity/Field.php b/core/modules/field/lib/Drupal/field/Entity/Field.php index 1b763b3..7d81cb8 100644 --- a/core/modules/field/lib/Drupal/field/Entity/Field.php +++ b/core/modules/field/lib/Drupal/field/Entity/Field.php @@ -698,6 +698,20 @@ public function hasData() { } /** + * {@inheritdoc} + */ + public function isFieldQueryable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function isFieldConfigurable() { + return TRUE; + } + + /** * Implements the magic __sleep() method. * * Using the Serialize interface and serialize() / unserialize() methods @@ -718,11 +732,4 @@ public function __wakeup() { $this->__construct($values); } - /** - * {@inheritdoc} - */ - public function isFieldConfigurable() { - return TRUE; - } - } diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php index 412bae2..954b6b6 100644 --- a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php +++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php @@ -617,6 +617,13 @@ public function isFieldConfigurable() { /** * {@inheritdoc} */ + public function isFieldQueryable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ public function offsetExists($offset) { return (isset($this->{$offset}) || $offset == 'field_id' || $offset == 'field_name'); } diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemList.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemList.php index 32bea80..2400e3d 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemList.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemList.php @@ -7,6 +7,7 @@ namespace Drupal\field\Plugin\Type\FieldType; +use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\Entity\Field\FieldItemList; use Drupal\field\Field as FieldAPI; @@ -26,7 +27,7 @@ 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']; 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/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index f3c65e2..aa20f8b 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -7,6 +7,7 @@ namespace Drupal\text; +use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedData; use Drupal\Core\TypedData\ReadOnlyException; @@ -29,7 +30,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'])) {