3 namespace Drupal\Core\TypedData\Plugin\DataType;
5 use Drupal\Core\TypedData\TypedData;
6 use Drupal\Core\TypedData\ComplexDataInterface;
11 * The "map" data type represent a simple complex data type, e.g. for
12 * representing associative arrays. It can also serve as base class for any
15 * By default there is no metadata for contained properties. Extending classes
16 * may want to override MapDataDefinition::getPropertyDefinitions() to define
23 * label = @Translation("Map"),
24 * definition_class = "\Drupal\Core\TypedData\MapDataDefinition"
27 class Map extends TypedData implements \IteratorAggregate, ComplexDataInterface {
30 * The data definition.
32 * @var \Drupal\Core\TypedData\ComplexDataDefinitionInterface
34 protected $definition;
37 * An array of values for the contained properties.
41 protected $values = [];
44 * The array of properties.
46 * @var \Drupal\Core\TypedData\TypedDataInterface[]
48 protected $properties = [];
53 public function getValue() {
54 // Update the values and return them.
55 foreach ($this->properties as $name => $property) {
56 $definition = $property->getDataDefinition();
57 if (!$definition->isComputed()) {
58 $value = $property->getValue();
59 // Only write NULL values if the whole map is not NULL.
60 if (isset($this->values) || isset($value)) {
61 $this->values[$name] = $value;
69 * Overrides \Drupal\Core\TypedData\TypedData::setValue().
71 * @param array|null $values
72 * An array of property values.
74 public function setValue($values, $notify = TRUE) {
75 if (isset($values) && !is_array($values)) {
76 throw new \InvalidArgumentException("Invalid values given. Values must be represented as an associative array.");
78 $this->values = $values;
80 // Update any existing property objects.
81 foreach ($this->properties as $name => $property) {
82 $value = isset($values[$name]) ? $values[$name] : NULL;
83 $property->setValue($value, FALSE);
84 // Remove the value from $this->values to ensure it does not contain any
85 // value for computed properties.
86 unset($this->values[$name]);
88 // Notify the parent of any changes.
89 if ($notify && isset($this->parent)) {
90 $this->parent->onChange($this->name);
97 public function getString() {
99 foreach ($this->getProperties() as $property) {
100 $strings[] = $property->getString();
102 // Remove any empty strings resulting from empty items.
103 return implode(', ', array_filter($strings, 'mb_strlen'));
109 public function get($property_name) {
110 if (!isset($this->properties[$property_name])) {
112 if (isset($this->values[$property_name])) {
113 $value = $this->values[$property_name];
115 // If the property is unknown, this will throw an exception.
116 $this->properties[$property_name] = $this->getTypedDataManager()->getPropertyInstance($this, $property_name, $value);
118 return $this->properties[$property_name];
124 public function set($property_name, $value, $notify = TRUE) {
125 // Separate the writing in a protected method, such that onChange
126 // implementations can make use of it.
127 $this->writePropertyValue($property_name, $value);
128 $this->onChange($property_name, $notify);
133 * Writes the value of a property without handling changes.
135 * Implementations of onChange() should use this method instead of set() in
136 * order to avoid onChange() being triggered again.
138 * @param string $property_name
139 * The name of the property to be written.
143 protected function writePropertyValue($property_name, $value) {
144 if ($this->definition->getPropertyDefinition($property_name)) {
145 $this->get($property_name)->setValue($value, FALSE);
148 // Just set the plain value, which allows adding a new entry to the map.
149 $this->values[$property_name] = $value;
156 public function getProperties($include_computed = FALSE) {
158 foreach ($this->definition->getPropertyDefinitions() as $name => $definition) {
159 if ($include_computed || !$definition->isComputed()) {
160 $properties[$name] = $this->get($name);
169 public function toArray() {
171 foreach ($this->getProperties() as $name => $property) {
172 $values[$name] = $property->getValue();
180 public function getIterator() {
181 return new \ArrayIterator($this->getProperties());
187 public function isEmpty() {
188 foreach ($this->properties as $property) {
189 $definition = $property->getDataDefinition();
190 if (!$definition->isComputed() && $property->getValue() !== NULL) {
194 if (isset($this->values)) {
195 foreach ($this->values as $name => $value) {
196 if (isset($value) && !isset($this->properties[$name])) {
205 * Magic method: Implements a deep clone.
207 public function __clone() {
208 foreach ($this->properties as $name => $property) {
209 $this->properties[$name] = clone $property;
210 $this->properties[$name]->setContext($name, $this);
217 * @param bool $notify
218 * (optional) Whether to forward the notification to the parent. Defaults to
219 * TRUE. By passing FALSE, overrides of this method can re-use the logic
220 * of parent classes without triggering notification.
222 public function onChange($property_name, $notify = TRUE) {
223 // Notify the parent of changes.
224 if ($notify && isset($this->parent)) {
225 $this->parent->onChange($this->name);
232 public function applyDefaultValue($notify = TRUE) {
233 // Apply the default value of all properties.
234 foreach ($this->getProperties() as $property) {
235 $property->applyDefaultValue(FALSE);