Issue #1913328 by fago: Provide general list and map classes.
parent
b62c536c54
commit
dad3ef5592
|
@ -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().
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.'),
|
||||
|
|
Loading…
Reference in New Issue