1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Flying\Struct; |
4
|
|
|
|
5
|
|
|
use Flying\Config\AbstractConfig; |
6
|
|
|
use Flying\Struct\Common\ComplexPropertyInterface; |
7
|
|
|
use Flying\Struct\Common\SimplePropertyInterface; |
8
|
|
|
use Flying\Struct\Common\UpdateNotifyListenerInterface; |
9
|
|
|
use Flying\Struct\Metadata\MetadataInterface; |
10
|
|
|
use Flying\Struct\Metadata\MetadataModificationInterface; |
11
|
|
|
use Flying\Struct\Metadata\StructMetadata; |
12
|
|
|
use Flying\Struct\Property\Property; |
13
|
|
|
use Flying\Struct\Property\PropertyInterface; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Base implementation of structure class |
17
|
|
|
*/ |
18
|
|
|
class Struct extends AbstractConfig implements StructInterface, MetadataModificationInterface |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* Initial contents for structure properties |
22
|
|
|
* |
23
|
|
|
* @var array |
24
|
|
|
*/ |
25
|
|
|
protected $initialContents; |
26
|
|
|
/** |
27
|
|
|
* TRUE to skip property change notification, FALSE otherwise |
28
|
|
|
* |
29
|
|
|
* @var boolean |
30
|
|
|
*/ |
31
|
|
|
protected $skipNotify = false; |
32
|
|
|
/** |
33
|
|
|
* Parent structure or NULL if this is top-level structure |
34
|
|
|
* |
35
|
|
|
* @var Struct |
36
|
|
|
*/ |
37
|
|
|
protected $parent; |
38
|
|
|
/** |
39
|
|
|
* Structure contents |
40
|
|
|
* |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
private $struct; |
44
|
|
|
/** |
45
|
|
|
* Structure size (for Countable interface) |
46
|
|
|
* |
47
|
|
|
* @var int |
48
|
|
|
*/ |
49
|
|
|
private $count = 0; |
50
|
|
|
/** |
51
|
|
|
* Current index in structure (for Iterator interface) |
52
|
|
|
* |
53
|
|
|
* @var int |
54
|
|
|
*/ |
55
|
|
|
private $index = 0; |
56
|
|
|
/** |
57
|
|
|
* Structure metadata |
58
|
|
|
* |
59
|
|
|
* @var StructMetadata |
60
|
|
|
*/ |
61
|
|
|
private $metadata; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Class constructor |
65
|
|
|
* |
66
|
|
|
* @param array|object $contents OPTIONAL Contents to initialize structure with |
67
|
|
|
* @param array|object $config OPTIONAL Configuration for this structure |
68
|
|
|
* @throws \Flying\Struct\Exception |
69
|
|
|
* @throws \RuntimeException |
70
|
|
|
*/ |
71
|
163 |
View Code Duplication |
public function __construct($contents = null, $config = null) |
|
|
|
|
72
|
|
|
{ |
73
|
|
|
// No change notification is required during object construction |
74
|
163 |
|
$flag = $this->skipNotify; |
75
|
163 |
|
$this->skipNotify = true; |
76
|
163 |
|
$this->setConfig($config); |
|
|
|
|
77
|
163 |
|
if ($contents !== null) { |
78
|
9 |
|
$this->setInitialContents($contents); |
79
|
9 |
|
} |
80
|
163 |
|
$this->createStruct($this->getMetadata()); |
81
|
163 |
|
$this->skipNotify = $flag; |
82
|
163 |
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Create structure from given metadata information |
86
|
|
|
* |
87
|
|
|
* @param StructMetadata $metadata |
88
|
|
|
* @throws Exception |
89
|
|
|
* @return void |
90
|
|
|
* @throws \RuntimeException |
91
|
|
|
*/ |
92
|
163 |
|
protected function createStruct(StructMetadata $metadata = null) |
93
|
|
|
{ |
94
|
163 |
|
if (!$metadata) { |
95
|
14 |
|
$metadata = $this->getMetadata(); |
96
|
14 |
|
} |
97
|
163 |
|
$contents = $this->getInitialContents(); |
98
|
|
|
$baseConfig = [ |
99
|
163 |
|
'parent_structure' => $this, |
100
|
163 |
|
'update_notify_listener' => $this, |
101
|
163 |
|
]; |
102
|
163 |
|
$this->struct = []; |
103
|
|
|
/** @var $property MetadataInterface */ |
104
|
163 |
|
foreach ($metadata->getProperties() as $name => $property) { |
105
|
163 |
|
$class = $property->getClass(); |
106
|
163 |
|
$value = array_key_exists($name, $contents) ? $contents[$name] : null; |
107
|
163 |
|
$config = array_merge($property->getConfig(), $baseConfig); |
108
|
163 |
|
if ($property instanceof StructMetadata) { |
109
|
61 |
|
$config['metadata'] = $property; |
110
|
61 |
|
} |
111
|
163 |
|
if ($class === null) { |
112
|
|
|
// Structure properties metadata is defined explicitly |
113
|
14 |
|
$class = $this->getConfig('explicit_metadata_class'); |
114
|
14 |
|
$config['metadata'] = $property; |
115
|
14 |
|
} |
116
|
163 |
|
$instance = new $class($value, $config); |
117
|
163 |
|
if ((!$instance instanceof PropertyInterface) && (!$instance instanceof StructInterface)) { |
118
|
|
|
throw new Exception('Invalid class "' . $class . '" for structure property: ' . $name); |
119
|
|
|
} |
120
|
163 |
|
$this->struct[$name] = $instance; |
121
|
163 |
|
} |
122
|
163 |
|
$this->count = count($this->struct); |
123
|
163 |
|
$this->rewind(); |
124
|
163 |
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Get structure metadata |
128
|
|
|
* |
129
|
|
|
* @return StructMetadata |
130
|
|
|
* @throws \Flying\Struct\Exception |
131
|
|
|
*/ |
132
|
163 |
|
protected function getMetadata() |
133
|
|
|
{ |
134
|
163 |
|
if (!$this->metadata) { |
135
|
|
|
try { |
136
|
|
|
/** @var $metadata StructMetadata */ |
137
|
161 |
|
$metadata = $this->getConfig('metadata'); |
138
|
161 |
|
if (!$metadata instanceof StructMetadata) { |
139
|
|
|
/** @var $configuration Configuration */ |
140
|
|
|
$configuration = $this->getConfig('configuration'); |
141
|
|
|
$metadata = $configuration->getMetadataManager()->getMetadata($this); |
142
|
|
|
/** @noinspection NotOptimalIfConditionsInspection */ |
143
|
|
|
if (!$metadata instanceof StructMetadata) { |
144
|
|
|
throw new Exception('No metadata information is found for structure: ' . get_class($this)); |
145
|
|
|
} |
146
|
|
|
} |
147
|
161 |
|
} catch (\RuntimeException $e) { |
148
|
|
|
throw new Exception('Failed to obtain metadata information for structure: ' . get_class($this)); |
149
|
|
|
} |
150
|
161 |
|
$this->metadata = $metadata; |
151
|
161 |
|
} |
152
|
163 |
|
return $this->metadata; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Get initial structure contents |
157
|
|
|
* |
158
|
|
|
* @param string $name OPTIONAL Structure property name to get contents of, |
159
|
|
|
* NULL to get all available contents |
160
|
|
|
* @return mixed |
161
|
|
|
*/ |
162
|
163 |
|
protected function getInitialContents($name = null) |
163
|
|
|
{ |
164
|
163 |
|
if (!is_array($this->initialContents)) { |
165
|
83 |
|
$this->initialContents = []; |
166
|
83 |
|
} |
167
|
163 |
|
if ($name !== null) { |
168
|
|
|
return array_key_exists($name, $this->initialContents) ? $this->initialContents[$name] : null; |
169
|
|
|
} |
170
|
|
|
|
171
|
163 |
|
return $this->initialContents; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Set initial structure contents |
176
|
|
|
* |
177
|
|
|
* @param array|object $contents |
178
|
|
|
* @return void |
179
|
|
|
*/ |
180
|
9 |
|
protected function setInitialContents($contents) |
181
|
|
|
{ |
182
|
9 |
|
if (is_array($contents)) { |
183
|
2 |
|
$this->initialContents = $contents; |
184
|
9 |
|
} elseif (is_object($contents)) { |
185
|
5 |
|
$this->initialContents = $this->convertToArray($contents); |
186
|
5 |
|
} |
187
|
9 |
|
if (!is_array($this->initialContents)) { |
188
|
2 |
|
$this->initialContents = []; |
189
|
2 |
|
} |
190
|
9 |
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Defined by Iterator interface |
194
|
|
|
* |
195
|
|
|
* @return void |
196
|
|
|
*/ |
197
|
163 |
|
public function rewind() |
198
|
|
|
{ |
199
|
163 |
|
reset($this->struct); |
200
|
163 |
|
$this->index = 0; |
201
|
163 |
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Modify metadata for this structure after it was parsed by MetadataManager |
205
|
|
|
* |
206
|
|
|
* @param StructMetadata $metadata |
207
|
|
|
*/ |
208
|
161 |
|
public static function modifyMetadata(StructMetadata $metadata) |
209
|
|
|
{ |
210
|
|
|
|
211
|
161 |
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Handling of object cloning |
215
|
|
|
* |
216
|
|
|
* @return void |
217
|
|
|
*/ |
218
|
4 |
|
public function __clone() |
219
|
|
|
{ |
220
|
|
|
$config = [ |
221
|
4 |
|
'parent_structure' => $this, |
222
|
4 |
|
'update_notify_listener' => $this, |
223
|
4 |
|
]; |
224
|
|
|
/** @var $property Property */ |
225
|
4 |
|
foreach ($this->struct as &$property) { |
226
|
4 |
|
$property = clone $property; |
227
|
4 |
|
$property->setConfig($config); |
228
|
4 |
|
} |
229
|
4 |
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Get structure property with given name |
233
|
|
|
* |
234
|
|
|
* @param string $name |
235
|
|
|
* @return PropertyInterface|ComplexPropertyInterface|null |
236
|
|
|
*/ |
237
|
7 |
|
public function getProperty($name) |
238
|
|
|
{ |
239
|
7 |
|
if ($this->__isset($name)) { |
240
|
7 |
|
return $this->struct[$name]; |
241
|
|
|
} |
242
|
6 |
|
return null; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Support isset() overloading |
247
|
|
|
* |
248
|
|
|
* @param string $name |
249
|
|
|
* @return boolean |
250
|
|
|
*/ |
251
|
9 |
|
public function __isset($name) |
252
|
|
|
{ |
253
|
9 |
|
return array_key_exists($name, $this->struct); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Magic function so that $obj->value will work. |
258
|
|
|
* |
259
|
|
|
* @param string $name |
260
|
|
|
* @return mixed |
261
|
|
|
*/ |
262
|
23 |
|
public function __get($name) |
263
|
|
|
{ |
264
|
23 |
|
return $this->get($name); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Magic function for setting structure property value |
269
|
|
|
* |
270
|
|
|
* @param string $name Structure property name to set value of |
271
|
|
|
* @param mixed $value New value for this property |
272
|
|
|
* @return void |
273
|
|
|
* @throws \RuntimeException |
274
|
|
|
*/ |
275
|
15 |
|
public function __set($name, $value) |
276
|
|
|
{ |
277
|
15 |
|
$this->set($name, $value); |
278
|
15 |
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Retrieve value of structure property with given name and return $default if there is no such property |
282
|
|
|
* |
283
|
|
|
* @param string $name Structure property name to get value of |
284
|
|
|
* @param mixed $default OPTIONAL Default value to return in a case if property is not available |
285
|
|
|
* @return mixed |
286
|
|
|
*/ |
287
|
77 |
|
public function get($name, $default = null) |
288
|
|
|
{ |
289
|
77 |
|
$result = $default; |
290
|
77 |
|
if (array_key_exists($name, $this->struct)) { |
291
|
75 |
|
$property = $this->struct[$name]; |
292
|
75 |
|
if ($property instanceof ComplexPropertyInterface) { |
293
|
40 |
|
$result = $property; |
294
|
75 |
|
} elseif ($property instanceof PropertyInterface) { |
295
|
64 |
|
$result = $property->getValue(); |
296
|
64 |
|
} |
297
|
75 |
|
} else { |
298
|
4 |
|
$result = $this->getMissed($name, $default); |
299
|
|
|
} |
300
|
77 |
|
return $result; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Handle get requests for missed structure properties |
305
|
|
|
* |
306
|
|
|
* @param string $name Requested structure property name |
307
|
|
|
* @param mixed $default Given default value |
308
|
|
|
* @return mixed |
309
|
|
|
*/ |
310
|
4 |
|
protected function getMissed($name, $default) |
|
|
|
|
311
|
|
|
{ |
312
|
|
|
// This method can be overridden into derived classes |
313
|
|
|
// to handle attempts to get missed structure properties |
314
|
4 |
|
return $default; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Set value of structure property with given name |
319
|
|
|
* |
320
|
|
|
* @param string|array $name Either name of structure property to set value of |
321
|
|
|
* or array of structure properties to set |
322
|
|
|
* @param mixed $value OPTIONAL New value for this property (only if $name is a string) |
323
|
|
|
* @return void |
324
|
|
|
* @throws \RuntimeException |
325
|
|
|
*/ |
326
|
54 |
|
public function set($name, $value = null) |
327
|
|
|
{ |
328
|
54 |
|
$values = is_scalar($name) ? [$name => $value] : $name; |
329
|
54 |
|
foreach ($values as $k => $v) { |
330
|
51 |
|
if (!array_key_exists($k, $this->struct)) { |
331
|
10 |
|
$this->setMissed($k, $v); |
332
|
10 |
|
continue; |
333
|
|
|
} |
334
|
49 |
|
$property = $this->struct[$k]; |
335
|
49 |
|
if ($property instanceof PropertyInterface) { |
336
|
49 |
|
$property->setValue($v); |
337
|
49 |
|
} elseif ($property instanceof StructInterface) { |
338
|
12 |
|
$property->set($v); |
339
|
12 |
|
$this->updateNotify($property); |
340
|
12 |
|
} |
341
|
49 |
|
$this->onChange($k); |
342
|
54 |
|
} |
343
|
54 |
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Handle set requests for missed structure properties |
347
|
|
|
* |
348
|
|
|
* @param string $name Structure property name to set |
349
|
|
|
* @param mixed $value Given value for the property |
350
|
|
|
* @return void |
351
|
|
|
*/ |
352
|
10 |
|
protected function setMissed($name, $value) |
|
|
|
|
353
|
|
|
{ |
354
|
|
|
// This method can be overridden into derived classes |
355
|
|
|
// to handle attempts to set missed structure properties |
356
|
10 |
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Handle notification about update of given property |
360
|
|
|
* |
361
|
|
|
* @param SimplePropertyInterface $property |
362
|
|
|
* @return void |
363
|
|
|
* @throws \RuntimeException |
364
|
|
|
*/ |
365
|
53 |
View Code Duplication |
public function updateNotify(SimplePropertyInterface $property) |
|
|
|
|
366
|
|
|
{ |
367
|
53 |
|
if (!$this->skipNotify) { |
368
|
42 |
|
$owner = $this->getConfig('update_notify_listener'); |
369
|
42 |
|
if ($owner instanceof UpdateNotifyListenerInterface) { |
370
|
20 |
|
$owner->updateNotify($property); |
371
|
20 |
|
} |
372
|
42 |
|
} |
373
|
53 |
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Structure properties change event handler |
377
|
|
|
* |
378
|
|
|
* @param string $name Name of changed property |
379
|
|
|
* @return void |
380
|
|
|
*/ |
381
|
49 |
|
protected function onChange($name) |
|
|
|
|
382
|
|
|
{ |
383
|
|
|
// This method can be overridden into derived classes |
384
|
|
|
// to perform some additional tasks upon structure's properties change |
385
|
49 |
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Reset structure to its initial state |
389
|
|
|
* |
390
|
|
|
* @return void |
391
|
|
|
*/ |
392
|
2 |
|
public function reset() |
393
|
|
|
{ |
394
|
2 |
|
$flag = $this->skipNotify; |
395
|
2 |
|
$this->skipNotify = true; |
396
|
|
|
/** @var $property PropertyInterface */ |
397
|
2 |
|
foreach ($this->struct as $property) { |
398
|
2 |
|
$property->reset(); |
399
|
2 |
|
} |
400
|
2 |
|
$this->skipNotify = $flag; |
401
|
2 |
|
$this->rewind(); |
402
|
2 |
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* Defined by Countable interface |
406
|
|
|
* |
407
|
|
|
* @return int |
408
|
|
|
*/ |
409
|
14 |
|
public function count() |
410
|
|
|
{ |
411
|
14 |
|
return $this->count; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Defined by Iterator interface |
416
|
|
|
* |
417
|
|
|
* @return mixed |
418
|
|
|
*/ |
419
|
32 |
|
public function current() |
420
|
|
|
{ |
421
|
32 |
|
return $this->get(key($this->struct)); |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Defined by Iterator interface |
426
|
|
|
* |
427
|
|
|
* @return mixed |
428
|
|
|
*/ |
429
|
32 |
|
public function key() |
430
|
|
|
{ |
431
|
32 |
|
return key($this->struct); |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Defined by Iterator interface |
436
|
|
|
* |
437
|
|
|
* @return void |
438
|
|
|
*/ |
439
|
32 |
|
public function next() |
440
|
|
|
{ |
441
|
32 |
|
next($this->struct); |
442
|
32 |
|
$this->index++; |
443
|
32 |
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Defined by Iterator interface |
447
|
|
|
* |
448
|
|
|
* @return boolean |
449
|
|
|
*/ |
450
|
32 |
|
public function valid() |
451
|
|
|
{ |
452
|
32 |
|
return ($this->index < $this->count); |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
/** |
456
|
|
|
* Defined by RecursiveIterator interface |
457
|
|
|
* |
458
|
|
|
* @return boolean |
459
|
|
|
*/ |
460
|
14 |
|
public function hasChildren() |
461
|
|
|
{ |
462
|
14 |
|
return ($this->struct[key($this->struct)] instanceof StructInterface); |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Defined by RecursiveIterator interface |
467
|
|
|
* |
468
|
|
|
* @return \RecursiveIterator |
469
|
|
|
*/ |
470
|
7 |
|
public function getChildren() |
471
|
|
|
{ |
472
|
7 |
|
return $this->struct[key($this->struct)]; |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* Defined by ArrayAccess interface |
477
|
|
|
* |
478
|
|
|
* @param mixed $offset |
479
|
|
|
* @return boolean |
480
|
|
|
*/ |
481
|
2 |
|
public function offsetExists($offset) |
482
|
|
|
{ |
483
|
2 |
|
return $this->__isset($offset); |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Defined by ArrayAccess interface |
488
|
|
|
* |
489
|
|
|
* @param mixed $offset |
490
|
|
|
* @return mixed |
491
|
|
|
*/ |
492
|
18 |
|
public function offsetGet($offset) |
493
|
|
|
{ |
494
|
18 |
|
return $this->get($offset); |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/** |
498
|
|
|
* Defined by ArrayAccess interface |
499
|
|
|
* |
500
|
|
|
* @param mixed $offset |
501
|
|
|
* @param mixed $value |
502
|
|
|
* @return void |
503
|
|
|
* @throws \RuntimeException |
504
|
|
|
*/ |
505
|
4 |
|
public function offsetSet($offset, $value) |
506
|
|
|
{ |
507
|
4 |
|
$this->set($offset, $value); |
508
|
4 |
|
} |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Defined by ArrayAccess interface |
512
|
|
|
* |
513
|
|
|
* @param mixed $offset |
514
|
|
|
* @return void |
515
|
|
|
*/ |
516
|
2 |
|
public function offsetUnset($offset) |
517
|
|
|
{ |
518
|
2 |
|
$this->__unset($offset); |
519
|
2 |
|
} |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* Support unset() overloading |
523
|
|
|
* Unset of structure property in a term of removing it from structure is not allowed, |
524
|
|
|
* so unset() just reset field's value. |
525
|
|
|
* |
526
|
|
|
* @param string $name |
527
|
|
|
* @return void |
528
|
|
|
*/ |
529
|
4 |
|
public function __unset($name) |
530
|
|
|
{ |
531
|
4 |
|
if (array_key_exists($name, $this->struct)) { |
532
|
|
|
/** @var $property PropertyInterface */ |
533
|
4 |
|
$property = $this->struct[$name]; |
534
|
4 |
|
$property->reset(); |
535
|
4 |
|
} |
536
|
4 |
|
} |
537
|
|
|
|
538
|
|
|
/** |
539
|
|
|
* Implementation of Serializable interface |
540
|
|
|
* |
541
|
|
|
* @return string |
542
|
|
|
* @throws \Flying\Struct\Exception |
543
|
|
|
*/ |
544
|
14 |
|
public function serialize() |
545
|
|
|
{ |
546
|
14 |
|
return serialize([ |
547
|
14 |
|
'metadata' => $this->getMetadata(), |
548
|
14 |
|
'struct' => $this->toArray(), |
549
|
14 |
|
]); |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
/** |
553
|
|
|
* Get structure contents as associative array |
554
|
|
|
* |
555
|
|
|
* @return array |
556
|
|
|
*/ |
557
|
74 |
|
public function toArray() |
558
|
|
|
{ |
559
|
74 |
|
$array = []; |
560
|
74 |
|
foreach ($this->struct as $key => $value) { |
561
|
74 |
|
if ($value instanceof ComplexPropertyInterface) { |
562
|
29 |
|
$array[$key] = $value->toArray(); |
563
|
74 |
|
} elseif ($value instanceof PropertyInterface) { |
564
|
74 |
|
$array[$key] = $value->getValue(); |
565
|
74 |
|
} |
566
|
74 |
|
} |
567
|
74 |
|
return $array; |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
/** |
571
|
|
|
* Implementation of Serializable interface |
572
|
|
|
* |
573
|
|
|
* @param string $serialized Serialized object data |
574
|
|
|
* @return void |
575
|
|
|
* @throws \InvalidArgumentException |
576
|
|
|
* @throws \RuntimeException |
577
|
|
|
* @throws \Flying\Struct\Exception |
578
|
|
|
*/ |
579
|
14 |
|
public function unserialize($serialized) |
580
|
|
|
{ |
581
|
14 |
|
$data = unserialize($serialized); |
582
|
14 |
|
if ((!is_array($data)) || |
583
|
14 |
|
(!array_key_exists('metadata', $data)) || |
584
|
14 |
|
(!array_key_exists('struct', $data)) || |
585
|
14 |
|
(!$data['metadata'] instanceof StructMetadata) || |
586
|
14 |
|
(!is_array($data['struct'])) |
587
|
14 |
|
) { |
588
|
|
|
throw new \InvalidArgumentException('Serialized structure information has invalid format'); |
589
|
|
|
} |
590
|
14 |
|
$flag = $this->skipNotify; |
591
|
14 |
|
$this->skipNotify = true; |
592
|
14 |
|
$this->setConfig('metadata', $data['metadata']); |
593
|
14 |
|
$this->createStruct(); |
594
|
14 |
|
$this->set($data['struct']); |
595
|
14 |
|
$this->skipNotify = $flag; |
596
|
14 |
|
} |
597
|
|
|
|
598
|
|
|
/** |
599
|
|
|
* Attempt to convert given structure contents to array |
600
|
|
|
* |
601
|
|
|
* @param mixed $contents Value to convert to array |
602
|
|
|
* @return array |
603
|
|
|
*/ |
604
|
10 |
|
protected function convertToArray($contents) |
605
|
|
|
{ |
606
|
10 |
|
if (is_object($contents)) { |
607
|
10 |
|
if (is_callable([$contents, 'toArray'])) { |
608
|
4 |
|
$contents = $contents->toArray(); |
609
|
10 |
|
} elseif ($contents instanceof \ArrayObject) { |
610
|
4 |
|
$contents = $contents->getArrayCopy(); |
611
|
6 |
|
} elseif ($contents instanceof \Iterator) { |
612
|
2 |
|
$temp = []; |
613
|
2 |
|
foreach ($contents as $k => $v) { |
614
|
2 |
|
$temp[$k] = $v; |
615
|
2 |
|
} |
616
|
2 |
|
$contents = $temp; |
617
|
2 |
|
} |
618
|
10 |
|
} |
619
|
10 |
|
if (!is_array($contents)) { |
620
|
|
|
$contents = []; |
621
|
|
|
} |
622
|
10 |
|
return $contents; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
/** |
626
|
|
|
* Initialize list of configuration options |
627
|
|
|
* |
628
|
|
|
* @return void |
629
|
|
|
* @throws \RuntimeException |
630
|
|
|
* @throws \InvalidArgumentException |
631
|
|
|
*/ |
632
|
1 |
|
protected function initConfig() |
633
|
|
|
{ |
634
|
1 |
|
parent::initConfig(); |
635
|
1 |
|
$this->mergeConfig([ |
636
|
1 |
|
'configuration' => null, // Structures configuration object (@see Configuration) |
637
|
1 |
|
'metadata' => null, // Structure metadata |
638
|
1 |
|
'parent_structure' => null, // Link to parent structure in a case of multi-level structures |
639
|
1 |
|
'update_notify_listener' => null, // Listener for structure's update notifications |
640
|
1 |
|
'explicit_metadata_class' => null, // Name of the class to use to create structures from explicitly defined properties |
641
|
1 |
|
]); |
642
|
1 |
|
} |
643
|
|
|
|
644
|
|
|
/** |
645
|
|
|
* Perform "lazy initialization" of configuration option with given name |
646
|
|
|
* |
647
|
|
|
* @param string $name Configuration option name |
648
|
|
|
* @return mixed |
649
|
|
|
* @throws \RuntimeException |
650
|
|
|
*/ |
651
|
161 |
|
protected function lazyConfigInit($name) |
652
|
|
|
{ |
653
|
|
|
switch ($name) { |
654
|
161 |
|
case 'configuration': |
655
|
161 |
|
return ConfigurationManager::getConfiguration(); |
656
|
161 |
|
case 'metadata': |
657
|
|
|
/** @var $configuration Configuration */ |
658
|
161 |
|
$configuration = $this->getConfig('configuration'); |
659
|
161 |
|
return $configuration->getMetadataManager()->getMetadata($this); |
660
|
45 |
|
case 'explicit_metadata_class': |
661
|
14 |
|
return __CLASS__; |
662
|
31 |
|
default: |
663
|
31 |
|
return parent::lazyConfigInit($name); |
664
|
31 |
|
} |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Check that given value of configuration option is valid |
669
|
|
|
* |
670
|
|
|
* @param string $name Configuration option name |
671
|
|
|
* @param mixed $value Option value (passed by reference) |
672
|
|
|
* @throws \InvalidArgumentException |
673
|
|
|
* @return boolean |
674
|
|
|
*/ |
675
|
163 |
|
protected function validateConfig($name, &$value) |
676
|
|
|
{ |
677
|
|
|
switch ($name) { |
678
|
163 |
|
case 'configuration': |
679
|
161 |
|
if (($value !== null) && (!$value instanceof Configuration)) { |
680
|
|
|
throw new \InvalidArgumentException('Structure configuration object must be instance of Configuration'); |
681
|
|
|
} |
682
|
161 |
|
break; |
683
|
163 |
|
case 'metadata': |
684
|
163 |
|
if (($value !== null) && (!$value instanceof StructMetadata)) { |
685
|
|
|
throw new \InvalidArgumentException('Structure metadata object must be instance of StructMetadata'); |
686
|
|
|
} |
687
|
163 |
|
break; |
688
|
85 |
|
case 'parent_structure': |
689
|
61 |
|
if (($value !== null) && (!$value instanceof StructInterface)) { |
690
|
|
|
throw new \InvalidArgumentException('Only structure object can be used as parent structure'); |
691
|
|
|
} |
692
|
61 |
|
break; |
693
|
85 |
View Code Duplication |
case 'update_notify_listener': |
|
|
|
|
694
|
85 |
|
if (($value !== null) && (!$value instanceof UpdateNotifyListenerInterface)) { |
695
|
|
|
throw new \InvalidArgumentException('Update notification listener must implement UpdateNotifyListenerInterface interface'); |
696
|
|
|
} |
697
|
85 |
|
break; |
698
|
14 |
|
case 'explicit_metadata_class': |
699
|
14 |
|
if (!is_string($value)) { |
700
|
|
|
throw new \InvalidArgumentException('Explicit metadata class name should be defined as string'); |
701
|
|
|
} |
702
|
14 |
|
if (!class_exists($value)) { |
703
|
|
|
throw new \InvalidArgumentException('Unknown class name is defined for explicit metadata class'); |
704
|
|
|
} |
705
|
|
|
try { |
706
|
14 |
|
$reflection = new \ReflectionClass($value); |
707
|
14 |
|
} catch (\ReflectionException $e) { |
708
|
|
|
throw new \InvalidArgumentException('Invalid class is defined for explicit metadata class'); |
709
|
|
|
} |
710
|
14 |
|
if ((!$reflection->isInstantiable()) || (!$reflection->implementsInterface(StructInterface::class))) { |
711
|
|
|
throw new \InvalidArgumentException('Invalid class is defined for explicit metadata class'); |
712
|
|
|
} |
713
|
14 |
|
break; |
714
|
|
|
} |
715
|
163 |
|
return true; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
/** |
719
|
|
|
* {@inheritdoc} |
720
|
|
|
*/ |
721
|
73 |
|
protected function onConfigChange($name, $value) |
722
|
|
|
{ |
723
|
|
|
switch ($name) { |
724
|
73 |
|
case 'metadata': |
725
|
70 |
|
$this->metadata = $value; |
726
|
70 |
|
break; |
727
|
64 |
|
case 'parent_structure': |
728
|
61 |
|
$this->parent = $value; |
729
|
61 |
|
break; |
730
|
|
|
} |
731
|
73 |
|
parent::onConfigChange($name, $value); |
732
|
73 |
|
} |
733
|
|
|
} |
734
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.