diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index 49418a0..9c8cb5e 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -10,7 +10,7 @@ use Drupal\Core\Entity\Field\FieldInterface; use Drupal\user\Plugin\Core\Entity\User; use Drupal\Core\TypedData\ContextAwareInterface; -use Drupal\Core\TypedData\ContextAwareTypedData; +use Drupal\Core\TypedData\ItemList; use Drupal\Core\TypedData\TypedDataInterface; use ArrayIterator; use IteratorAggregate; @@ -27,7 +27,7 @@ * * @see \Drupal\Core\Entity\Field\FieldInterface */ -class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInterface { +class Field extends ItemList implements IteratorAggregate, FieldInterface { /** * Numerically indexed array of field items, implementing the @@ -50,7 +50,7 @@ public function __construct(array $definition, $name = NULL, ContextAwareInterfa } /** - * Overrides \Drupal\Core\TypedData\TypedData::getValue(). + * Overrides \Drupal\Core\TypedData\ItemList::getValue(). */ public function getValue() { if (isset($this->list)) { @@ -68,147 +68,6 @@ public function getValue() { } /** - * 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) { - if (!isset($values) || $values === array()) { - $this->list = $values; - } - else { - // Support passing in only the value of the first item. - if (!is_array($values) || !is_numeric(current(array_keys($values)))) { - $values = array(0 => $values); - } - - // 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); - } - } - } - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::getString(). - */ - public function getString() { - $strings = array(); - if (isset($this->list)) { - foreach ($this->list as $item) { - $strings[] = $item->getString(); - } - return implode(', ', array_filter($strings)); - } - } - - /** - * 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); - } - - /** - * 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 typed_data()->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\Entity\Field\FieldInterface::getPropertyDefinition(). */ public function getPropertyDefinition($name) { @@ -258,34 +117,6 @@ public function __unset($property_name) { } /** - * Implements \Drupal\Core\TypedData\ListInterface::isEmpty(). - */ - public function isEmpty() { - if (isset($this->list)) { - foreach ($this->list as $item) { - if (!$item->isEmpty()) { - return FALSE; - } - } - } - return TRUE; - } - - /** - * Magic method: Implements a deep clone. - */ - public function __clone() { - if (isset($this->list)) { - foreach ($this->list as $delta => $item) { - $this->list[$delta] = clone $item; - if ($item instanceof ContextAwareInterface) { - $this->list[$delta]->setContext($delta, $this); - } - } - } - } - - /** * Implements \Drupal\Core\TypedData\AccessibleInterface::access(). */ public function access($operation = 'view', User $account = NULL) { diff --git a/core/lib/Drupal/Core/TypedData/ItemList.php b/core/lib/Drupal/Core/TypedData/ItemList.php new file mode 100644 index 0000000..040579c --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ItemList.php @@ -0,0 +1,204 @@ +list; + } + + /** + * 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) { + if (!isset($values) || $values === array()) { + $this->list = $values; + } + else { + // Support passing in only the value of the first item. + if (!is_array($values) || !is_numeric(current(array_keys($values)))) { + $values = array(0 => $values); + } + + // 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); + } + } + } + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::getString(). + */ + public function getString() { + $strings = array(); + if (isset($this->list)) { + foreach ($this->list as $item) { + $strings[] = $item->getString(); + } + return implode(', ', array_filter($strings)); + } + } + + /** + * 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 typed_data()->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->isEmpty()) { + return FALSE; + } + } + } + return TRUE; + } + + /** + * Magic method: Implements a deep clone. + */ + public function __clone() { + if (isset($this->list)) { + foreach ($this->list as $delta => $item) { + $this->list[$delta] = clone $item; + if ($item instanceof ContextAwareInterface) { + $this->list[$delta]->setContext($delta, $this); + } + } + } + } +} diff --git a/core/lib/Drupal/Core/TypedData/Type/Any.php b/core/lib/Drupal/Core/TypedData/Type/Any.php new file mode 100644 index 0000000..21afa99 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Type/Any.php @@ -0,0 +1,27 @@ +values as $name => $value) { + $definitions[$name] = array( + 'type' => 'any', + ); + } + return $definitions; + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::getValue(). + */ + public function getValue() { + return $this->values; + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::setValue(). + * + * @param array|null $values + * An array of property values. + */ + public function setValue($values) { + if (isset($values) && !is_array($values)) { + throw new \InvalidArgumentException("Invalid values given. Values must be represented as an associative array."); + } + $this->values = $values; + unset($this->properties); + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::getString(). + */ + public function getString() { + $strings = array(); + foreach ($this->getProperties() as $property) { + $strings[] = $property->getString(); + } + return implode(', ', array_filter($strings)); + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::get(). + */ + public function get($property_name) { + if (!$this->getPropertyDefinition($property_name)) { + throw new InvalidArgumentException('Property ' . check_plain($property_name) . ' is unknown.'); + } + if (!isset($this->properties[$property_name])) { + $this->properties[$property_name] = typed_data()->getPropertyInstance($this, $property_name, isset($this->values[$property_name]) ? $this->values[$property_name] : NULL); + } + return $this->properties[$property_name]; + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::set(). + */ + public function set($property_name, $value) { + $this->get($property_name)->setValue($value); + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties(). + */ + public function getProperties($include_computed = FALSE) { + $properties = array(); + foreach ($this->getPropertyDefinitions() as $name => $definition) { + if ($include_computed || empty($definition['computed'])) { + $properties[$name] = $this->get($name); + } + } + return $properties; + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues(). + */ + public function getPropertyValues() { + $values = array(); + foreach ($this->getProperties() as $name => $property) { + $values[$name] = $property->getValue(); + } + return $values; + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues(). + */ + public function setPropertyValues($values) { + foreach ($values as $name => $value) { + $this->get($name)->setValue($value); + } + } + + /** + * Implements \IteratorAggregate::getIterator(). + */ + public function getIterator() { + return new ArrayIterator($this->getProperties()); + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition(). + */ + public function getPropertyDefinition($name) { + $definitions = $this->getPropertyDefinitions(); + if (isset($definitions[$name])) { + return $definitions[$name]; + } + else { + return FALSE; + } + } + + /** + * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty(). + */ + public function isEmpty() { + foreach ($this->getProperties() as $property) { + if ($property->getValue() !== NULL) { + return FALSE; + } + } + return TRUE; + } + + /** + * Magic method: Implements a deep clone. + */ + public function __clone() { + foreach ($this->getProperties() as $name => $property) { + $this->properties[$name] = clone $property; + if ($property instanceof ContextAwareInterface) { + $this->properties[$name]->setContext($name, $this); + } + } + } +} diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index 8b5306d..d81dc30 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -8,6 +8,7 @@ namespace Drupal\Core\TypedData; use InvalidArgumentException; +use Drupal\Component\Plugin\Discovery\ProcessDecorator; use Drupal\Component\Plugin\PluginManagerBase; use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\HookDiscovery; @@ -44,11 +45,24 @@ class TypedDataManager extends PluginManagerBase { protected $prototypes = array(); public function __construct() { - $this->discovery = new CacheDecorator(new HookDiscovery('data_type_info'), 'typed_data:types'); + $this->discovery = new HookDiscovery('data_type_info'); + $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition')); + $this->discovery = new CacheDecorator($this->discovery, 'typed_data:types'); + $this->factory = new TypedDataFactory($this->discovery); } /** + * Process definition callback for the ProcessDecorator. + */ + public function processDefinition(&$definition, $plugin_id) { + // Provide a general list class by default. + if (!isset($definition['list class'])) { + $definition['list class'] = '\Drupal\Core\TypedData\ItemList'; + } + } + + /** * Implements \Drupal\Component\Plugin\PluginManagerInterface::createInstance(). * * @param string $plugin_id @@ -191,14 +205,20 @@ public function getInstance(array $options) { * @see \Drupal\Core\TypedData\TypedDataManager::create() */ public function getPropertyInstance(ContextAwareInterface $object, $property_name, $value = NULL) { - $key = $object->getRoot()->getType() . ':' . $object->getPropertyPath() . '.'; - // If we are creating list items, we always use 0 in the key as all list - // items look the same. - $key .= is_numeric($property_name) ? 0 : $property_name; + if ($root = $object->getRoot()) { + $key = $root->getType() . ':' . $object->getPropertyPath() . '.'; + // If we are creating list items, we always use 0 in the key as all list + // items look the same. + $key .= is_numeric($property_name) ? 0 : $property_name; } + else { + // Missing context, thus we cannot determine a unique key for prototyping. + // Fall back to create a new prototype on each call. + $key = FALSE; + } // Make sure we have a prototype. Then, clone the prototype and set object // specific values, i.e. the value and the context. - if (!isset($this->prototypes[$key])) { + if (!isset($this->prototypes[$key]) || !$key) { // Create the initial prototype. For that we need to fetch the definition // of the to be created property instance from the parent. if ($object instanceof ComplexDataInterface) { diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 484ee93..3dfee74 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -158,7 +158,8 @@ function hook_cron() { * \Drupal\Core\TypedData\TypedDataInterface. * - list class: (optional) A typed data class used for wrapping multiple * data items of the type. Must implement the - * \Drupal\Core\TypedData\ListInterface. + * \Drupal\Core\TypedData\ListInterface. Defaults to + * \Drupal\Core\TypedData\ItemList; * - primitive type: (optional) Maps the data type to one of the pre-defined * primitive types in \Drupal\Core\TypedData\Primitive. If set, it must be * a constant defined by \Drupal\Core\TypedData\Primitive such as diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 1d7671b..a7b5e88 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2229,6 +2229,14 @@ function system_data_type_info() { 'class' => '\Drupal\Core\TypedData\Type\Binary', 'primitive type' => Primitive::BINARY, ), + 'map' => array( + 'label' => t('Map'), + 'class' => '\Drupal\Core\TypedData\Type\Map', + ), + 'any' => array( + 'label' => t('Any data'), + 'class' => '\Drupal\Core\TypedData\Type\Any', + ), 'language' => array( 'label' => t('Language'), 'description' => t('A language object.'),