Issue #1913328 by fago: Provide general list and map classes.

8.0.x
webchick 2013-03-08 21:35:04 -08:00
parent b62c536c54
commit dad3ef5592
8 changed files with 665 additions and 152 deletions

View File

@ -10,11 +10,7 @@ namespace Drupal\Core\Entity\Field\Type;
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\TypedDataInterface;
use ArrayIterator;
use IteratorAggregate;
use InvalidArgumentException;
use Drupal\Core\TypedData\ItemList;
/**
* Represents an entity field; that is, a list of field item objects.
@ -27,7 +23,7 @@ use InvalidArgumentException;
*
* @see \Drupal\Core\Entity\Field\FieldInterface
*/
class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInterface {
class Field extends ItemList implements FieldInterface {
/**
* Numerically indexed array of field items, implementing the
@ -50,7 +46,7 @@ class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInt
}
/**
* Overrides \Drupal\Core\TypedData\TypedData::getValue().
* Overrides \Drupal\Core\TypedData\ItemList::getValue().
*/
public function getValue() {
if (isset($this->list)) {
@ -68,10 +64,7 @@ class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInt
}
/**
* 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()) {
@ -91,7 +84,7 @@ class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInt
// 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.');
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);
@ -103,111 +96,6 @@ class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInt
}
}
/**
* 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().
*/
@ -257,34 +145,6 @@ class Field extends ContextAwareTypedData implements IteratorAggregate, FieldInt
return $this->offsetGet(0)->__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().
*/

View File

@ -0,0 +1,212 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\List.
*/
namespace Drupal\Core\TypedData;
/**
* A generic list class.
*
* This class can serve as list for any type of items.
* Note: The class cannot be called "List" as list is a reserved PHP keyword.
*/
class ItemList extends ContextAwareTypedData implements \IteratorAggregate, ListInterface {
/**
* Numerically indexed array items.
*
* @var array
*/
protected $list = array();
/**
* Overrides \Drupal\Core\TypedData\TypedData::getValue().
*/
public function getValue() {
if (isset($this->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();
}
// 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 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);
}
}
}
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Type\Any.
*/
namespace Drupal\Core\TypedData\Type;
use Drupal\Core\TypedData\TypedData;
/**
* The "any" data type.
*
* The "any" data type does not implement a list or complex data interface, nor
* is it mappable to any primitive type. Thus, it may contain any PHP data for
* which no further metadata is available.
*/
class Any extends TypedData {
/**
* The data value.
*
* @var mixed
*/
protected $value;
}

View File

@ -0,0 +1,181 @@
<?php
/**
* @file
* Contains \Drupal\Core\TypedData\Type\Map.
*/
namespace Drupal\Core\TypedData\Type;
use Drupal\Core\TypedData\ContextAwareTypedData;
use Drupal\Core\TypedData\ComplexDataInterface;
/**
* The "map" data type.
*
* The "map" data type represent a simple complex data type, e.g. for
* representing associative arrays. It can also serve as base class for any
* complex data type.
*
* By default there is no metadata for contained properties. Extending classes
* may want to override Map::getPropertyDefinitions() to define it.
*/
class Map extends ContextAwareTypedData implements \IteratorAggregate, ComplexDataInterface {
/**
* An array of values for the contained properties.
*
* @var array
*/
protected $values = array();
/**
* The array of properties, each implementing the TypedDataInterface.
*
* @var array
*/
protected $properties;
/**
* Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
*/
public function getPropertyDefinitions() {
$definitions = array();
foreach ($this->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();
}
// Remove any empty strings resulting from empty items.
return implode(', ', array_filter($strings, 'drupal_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);
}
}
}
}

View File

@ -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;
@ -36,6 +37,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.
*
@ -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 @@ class TypedDataManager extends PluginManagerBase {
* @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) {

View File

@ -197,6 +197,208 @@ class TypedDataTest extends WebTestBase {
$this->assertEqual($typed_data->validate()->count(), 0);
$typed_data->setValue('invalid');
$this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.');
// Any type.
$value = array('foo');
$typed_data = $this->createTypedData(array('type' => 'any'), $value);
$this->assertIdentical($typed_data->getValue(), $value, 'Any value was fetched.');
$new_value = 'test@example.com';
$typed_data->setValue($new_value);
$this->assertIdentical($typed_data->getValue(), $new_value, 'Any value was changed.');
$this->assertTrue(is_string($typed_data->getString()), 'Any value was converted to string');
$this->assertEqual($typed_data->validate()->count(), 0);
$typed_data->setValue(NULL);
$this->assertNull($typed_data->getValue(), 'Any wrapper is null-able.');
$this->assertEqual($typed_data->validate()->count(), 0);
// We cannot test invalid values as everything is valid for the any type,
// but make sure an array or object value passes validation also.
$typed_data->setValue(array('entry'));
$this->assertEqual($typed_data->validate()->count(), 0);
$typed_data->setValue((object) array('entry'));
$this->assertEqual($typed_data->validate()->count(), 0);
}
/**
* 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());
}
}
/**

View File

@ -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

View File

@ -2228,6 +2228,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.'),