Skip to content

Commit eb6bcbd

Browse files
committed
Imported code from sandbox branch.
1 parent 255fd74 commit eb6bcbd

33 files changed

+8781
-0
lines changed

Data/ArrayData.php

Lines changed: 407 additions & 0 deletions
Large diffs are not rendered by default.

Data/BooleanData.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace MutableTypedData\Data;
4+
5+
use MutableTypedData\Exception\InvalidInputException;
6+
7+
/**
8+
* Represents boolean data.
9+
*
10+
* Data values are accessed with the 'value' property:
11+
* $data->value = TRUE;
12+
* $value = $data->value;
13+
*
14+
* As well as boolean TRUE or FALSE, this may be set with a 0 or 1 as either
15+
* integers or strings.
16+
*/
17+
class BooleanData extends SimpleData {
18+
19+
/**
20+
* {@inheritdoc}
21+
*/
22+
public function set($value) {
23+
// Allow the value to be set as 0 or 1 as a string or an integer.
24+
if ($value === 0 || $value === '0') {
25+
$value = FALSE;
26+
}
27+
if ($value === 1 || $value === '1') {
28+
$value = TRUE;
29+
}
30+
31+
if (!is_bool($value) && !is_null($value)) {
32+
throw new InvalidInputException(sprintf("Boolean data may only have boolean or NULL values at address %s.",
33+
$this->getAddress()
34+
));
35+
}
36+
37+
parent::set($value);
38+
}
39+
40+
}

Data/ComplexData.php

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
<?php
2+
3+
namespace MutableTypedData\Data;
4+
5+
use MutableTypedData\Definition\DataDefinition;
6+
use MutableTypedData\Exception\InvalidAccessException;
7+
use MutableTypedData\Exception\InvalidDefinitionException;
8+
use MutableTypedData\Exception\InvalidInputException;
9+
10+
/**
11+
* Represents a data item that is composed of multiple properties.
12+
*/
13+
class ComplexData extends DataItem implements \IteratorAggregate {
14+
15+
/**
16+
* The properties of this data.
17+
*
18+
* For this class, these are the same as the properties from the definition,
19+
* but subclasses may change this behaviour, hence the need to copy them to
20+
* this object.
21+
*
22+
* @var array
23+
*/
24+
protected $properties;
25+
26+
protected $value = [];
27+
28+
public static function isSimple(): bool {
29+
return FALSE;
30+
}
31+
32+
public function __construct(DataDefinition $definition) {
33+
parent::__construct($definition);
34+
35+
$this->properties = $this->definition->getProperties();
36+
}
37+
38+
/**
39+
* Returns the iterator for this object.
40+
*
41+
* iterates over data!!! auto-creates!
42+
*/
43+
public function getIterator() {
44+
// Iterate in the order the properties are defined in, rather than the
45+
// keys in the $this->value array, which are in whichever order a value
46+
// was set.
47+
foreach ($this->properties as $name => $property) {
48+
if (!$this->revealInternal) {
49+
if ($property->isInternal()) {
50+
continue;
51+
}
52+
}
53+
54+
if (!isset($this->value[$name])) {
55+
$this->value[$name] = $this->factoryClass::createFromDefinition($this->properties[$name], $this);
56+
}
57+
58+
yield $name => $this->value[$name];
59+
}
60+
}
61+
62+
public function items(): array {
63+
return $this->value;
64+
}
65+
66+
// Hides internals unless requested.
67+
public function getProperties() {
68+
$return = [];
69+
70+
// Use the local properties, as subclasses may change them.
71+
foreach ($this->properties as $name => $property) {
72+
if (!$this->revealInternal) {
73+
if ($property->isInternal()) {
74+
continue;
75+
}
76+
}
77+
78+
$return[$name] = $property;
79+
}
80+
81+
return $return;
82+
}
83+
84+
// Hides internals unless requested.
85+
public function getPropertyNames() {
86+
$property_names = [];
87+
88+
// Use the local properties, as subclasses may change them.
89+
foreach ($this->properties as $name => $property) {
90+
if (!$this->revealInternal) {
91+
if ($property->isInternal()) {
92+
continue;
93+
}
94+
}
95+
96+
$property_names[] = $name;
97+
}
98+
99+
return $property_names;
100+
}
101+
102+
public function hasProperty($name) {
103+
return isset($this->properties[$name]);
104+
}
105+
106+
public function __set($name, $value) {
107+
if (!array_key_exists($name, $this->properties)) {
108+
throw new InvalidAccessException(sprintf("Attempt to set nonexistent property '%s' at %s.",
109+
$name,
110+
$this->getAddress()
111+
));
112+
}
113+
114+
// TODO: hand over to set()!
115+
116+
if (!isset($this->value[$name])) {
117+
// yeah this works for a string, but what if it's a component????
118+
$item_data = $this->factoryClass::createFromDefinition($this->properties[$name], $this);
119+
$this->value[$name] = $item_data;
120+
}
121+
122+
$this->value[$name]->set($value);
123+
$this->set = TRUE;
124+
125+
// Don't bubble up, as the child data getting set will do that.
126+
}
127+
128+
/**
129+
* Sets the whole or partial value of the data.
130+
*
131+
* Note that this doesn't clobber existing values not contained in the given
132+
* array.
133+
*
134+
* @param array $value
135+
* The values to set. Keys should be property names.
136+
*/
137+
public function set($value) {
138+
if (!is_array($value)) {
139+
throw new InvalidInputException(sprintf(
140+
"Attempt to set a non-array value on complex data at address %s. Set individual values with properties.",
141+
$this->getAddress()
142+
));
143+
}
144+
145+
foreach ($value as $name => $item_value) {
146+
if (!array_key_exists($name, $this->properties)) {
147+
throw new InvalidAccessException(sprintf(
148+
"Attempt to set nonexistent property '%s' at %s, available properties are: %s.",
149+
$name,
150+
$this->getAddress(),
151+
implode(', ', $this->definition->getPropertyNames())
152+
));
153+
}
154+
155+
if (!isset($this->value[$name])) {
156+
$item_data = $this->factoryClass::createFromDefinition($this->properties[$name], $this);
157+
$this->value[$name] = $item_data;
158+
}
159+
160+
$this->value[$name]->set($item_value);
161+
}
162+
$this->set = TRUE;
163+
164+
// Don't bubble up, as the child data getting set will do that.
165+
}
166+
167+
public function removeItem(string $property_name) {
168+
unset($this->value[$property_name]);
169+
}
170+
171+
public function validate(): array {
172+
$violations = parent::validate();
173+
174+
// Check properties, and ensure that those which are required are
175+
// instantiated so they can be validated.
176+
foreach ($this->properties as $name => $definition) {
177+
// Don't validate internals unless they are revealed.
178+
if (!$this->revealInternal) {
179+
if ($definition->isInternal()) {
180+
continue;
181+
}
182+
}
183+
184+
if ($definition->isRequired()) {
185+
if ($definition->isMultiple()) {
186+
throw new InvalidDefinitionException(sprintf("Required multiple-valued data is not supported, address was %s.",
187+
$this->{$name}->getAddress()
188+
));
189+
}
190+
191+
// Need to explicitly get the property in case $name happens to be also
192+
// the name of one of our internal properties, which then means the
193+
// magic __get() wouldn't be called because here the property is
194+
// accessible.
195+
$this->get($name)->value;
196+
}
197+
}
198+
// dsm($this->value);
199+
200+
foreach ($this->value as $item) {
201+
$violations = array_merge($violations, $item->validate());
202+
}
203+
204+
return $violations;
205+
}
206+
207+
/**
208+
* {@inheritdoc}
209+
*/
210+
public function isEmpty(): bool {
211+
$values_are_empty = [];
212+
213+
// Only consider visible properties.
214+
foreach ($this->getProperties() as $name => $property_definition) {
215+
// Avoid instantiating child data.
216+
$values_are_empty[] = !isset($this->value[$name]) || $this->value[$name]->isEmpty();
217+
}
218+
219+
return (bool) array_product($values_are_empty);
220+
}
221+
222+
public function import($value) {
223+
foreach ($value as $name => $item_value) {
224+
if (array_key_exists($name, $this->properties)) {
225+
if (!isset($this->value[$name])) {
226+
$item_data = $this->factoryClass::createFromDefinition($this->properties[$name], $this);
227+
$this->value[$name] = $item_data;
228+
}
229+
230+
$this->value[$name]->import($item_value);
231+
$this->set = TRUE;
232+
}
233+
}
234+
}
235+
236+
237+
public function __isset($name) {
238+
// Need to implement this so empty() works on $data_item->value.
239+
$return = isset($this->value[$name]);
240+
return $return;
241+
}
242+
243+
public function __get($name) {
244+
return $this->get($name);
245+
}
246+
247+
public function get(string $name = '') {
248+
if (empty($name)) {
249+
throw new InvalidAccessException(sprintf(
250+
"Accessing complex data at %s without a property name.",
251+
$name,
252+
$this->getAddress()
253+
));
254+
}
255+
256+
if (!array_key_exists($name, $this->properties)) {
257+
throw new InvalidAccessException(sprintf(
258+
"Unknown property %s on data at %s.",
259+
$name,
260+
$this->getAddress()
261+
));
262+
}
263+
264+
// dump($this->value);
265+
// For complex data, accessing the property should auto-create it??
266+
// this is to allow chaining like $data->complex->subProperty = 'value'
267+
if (!isset($this->value[$name])) {
268+
$item_data = $this->factoryClass::createFromDefinition($this->properties[$name], $this);
269+
$this->value[$name] = $item_data;
270+
}
271+
272+
273+
// if (empty($this->generatorClass)) {
274+
// throw new \Exception("type must be set first");
275+
// }
276+
277+
// assert(isset($this->value[$name]));
278+
// dump($this->value[$name]);
279+
// dump($this->value[$name]->value);
280+
// ????
281+
// dump($this->value);
282+
283+
return $this->value[$name];
284+
}
285+
286+
public function access(): void {
287+
// Access each child property.
288+
// Iterating over ourselves takes care of whether we're set to reveal
289+
// internal or not.
290+
foreach ($this as $item) {
291+
$item->access();
292+
}
293+
}
294+
295+
296+
public function walk(callable $callback) {
297+
$callback($this);
298+
299+
foreach ($this->value as $item) {
300+
$item->walk($callback);
301+
}
302+
}
303+
304+
protected function restoreOnWake() {
305+
$this->properties = $this->definition->getProperties();
306+
307+
foreach ($this->value as $name => $value) {
308+
$value->parent = $this;
309+
$value->definition = $this->definition->getProperty($name);
310+
311+
$value->restoreOnWake();
312+
}
313+
}
314+
315+
316+
protected function getRaw() {
317+
$export = [];
318+
foreach ($this->value as $name => $value) {
319+
$export[$name] = $value->getRaw();
320+
}
321+
322+
return $export;
323+
}
324+
325+
public function export() {
326+
$export = [];
327+
foreach ($this->value as $name => $value) {
328+
// Don't export empty values.
329+
if ($value->isEmpty()) {
330+
continue;
331+
}
332+
333+
$export[$name] = $value->export();
334+
}
335+
336+
return $export;
337+
}
338+
339+
}

0 commit comments

Comments
 (0)