-
Notifications
You must be signed in to change notification settings - Fork 191
/
WireData.php
558 lines (527 loc) · 15 KB
/
WireData.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
<?php namespace ProcessWire;
/**
* ProcessWire WireData
*
* This is the base data container class used throughout ProcessWire.
* It provides get and set access to properties internally stored in a $data array.
* Otherwise it is identical to the Wire class.
*
* #pw-summary WireData is the base data-storage class used by many ProcessWire object types and most modules.
* #pw-body =
* WireData is very much like its parent `Wire` class with the fundamental difference being that it is designed
* for runtime data storage. It provides this primarily through the built-in `get()` and `set()` methods for
* getting and setting named properties to WireData objects. The most common example of a WireData object is
* `Page`, the type used for all pages in ProcessWire.
*
* Properties set to a WireData object can also be set or accessed directly, like `$item->property` or using
* array access like `$item[$property]`. If you `foreach()` a WireData object, the default behavior is to
* iterate all of the properties/values present within it.
* #pw-body
*
* May also be accessed as array.
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* https://processwire.com
*
* @method WireArray and($items = null)
*
*/
class WireData extends Wire implements \IteratorAggregate, \ArrayAccess {
/**
* Array where get/set properties are stored
*
*/
protected $data = array();
/**
* Set a value to this object’s data
*
* ~~~~~
* // Set a value for a property
* $item->set('foo', 'bar');
*
* // Set a property value directly
* $item->foo = 'bar';
*
* // Set a property using array access
* $item['foo'] = 'bar';
* ~~~~~
*
* #pw-group-manipulation
*
* @param string $key Name of property you want to set
* @param mixed $value Value of property
* @return $this
* @see WireData::setQuietly(), WireData::get()
*
*/
public function set($key, $value) {
if($key === 'data') {
if(!is_array($value)) $value = (array) $value;
return $this->setArray($value);
}
if($this->trackChanges) {
$v = isset($this->data[$key]) ? $this->data[$key] : null;
if(!$this->isEqual($key, $v, $value)) $this->trackChange($key, $v, $value);
}
$this->data[$key] = $value;
return $this;
}
/**
* Same as set() but without change tracking
*
* - If `$this->trackChanges()` is false, then this is no different than set(), since changes aren't being tracked.
* - If `$this->trackChanges()` is true, then the value will be set quietly (i.e. not recorded in the changes list).
*
* #pw-group-manipulation
*
* @param string $key Name of property you want to set
* @param mixed $value Value of property
* @return $this
* @see Wire::trackChanges(), WireData::set()
*
*/
public function setQuietly($key, $value) {
$track = $this->trackChanges();
if($track) $this->setTrackChanges(false);
$this->set($key, $value);
if($track) $this->setTrackChanges(true);
return $this;
}
/**
* Is $value1 equal to $value2?
*
* This template method provided so that descending classes can optionally determine
* whether a change should be tracked.
*
* #pw-internal
*
* @param string $key Name of the property/key that triggered the check (see `WireData::set()`)
* @param mixed $value1 Comparison value
* @param mixed $value2 A second comparison value
* @return bool True if values are equal, false if not
*
*/
protected function isEqual($key, $value1, $value2) {
if($key) {} // intentional to avoid unused argument notice
// $key intentionally not used here, but may be used by descending classes
return $value1 === $value2;
}
/**
* Set an array of key=value pairs
*
* This is the same as the `WireData::set()` method except that it can set an array
* of properties at once.
*
* #pw-group-manipulation
*
* @param array $data Associative array of where the keys are property names, and values are… values.
* @return $this
* @see WireData::set()
*
*/
public function setArray(array $data) {
foreach($data as $key => $value) $this->set($key, $value);
return $this;
}
/**
* Provides direct reference access to set values in the $data array
*
* @param string $key
* @param mixed $value
*
*/
public function __set($key, $value) {
$this->set($key, $value);
}
/**
* Retrieve the value for a previously set property, or retrieve an API variable
*
* - If the given $key is an object, it will cast it to a string.
* - If the given $key is a string with "|" pipe characters in it, it will try all till it finds a non-empty value.
* - If given an API variable name, it will return that API variable unless the class has direct access API variables disabled.
*
* ~~~~~
* // Retrieve the value of a property
* $value = $item->get("some_property");
*
* // Retrieve the value of the first non-empty property:
* $value = $item->get("property1|property2|property2");
*
* // Retrieve a value using array access
* $value = $item["some_property"];
* ~~~~~
*
* #pw-group-retrieval
*
* @param string|object $key Name of property you want to retrieve.
* @return mixed|null Returns value of requested property, or null if the property was not found.
* @see WireData::set()
*
*/
public function get($key) {
if(is_object($key)) $key = "$key";
if(array_key_exists($key, $this->data)) return $this->data[$key];
if(strpos($key, '|')) {
$keys = explode('|', $key);
foreach($keys as $k) {
/** @noinspection PhpAssignmentInConditionInspection */
if($value = $this->get($k)) return $value;
}
}
return parent::__get($key); // back to Wire
}
/**
* Get or set a low-level data value
*
* Like get() or set() but will only get/set from the WireData's protected $data array.
* This is used to bypass any extra logic a class may have added to its get() or set()
* methods. The benefit of this method over get() is that it excludes API vars and potentially
* other things (defined by descending classes) that you may not want.
*
* - To get a value, simply omit the $value argument.
* - To set a value, specify both the $key and $value arguments.
* - If you omit a $key and $value, this method will return the entire data array.
*
* #pw-group-manipulation
* #pw-group-retrieval
*
* ~~~~~
* // Set a property
* $item->data('some_property', 'some value');
*
* // Get the value of a previously set property
* $value = $item->data('some_property');
* ~~~~~
*
* @param string|array $key Property you want to get or set, or associative array of properties you want to set.
* @param mixed $value Optionally specify a value if you want to set rather than get.
* Or Specify boolean TRUE if setting an array via $key and you want to overwrite any existing values (rather than merge).
* @return array|WireData|null Returns one of the following:
* - `mixed` - Actual value if getting a previously set value.
* - `null` - If you are attempting to get a value that has not been set.
* - `$this` - If you are setting a value.
*/
public function data($key = null, $value = null) {
if($key === null) return $this->data;
if(is_array($key)) {
if($value === true) {
$this->data = $key;
} else {
$this->data = array_merge($this->data, $key);
}
return $this;
} else if($value === null) {
return isset($this->data[$key]) ? $this->data[$key] : null;
} else {
$this->data[$key] = $value;
return $this;
}
}
/**
* Returns the full array of properties set to this object
*
* If descending classes also store data in other containers, they may want to
* override this method to include that data as well.
*
* #pw-group-retrieval
*
* @return array Returned array is associative and indexed by property name.
*
*/
public function getArray() {
return $this->data;
}
/**
* Get a property via dot syntax: field.subfield (static)
*
* Static version for internal core use. Use the non-static getDot() instead.
*
* #pw-internal
*
* @param string $key
* @param Wire $from The instance you want to pull the value from
* @return null|mixed Returns value if found or null if not
*
*/
public static function _getDot($key, Wire $from) {
$key = trim($key, '.');
if(strpos($key, '.')) {
// dot present
$keys = explode('.', $key); // convert to array
$key = array_shift($keys); // get first item
} else {
// dot not present
$keys = array();
}
if($from->wire($key) !== null) return null; // don't allow API vars to be retrieved this way
if($from instanceof WireData) {
$value = $from->get($key);
} else if($from instanceof WireArray) {
$value = $from->getProperty($key);
} else {
$value = $from->$key;
}
if(!count($keys)) return $value; // final value
if(is_object($value)) {
if(count($keys) > 1) {
$keys = implode('.', $keys); // convert back to string
if($value instanceof WireData) $value = $value->getDot($keys); // for override potential
else $value = self::_getDot($keys, $value);
} else {
$key = array_shift($keys);
// just one key left, like 'title'
if($value instanceof WireData) {
$value = $value->get($key);
} else if($value instanceof WireArray) {
if($key == 'count') {
$value = count($value);
} else {
$a = array();
foreach($value as $v) $a[] = $v->get($key);
$value = $a;
}
}
}
} else {
// there is a dot property remaining and nothing to send it to
$value = null;
}
return $value;
}
/**
* Get a property via dot syntax: field.subfield.subfield
*
* Some classes descending WireData may choose to add a call to this as part of their
* get() method as a syntax convenience.
*
* ~~~~~
* $value = $item->get("parent.title");
* ~~~~~
*
* #pw-group-retrieval
*
* @param string $key Name of property you want to retrieve in "a.b" or "a.b.c" format
* @return null|mixed Returns value if found or null if not
*
*/
public function getDot($key) {
return self::_getDot($key, $this);
}
/**
* Provides direct reference access to variables in the $data array
*
* Otherwise the same as get()
*
* @param string $name
* @return mixed|null
*
*/
public function __get($name) {
return $this->get($name);
}
/**
* Enables use of $var('key')
*
* @param string $key
* @return mixed
*
*/
public function __invoke($key) {
return $this->get($key);
}
/**
* Remove a previously set property
*
* ~~~~~
* $item->remove('some_property');
* ~~~~~
*
* #pw-group-manipulation
*
* @param string $key Name of property you want to remove
* @return $this
*
*/
public function remove($key) {
$value = isset($this->data[$key]) ? $this->data[$key] : null;
$this->trackChange("unset:$key", $value, null);
unset($this->data[$key]);
return $this;
}
/**
* Enables the object data properties to be iterable as an array
*
* ~~~~~
* foreach($item as $key => $value) {
* // ...
* }
* ~~~~~
*
* #pw-group-retrieval
*
* @return \ArrayObject
*
*/
#[\ReturnTypeWillChange]
public function getIterator() {
return new \ArrayObject($this->data);
}
/**
* Does this object have the given property?
*
* ~~~~~
* if($item->has('some_property')) {
* // the item has some_property
* }
* ~~~~~
*
* #pw-group-retrieval
*
* @param string $key Name of property you want to check.
* @return bool True if it has the property, false if not.
*
*/
public function has($key) {
if(isset($this->data[$key])) return true; // optimization
return ($this->get($key) !== null);
}
/**
* Take the current item and append the given item(s), returning a new WireArray
*
* This is for syntactic convenience in fluent interfaces.
* ~~~~~
* if($page->and($page->parents)->has("featured=1")) {
* // page or one of its parents has a featured property with value of 1
* }
* ~~~~~
*
* #pw-group-retrieval
*
* @param WireArray|WireData|string|null $items May be any of the following:
* - `WireData` object (or derivative)
* - `WireArray` object (or derivative)
* - Name of any property from this object that returns one of the above.
* - Omit argument to simply return this object in a WireArray
* @return WireArray Returns a WireArray of this object *and* the one(s) given.
* @throws WireException If invalid argument supplied.
*
*/
public function ___and($items = null) {
if(is_string($items)) $items = $this->get($items);
if($items instanceof WireArray) {
// great, that's what we want
$a = clone $items;
$a->prepend($this);
} else if($items instanceof WireData || is_null($items)) {
// single item
$className = $this->className(true) . 'Array';
if(!class_exists($className)) $className = wireClassName('WireArray', true);
/** @var WireArray $a */
$a = $this->wire(new $className());
$a->add($this);
if($items) $a->add($items);
} else {
// unknown
throw new WireException('Invalid argument provided to WireData::and(...)');
}
return $a;
}
/**
* Ensures that isset() and empty() work for this classes properties.
*
* #pw-internal
*
* @param string $key
* @return bool
*
*/
public function __isset($key) {
return isset($this->data[$key]);
}
/**
* Ensures that unset() works for this classes data.
*
* #pw-internal
*
* @param string $key
*
*/
public function __unset($key) {
$this->remove($key);
}
/**
* Sets an index in the WireArray.
*
* For the ArrayAccess interface.
*
* #pw-internal
*
* @param int|string $offset Key of item to set.
* @param int|string|array|object $value Value of item.
*
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value) {
$this->set($offset, $value);
}
/**
* Returns the value of the item at the given index, or false if not set.
*
* #pw-internal
*
* @param int|string $offset Key of item to retrieve.
* @return int|string|array|object Value of item requested, or false if it doesn't exist.
*
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
$value = $this->get($offset);
return is_null($value) ? false : $value;
}
/**
* Unsets the value at the given index.
*
* For the ArrayAccess interface.
*
* #pw-internal
*
* @param int|string $offset Key of the item to unset.
* @return bool True if item existed and was unset. False if item didn't exist.
*
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset) {
if($this->__isset($offset)) {
$this->remove($offset);
return true;
} else {
return false;
}
}
/**
* Determines if the given index exists in this WireData.
*
* For the ArrayAccess interface.
*
* #pw-internal
*
* @param int|string $offset Key of the item to check for existence.
* @return bool True if the item exists, false if not.
*
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset) {
return $this->__isset($offset);
}
/**
* debugInfo PHP 5.6+ magic method
*
* @return array
*
*/
public function __debugInfo() {
$info = parent::__debugInfo();
if(count($this->data)) $info['data'] = $this->data;
return $info;
}
}