diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php index 4a8a289..38d9bde 100644 --- a/core/lib/Drupal/Core/Config/TypedConfigManager.php +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php @@ -12,6 +12,9 @@ /** * Manages config type plugins. + * + * Extends the typed data manager to process config schema definitions to + * regular typed data definitions before instances are created. */ class TypedConfigManager extends TypedDataManager { @@ -54,16 +57,18 @@ public function get($name) { /** * Overrides \Drupal\Core\TypedData\TypedDataManager::create() * - * Fills in default type and does variable replacement. + * Takes a config schema definition and processes it to a regular typed data + * definition before creation, i.e. it fills in default type and does variable + * replacement. */ public function create(array $definition, $value = NULL, $name = NULL, $parent = NULL) { if (!isset($definition['type'])) { - // Set default type 'string' if possible. If not it will be 'undefined'. + // Set default type 'string' if possible. If not it will be 'any'. if (is_string($value)) { $definition['type'] = 'string'; } else { - $definition['type'] = 'undefined'; + $definition['type'] = 'any'; } } elseif (strpos($definition['type'], ']')) { diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index 49418a0..730be99 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,10 +68,7 @@ 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. + * Overrides \Drupal\Core\TypedData\ItemList::setValue(). */ public function setValue($values) { if (!isset($values) || $values === array()) { @@ -104,111 +101,6 @@ public function setValue($values) { } /** - * 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 +150,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..997a4de --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ItemList.php @@ -0,0 +1,215 @@ +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) { + 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); + } + } + } + } + + /** + * 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, '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 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 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; + } + + /** + * 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, 'strlen')); + } + + /** + * 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..0f8dc73 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; @@ -37,6 +38,17 @@ class TypedDataManager extends PluginManagerBase { protected $constraintManager; /** + * Type definition defaults which are merged in by the ProcessDecorator. + * + * @see \Drupal\Component\Plugin\PluginManagerBase::processDefinition() + * + * @var array + */ + protected $defaults = array( + 'list class' => '\Drupal\Core\TypedData\ItemList', + ); + + /** * An array of typed data property prototypes. * * @var array @@ -44,7 +56,10 @@ 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); } @@ -191,14 +206,21 @@ 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/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php index e7a94f7..583015b 100644 --- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php @@ -200,6 +200,189 @@ public function testGetAndSet() { } /** + * Tests using typed data lists. + */ + 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); + $this->assertEqual($typed_data->getValue(), $value, 'List value has been set.'); + // Test iterating. + $count = 0; + foreach ($typed_data as $item) { + $this->assertTrue($item instanceof \Drupal\Core\TypedData\TypedDataInterface); + $count++; + } + $this->assertEqual($count, 3); + + // Test getting the string representation. + $this->assertEqual($typed_data->getString(), 'one, two, three'); + $typed_data[1] = ''; + $this->assertEqual($typed_data->getString(), 'one, three'); + + // Test using array access. + $this->assertEqual($typed_data[0]->getValue(), 'one'); + $typed_data[4] = 'four'; + $this->assertEqual($typed_data[4]->getValue(), 'four'); + $typed_data[] = 'five'; + $this->assertEqual($typed_data[5]->getValue(), 'five'); + $this->assertEqual($typed_data->count(), 5); + $this->assertTrue(isset($typed_data[0])); + $this->assertTrue(!isset($typed_data[6])); + + // Test isEmpty and cloning. + $this->assertFalse($typed_data->isEmpty()); + $clone = clone $typed_data; + $this->assertTrue($typed_data->getValue() === $clone->getValue()); + $this->assertTrue($typed_data[0] !== $clone[0]); + $clone->setValue(array()); + $this->assertTrue($clone->isEmpty()); + + // Make sure the difference between NULL (not set) and an empty array is + // kept. + $clone->setValue(array()); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getValue()); + $this->assertIdentical($clone->getValue(), array()); + + // Test dealing with NULL items. + $typed_data[] = NULL; + $this->assertTrue($typed_data->isEmpty()); + $this->assertEqual(count($typed_data), 1); + $typed_data[] = ''; + $this->assertFalse($typed_data->isEmpty()); + $this->assertEqual(count($typed_data), 2); + $typed_data[] = 'three'; + $this->assertFalse($typed_data->isEmpty()); + $this->assertEqual(count($typed_data), 3); + + $this->assertEqual($typed_data->getValue(), array(NULL, '', 'three')); + // Test unsetting. + unset($typed_data[2]); + $this->assertEqual(count($typed_data), 2); + $this->assertNull($typed_data[3]->getValue()); + + // Getting a not set list item sets it. + $this->assertNull($typed_data[4]->getValue()); + $this->assertEqual(count($typed_data), 4); + + // Test setting the list with less values. + $typed_data->setValue(array('one')); + $this->assertEqual($typed_data->count(), 1); + + // Test setting invalid values. + try { + $typed_data->setValue(array('not a list' => 'one')); + $this->fail('No exception has been thrown when setting an invalid value.'); + } + catch (\Exception $e) { + $this->pass('Exception thrown:' . $e->getMessage()); + } + try { + $typed_data->setValue('string'); + $this->fail('No exception has been thrown when setting an invalid value.'); + } + catch (\Exception $e) { + $this->pass('Exception thrown:' . $e->getMessage()); + } + } + + /** + * Tests using a typed data map. + */ + public function testTypedDataMaps() { + // Test working with a simple map. + $value = array( + 'one' => 'eins', + 'two' => 'zwei', + 'three' => 'drei', + ); + $typed_data = $this->createTypedData(array( + 'type' => 'map', + ), $value); + + // Test iterating. + $count = 0; + foreach ($typed_data as $item) { + $this->assertTrue($item instanceof \Drupal\Core\TypedData\TypedDataInterface); + $count++; + } + $this->assertEqual($count, 3); + + // Test retrieving metadata. + $this->assertEqual(array_keys($typed_data->getPropertyDefinitions()), array_keys($value)); + $definition = $typed_data->getPropertyDefinition('one'); + $this->assertEqual($definition['type'], 'any'); + $this->assertFalse($typed_data->getPropertyDefinition('invalid')); + + // Test getting and setting properties. + $this->assertEqual($typed_data->get('one')->getValue(), 'eins'); + $this->assertEqual($typed_data->getPropertyValues(), $value); + $typed_data->set('one', 'uno'); + $this->assertEqual($typed_data->get('one')->getValue(), 'uno'); + + $properties = $typed_data->getProperties(); + $this->assertEqual(array_keys($properties), array_keys($value)); + $this->assertIdentical($properties['one'], $typed_data->get('one')); + + $typed_data->setPropertyValues(array('one' => 'eins')); + $this->assertEqual($typed_data->get('one')->getValue(), 'eins'); + $this->assertEqual($typed_data->get('two')->getValue(), 'zwei'); + $this->assertEqual($typed_data->get('three')->getValue(), 'drei'); + + $typed_data->setValue(array('foo' => 'bar')); + $this->assertEqual(array_keys($typed_data->getProperties()), array('foo')); + + // Test getting the string representation. + $typed_data->setValue(array('one' => 'eins', 'two' => '', 'three' => 'drei')); + $this->assertEqual($typed_data->getString(), 'eins, drei'); + + // Test isEmpty and cloning. + $this->assertFalse($typed_data->isEmpty()); + $clone = clone $typed_data; + $this->assertTrue($typed_data->getValue() === $clone->getValue()); + $this->assertTrue($typed_data->get('one') !== $clone->get('one')); + $clone->setValue(array()); + $this->assertTrue($clone->isEmpty()); + + // Make sure the difference between NULL (not set) and an empty array is + // kept. + $clone->setValue(array()); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getValue()); + $this->assertIdentical($clone->getValue(), array()); + + // Test accessing invalid properties. + $typed_data->setValue($value); + try { + $typed_data->get('invalid'); + $this->fail('No exception has been thrown when getting an invalid value.'); + } + catch (\Exception $e) { + $this->pass('Exception thrown:' . $e->getMessage()); + } + try { + $typed_data->set('invalid', 'foo'); + $this->fail('No exception has been thrown when setting an invalid value.'); + } + catch (\Exception $e) { + $this->pass('Exception thrown:' . $e->getMessage()); + } + + // Test setting invalid values. + try { + $typed_data->setValue('invalid'); + $this->fail('No exception has been thrown when setting an invalid value.'); + } + catch (\Exception $e) { + $this->pass('Exception thrown:' . $e->getMessage()); + } + } + + /** * Tests typed data validation. */ public function testTypedDataValidation() { diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 2f98dad..963d454 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 a21caa6..5016b99 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2227,6 +2227,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.'),