These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Charcoal\Property; |
||
4 | |||
5 | use PDO; |
||
6 | use ArrayAccess; |
||
7 | use RuntimeException; |
||
8 | use InvalidArgumentException; |
||
9 | use UnexpectedValueException; |
||
10 | |||
11 | // From Pimple |
||
12 | use Pimple\Container; |
||
13 | |||
14 | // From 'charcoal-core' |
||
15 | use Charcoal\Model\DescribableInterface; |
||
16 | use Charcoal\Model\MetadataInterface; |
||
17 | use Charcoal\Model\ModelInterface; |
||
18 | use Charcoal\Model\Model; |
||
19 | |||
20 | // From 'charcoal-factory' |
||
21 | use Charcoal\Factory\FactoryInterface; |
||
22 | |||
23 | // From 'charcoal-property' |
||
24 | use Charcoal\Property\StructureProperty; |
||
25 | use Charcoal\Property\Structure\StructureMetadata; |
||
26 | use Charcoal\Property\Structure\StructureModel; |
||
27 | |||
28 | /** |
||
29 | * Model Structure Data Property |
||
30 | * |
||
31 | * Allows for multiple complex entries to a property, which are stored |
||
32 | * as a JSON string in the model's storage source. Typical use cases would be |
||
33 | * {@see \Charcoal\Cms\Property\TemplateOptionsProperty template options}, |
||
34 | * {@see \Charcoal\Property\MapStructureProperty geolocation coordinates}, |
||
35 | * details for a log, or a list of addresses or people. |
||
36 | * |
||
37 | * The property's "structured_metadata" attribute allows one to build a virtual |
||
38 | * model using much of the same specifications used for defining object models. |
||
39 | * This allows you to constrain the kind of structure you need to store. |
||
40 | * For any values that can't be bound to a model-like structure, consider using |
||
41 | * {@see StructureProperty}. |
||
42 | * |
||
43 | * ## Examples |
||
44 | * |
||
45 | * **Example #1 — Address** |
||
46 | * |
||
47 | * With the use of the {@see \Charcoal\Admin\Widget\FormGroup\StructureFormGroup Structure Form Group}, |
||
48 | * a form UI can be embedded in the object form widget. |
||
49 | * |
||
50 | * ```json |
||
51 | * { |
||
52 | * "properties": { |
||
53 | * "street_address": { |
||
54 | * "type": "string", |
||
55 | * "input_type": "charcoal/admin/property/input/textarea", |
||
56 | * "label": "Street Address" |
||
57 | * }, |
||
58 | * "locality": { |
||
59 | * "type": "string", |
||
60 | * "label": "Municipality" |
||
61 | * }, |
||
62 | * "administrative_area": { |
||
63 | * "type": "string", |
||
64 | * "multiple": true, |
||
65 | * "label": "Administrative Division(s)" |
||
66 | * }, |
||
67 | * "postal_code": { |
||
68 | * "type": "string", |
||
69 | * "label": "Postal Code" |
||
70 | * }, |
||
71 | * "country": { |
||
72 | * "type": "string", |
||
73 | * "label": "Country" |
||
74 | * } |
||
75 | * }, |
||
76 | * "admin": { |
||
77 | * "form_group": { |
||
78 | * "title": "Address", |
||
79 | * "show_header": false, |
||
80 | * "properties": [ |
||
81 | * "street_address", |
||
82 | * "locality", |
||
83 | * "postal_code", |
||
84 | * "administrative_area", |
||
85 | * "country" |
||
86 | * ], |
||
87 | * "layout": { |
||
88 | * "structure": [ |
||
89 | * { "columns": [ 1 ] }, |
||
90 | * { "columns": [ 5, 1 ] }, |
||
91 | * { "columns": [ 1, 1 ] } |
||
92 | * ] |
||
93 | * } |
||
94 | * } |
||
95 | * } |
||
96 | * } |
||
97 | * ``` |
||
98 | */ |
||
99 | class ModelStructureProperty extends StructureProperty |
||
100 | { |
||
101 | /** |
||
102 | * Track the state of loaded metadata for the structure. |
||
103 | * |
||
104 | * @var boolean |
||
105 | */ |
||
106 | private $isStructureFinalized = false; |
||
107 | |||
108 | /** |
||
109 | * The metadata interfaces to use as the structure. |
||
110 | * |
||
111 | * These are paths (PSR-4) to import. |
||
112 | * |
||
113 | * @var array |
||
114 | */ |
||
115 | private $structureInterfaces = []; |
||
116 | |||
117 | /** |
||
118 | * Store the property's structure. |
||
119 | * |
||
120 | * @var MetadataInterface|array|null |
||
121 | */ |
||
122 | private $structureMetadata; |
||
123 | |||
124 | /** |
||
125 | * Store the property's "terminal" structure. |
||
126 | * |
||
127 | * This represents the value of "structure_metadata" key on a property definition. |
||
128 | * This should always be merged last, after the interfaces are imported. |
||
129 | * |
||
130 | * @var MetadataInterface|array|null |
||
131 | */ |
||
132 | private $terminalStructureMetadata; |
||
133 | |||
134 | /** |
||
135 | * Store the property's model prototype. |
||
136 | * |
||
137 | * @var ArrayAccess|DescribableInterface|null |
||
138 | */ |
||
139 | private $structurePrototype; |
||
140 | |||
141 | /** |
||
142 | * The object type of the "structure" collection to use. |
||
143 | * |
||
144 | * @var string |
||
145 | */ |
||
146 | private $structureModelType; |
||
147 | |||
148 | /** |
||
149 | * The class name of the "structure" collection to use. |
||
150 | * |
||
151 | * Must be a fully-qualified PHP namespace and an implementation of {@see ArrayAccess}. |
||
152 | * |
||
153 | * @var string |
||
154 | */ |
||
155 | private $structureModelClass = StructureModel::class; |
||
156 | |||
157 | /** |
||
158 | * Store the factory instance. |
||
159 | * |
||
160 | * @var FactoryInterface |
||
161 | */ |
||
162 | protected $structureModelFactory; |
||
163 | |||
164 | /** |
||
165 | * Retrieve the property's type identifier. |
||
166 | * |
||
167 | * @return string |
||
168 | */ |
||
169 | public function type() |
||
170 | { |
||
171 | return 'model-structure'; |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * Retrieve the property's structure. |
||
176 | * |
||
177 | * @return MetadataInterface|null |
||
178 | */ |
||
179 | public function getStructureMetadata() |
||
180 | { |
||
181 | if ($this->structureMetadata === null || $this->isStructureFinalized === false) { |
||
182 | $this->structureMetadata = $this->loadStructureMetadata(); |
||
183 | } |
||
184 | |||
185 | return $this->structureMetadata; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Set the property's structure. |
||
190 | * |
||
191 | * @param MetadataInterface|array|null $data The property's structure (fields, data). |
||
192 | * @throws InvalidArgumentException If the structure is invalid. |
||
193 | * @return self |
||
194 | */ |
||
195 | public function setStructureMetadata($data) |
||
196 | { |
||
197 | if ($data === null) { |
||
198 | $this->structureMetadata = $data; |
||
199 | $this->terminalStructureMetadata = $data; |
||
200 | } elseif (is_array($data)) { |
||
201 | $struct = $this->createStructureMetadata(); |
||
202 | $struct->merge($data); |
||
203 | |||
204 | $this->structureMetadata = $struct; |
||
205 | $this->terminalStructureMetadata = $data; |
||
206 | } elseif ($data instanceof MetadataInterface) { |
||
207 | $this->structureMetadata = $data; |
||
208 | $this->terminalStructureMetadata = $data; |
||
209 | } else { |
||
210 | throw new InvalidArgumentException(sprintf( |
||
211 | 'Structure [%s] is invalid (must be array or an instance of %s).', |
||
212 | (is_object($data) ? get_class($data) : gettype($data)), |
||
213 | StructureMetadata::class |
||
214 | )); |
||
215 | } |
||
216 | |||
217 | $this->isStructureFinalized = false; |
||
218 | |||
219 | return $this; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Retrieve the metadata interfaces used by the property as a structure. |
||
224 | * |
||
225 | * @return array |
||
226 | */ |
||
227 | public function getStructureInterfaces() |
||
228 | { |
||
229 | if (empty($this->structureInterfaces)) { |
||
230 | return $this->structureInterfaces; |
||
231 | } |
||
232 | |||
233 | return array_keys($this->structureInterfaces); |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Determine if the property has any structure metadata interfaces. |
||
238 | * |
||
239 | * @return boolean |
||
240 | */ |
||
241 | public function hasStructureInterfaces() |
||
242 | { |
||
243 | return !empty($this->structureInterfaces); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Set the given metadata interfaces for the property to use as a structure. |
||
248 | * |
||
249 | * @param array $interfaces One or more metadata interfaces to use. |
||
250 | * @return self |
||
251 | */ |
||
252 | public function setStructureInterfaces(array $interfaces) |
||
253 | { |
||
254 | $this->structureInterfaces = []; |
||
255 | |||
256 | $this->addStructureInterfaces($interfaces); |
||
257 | |||
258 | return $this; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Add the given metadata interfaces for the property to use as a structure. |
||
263 | * |
||
264 | * @param array $interfaces One or more metadata interfaces to use. |
||
265 | * @return self |
||
266 | */ |
||
267 | public function addStructureInterfaces(array $interfaces) |
||
268 | { |
||
269 | foreach ($interfaces as $interface) { |
||
270 | $this->addStructureInterface($interface); |
||
271 | } |
||
272 | |||
273 | return $this; |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Add the given metadata interfaces for the property to use as a structure. |
||
278 | * |
||
279 | * @param string $interface A metadata interface to use. |
||
280 | * @throws InvalidArgumentException If the interface is not a string. |
||
281 | * @return self |
||
282 | */ |
||
283 | public function addStructureInterface($interface) |
||
284 | { |
||
285 | if (!is_string($interface)) { |
||
286 | throw new InvalidArgumentException(sprintf( |
||
287 | 'Structure interface must to be a string, received %s', |
||
288 | is_object($interface) ? get_class($interface) : gettype($interface) |
||
289 | )); |
||
290 | } |
||
291 | |||
292 | if (!empty($interface)) { |
||
293 | $interface = $this->parseStructureInterface($interface); |
||
294 | |||
295 | $this->structureInterfaces[$interface] = true; |
||
296 | $this->isStructureFinalized = false; |
||
297 | } |
||
298 | |||
299 | return $this; |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Load the property's structure. |
||
304 | * |
||
305 | * @return MetadataInterface |
||
306 | */ |
||
307 | protected function loadStructureMetadata() |
||
308 | { |
||
309 | $structureMetadata = null; |
||
310 | |||
311 | if ($this->isStructureFinalized === false) { |
||
312 | $this->isStructureFinalized = true; |
||
313 | |||
314 | if ($this->hasStructureInterfaces()) { |
||
315 | $structureInterfaces = $this->getStructureInterfaces(); |
||
316 | } elseif ($this->hasCustomStructureModelClass()) { |
||
317 | $structureInterfaces = (array)$this->getStructureModelType(); |
||
318 | } |
||
319 | |||
320 | if (!empty($structureInterfaces)) { |
||
321 | $metadataLoader = $this->metadataLoader(); |
||
322 | $metadataClass = $this->getStructureMetadataClass(); |
||
323 | |||
324 | $structureKey = $structureInterfaces; |
||
325 | array_unshift($structureKey, $this->ident()); |
||
326 | $structureKey = 'property/structure='.$metadataLoader->serializeMetaKey($structureKey); |
||
327 | |||
328 | $structureMetadata = $metadataLoader->load( |
||
329 | $structureKey, |
||
330 | $metadataClass, |
||
331 | $structureInterfaces |
||
332 | ); |
||
333 | } |
||
334 | } |
||
335 | |||
336 | if ($structureMetadata === null) { |
||
337 | $structureMetadata = $this->createStructureMetadata(); |
||
338 | } |
||
339 | |||
340 | if ($this->terminalStructureMetadata) { |
||
341 | $structureMetadata->merge($this->terminalStructureMetadata); |
||
342 | } |
||
343 | |||
344 | return $structureMetadata; |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * Retrieve a singleton of the structure model for prototyping. |
||
349 | * |
||
350 | * @return ArrayAccess|DescribableInterface |
||
351 | */ |
||
352 | public function structureProto() |
||
353 | { |
||
354 | if ($this->structurePrototype === null) { |
||
355 | $model = $this->createStructureModel(); |
||
356 | |||
357 | if ($model instanceof DescribableInterface) { |
||
358 | $model->setMetadata($this->getStructureMetadata()); |
||
359 | } |
||
360 | |||
361 | $this->structurePrototype = $model; |
||
362 | } |
||
363 | |||
364 | return $this->structurePrototype; |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * Retrieve the default data-model structure class name. |
||
369 | * |
||
370 | * @return string |
||
371 | */ |
||
372 | public static function getDefaultStructureModelClass() |
||
373 | { |
||
374 | return StructureModel::class; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * Set the class name of the data-model structure. |
||
379 | * |
||
380 | * A model type (kebab-case) is converted to a FQN. |
||
381 | * |
||
382 | * @param string $className The class name of the structure. |
||
383 | * @throws InvalidArgumentException If the class name is invalid. |
||
384 | * @return self |
||
385 | */ |
||
386 | protected function setStructureModelClass($className) |
||
387 | { |
||
388 | if ($className === null) { |
||
389 | $this->structureModelClass = static::getDefaultStructureModelClass(); |
||
390 | |||
391 | return $this; |
||
392 | } |
||
393 | |||
394 | if (!is_string($className)) { |
||
395 | throw new InvalidArgumentException( |
||
396 | 'Structure class name must be a string.' |
||
397 | ); |
||
398 | } |
||
399 | |||
400 | if (strpos($className, '/') !== false) { |
||
401 | try { |
||
402 | $this->structureModelType = $className; |
||
403 | $prototype = $this->structureModelFactory()->get($className); |
||
404 | $className = get_class($prototype); |
||
405 | } catch (Exception $e) { |
||
406 | throw new InvalidArgumentException(sprintf( |
||
407 | 'Invalid structure class name: %s', |
||
408 | $className |
||
409 | ), 0, $e); |
||
410 | } |
||
411 | } |
||
412 | |||
413 | $this->structureModelClass = $className; |
||
414 | $this->structureModelType = $this->parseStructureInterface($className); |
||
415 | |||
416 | return $this; |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * Determine if the property is using a custom data-model. |
||
421 | * |
||
422 | * @return boolean |
||
423 | */ |
||
424 | public function hasCustomStructureModelClass() |
||
425 | { |
||
426 | return $this->getStructureModelClass() !== static::getDefaultStructureModelClass(); |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * Retrieve the class name of the data-model structure. |
||
431 | * |
||
432 | * @return string |
||
433 | */ |
||
434 | public function getStructureModelClass() |
||
435 | { |
||
436 | return $this->structureModelClass; |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Retrieve the class name of the data-model structure. |
||
441 | * |
||
442 | * @return string |
||
443 | */ |
||
444 | public function getStructureModelType() |
||
445 | { |
||
446 | if ($this->structureModelType === null) { |
||
447 | $this->structureModelType = $this->parseStructureInterface($this->getStructureModelClass()); |
||
448 | } |
||
449 | |||
450 | return $this->structureModelType; |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * Convert the given value into a structure. |
||
455 | * |
||
456 | * Options: |
||
457 | * - `default_data` (_boolean_|_array_) — If TRUE, the default data defined |
||
458 | * in the structure's metadata is merged. If an array, that is merged. |
||
459 | * |
||
460 | * @param mixed $val The value to "structurize". |
||
461 | * @param array|MetadataInterface $options Optional structure options. |
||
462 | * @throws InvalidArgumentException If the options are invalid. |
||
463 | * @return ModelInterface|ModelInterface[] |
||
464 | */ |
||
465 | public function structureVal($val, $options = []) |
||
466 | { |
||
467 | if ($val === null) { |
||
468 | return ($this['multiple'] ? [] : null); |
||
469 | } |
||
470 | |||
471 | $metadata = clone $this->getStructureMetadata(); |
||
472 | |||
473 | if ($options instanceof MetadataInterface) { |
||
474 | $metadata->merge($options); |
||
475 | } elseif ($options === null) { |
||
476 | $options = []; |
||
477 | } elseif (is_array($options)) { |
||
478 | if (isset($options['metadata'])) { |
||
479 | $metadata->merge($options['metadata']); |
||
480 | } |
||
481 | } else { |
||
482 | throw new InvalidArgumentException(sprintf( |
||
483 | 'Structure value options must to be an array or an instance of %2$s, received %1$s', |
||
484 | is_object($options) ? get_class($options) : gettype($options), |
||
485 | StructureMetadata::class |
||
486 | )); |
||
487 | } |
||
488 | |||
489 | $defaultData = []; |
||
490 | if (isset($options['default_data'])) { |
||
491 | if (is_bool($options['default_data'])) { |
||
492 | $withDefaultData = $options['default_data']; |
||
493 | if ($withDefaultData) { |
||
494 | $defaultData = $metadata->defaultData(); |
||
495 | } |
||
496 | } elseif (is_array($options['default_data'])) { |
||
497 | $withDefaultData = true; |
||
498 | $defaultData = $options['default_data']; |
||
499 | } |
||
500 | } |
||
501 | |||
502 | $val = $this->parseVal($val); |
||
503 | |||
504 | if ($this['multiple']) { |
||
505 | $entries = []; |
||
506 | foreach ($val as $v) { |
||
507 | $entries[] = $this->createStructureModelWith($metadata, $defaultData, $v); |
||
508 | } |
||
509 | |||
510 | return $entries; |
||
511 | } else { |
||
512 | return $this->createStructureModelWith($metadata, $defaultData, $val); |
||
513 | } |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Retrieve the structure as a plain array. |
||
518 | * |
||
519 | * @return array |
||
520 | */ |
||
521 | public function toStructure() |
||
522 | { |
||
523 | return $this->structureVal($this->val()); |
||
524 | } |
||
525 | |||
526 | /** |
||
527 | * @param null|string $model Model ident. |
||
528 | * @return ArrayAccess|DescribableInterface|mixed |
||
529 | * @throws UnexpectedValueException If the structure is invalid. |
||
530 | */ |
||
531 | View Code Duplication | public function toModel($model = null) |
|
532 | { |
||
533 | if ($model) { |
||
534 | $structure = $this->structureModelFactory()->create($model); |
||
535 | |||
536 | if (!$structure instanceof ArrayAccess) { |
||
537 | throw new UnexpectedValueException(sprintf( |
||
538 | 'Structure [%s] must implement [%s]', |
||
539 | $model, |
||
540 | ArrayAccess::class |
||
541 | )); |
||
542 | } |
||
543 | |||
544 | return $structure; |
||
545 | } |
||
546 | |||
547 | return $this->structureProto(); |
||
548 | } |
||
549 | |||
550 | /** |
||
551 | * PropertyInterface::save(). |
||
552 | * @param mixed $val The value, at time of saving. |
||
553 | * @return mixed |
||
554 | */ |
||
555 | public function save($val) |
||
556 | { |
||
557 | $val = parent::save($val); |
||
558 | |||
559 | if ($this['multiple']) { |
||
560 | $proto = $this->structureProto(); |
||
561 | if ($proto instanceof ModelInterface) { |
||
562 | $objs = (array)$this->structureVal($val); |
||
563 | $val = []; |
||
564 | if (!empty($objs)) { |
||
565 | $val = []; |
||
566 | foreach ($objs as $obj) { |
||
567 | $obj->saveProperties(); |
||
568 | $val[] = $obj->data(); |
||
569 | } |
||
570 | } |
||
571 | } |
||
572 | } else { |
||
573 | $obj = $this->structureVal($val); |
||
574 | if ($obj instanceof ModelInterface) { |
||
575 | $obj->saveProperties(); |
||
576 | $val = $obj->data(); |
||
577 | } |
||
578 | } |
||
579 | |||
580 | return $val; |
||
581 | } |
||
582 | |||
583 | /** |
||
584 | * @param mixed $val The value to to convert for display. |
||
585 | * @param array $options Optional display options. |
||
586 | * @return string |
||
587 | */ |
||
588 | public function displayVal($val, array $options = []) |
||
589 | { |
||
590 | if ($val === null || $val === '') { |
||
591 | return ''; |
||
592 | } |
||
593 | |||
594 | /** Parse multilingual values */ |
||
595 | View Code Duplication | if ($this['l10n']) { |
|
0 ignored issues
–
show
|
|||
596 | $propertyValue = $this->l10nVal($val, $options); |
||
597 | if ($propertyValue === null) { |
||
598 | return ''; |
||
599 | } |
||
600 | } elseif ($val instanceof Translation) { |
||
601 | $propertyValue = (string)$val; |
||
602 | } else { |
||
603 | $propertyValue = $val; |
||
604 | } |
||
605 | |||
606 | if (!is_scalar($propertyValue)) { |
||
607 | $propertyValue = json_encode($propertyValue, JSON_PRETTY_PRINT); |
||
608 | } |
||
609 | |||
610 | return (string)$propertyValue; |
||
611 | } |
||
612 | |||
613 | /** |
||
614 | * Inject dependencies from a DI Container. |
||
615 | * |
||
616 | * @param Container $container A dependencies container instance. |
||
617 | * @return void |
||
618 | */ |
||
619 | protected function setDependencies(Container $container) |
||
620 | { |
||
621 | parent::setDependencies($container); |
||
622 | |||
623 | $this->setStructureModelFactory($container['model/factory']); |
||
624 | } |
||
625 | |||
626 | /** |
||
627 | * Retrieve the structure model factory. |
||
628 | * |
||
629 | * @throws RuntimeException If the model factory was not previously set. |
||
630 | * @return FactoryInterface |
||
631 | */ |
||
632 | protected function structureModelFactory() |
||
633 | { |
||
634 | if (!isset($this->structureModelFactory)) { |
||
635 | throw new RuntimeException(sprintf( |
||
636 | 'Model Factory is not defined for "%s"', |
||
637 | get_class($this) |
||
638 | )); |
||
639 | } |
||
640 | |||
641 | return $this->structureModelFactory; |
||
642 | } |
||
643 | |||
644 | /** |
||
645 | * Set an structure model factory. |
||
646 | * |
||
647 | * @param FactoryInterface $factory The model factory, to create objects. |
||
648 | * @return self |
||
649 | */ |
||
650 | private function setStructureModelFactory(FactoryInterface $factory) |
||
651 | { |
||
652 | $this->structureModelFactory = $factory; |
||
653 | |||
654 | return $this; |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Parse a metadata identifier from given interface. |
||
659 | * |
||
660 | * Change `\` and `.` to `/` and force lowercase |
||
661 | * |
||
662 | * @param string $interface A metadata interface to convert. |
||
663 | * @return string |
||
664 | */ |
||
665 | protected function parseStructureInterface($interface) |
||
666 | { |
||
667 | $ident = preg_replace('/([a-z])([A-Z])/', '$1-$2', $interface); |
||
668 | $ident = strtolower(str_replace('\\', '/', $ident)); |
||
669 | |||
670 | return $ident; |
||
671 | } |
||
672 | |||
673 | /** |
||
674 | * Create a new metadata object for structures. |
||
675 | * |
||
676 | * Similar to {@see \Charcoal\Model\DescribableTrait::createMetadata()}. |
||
677 | * |
||
678 | * @return MetadataInterface |
||
679 | */ |
||
680 | protected function createStructureMetadata() |
||
681 | { |
||
682 | $class = $this->getStructureMetadataClass(); |
||
683 | return new $class(); |
||
684 | } |
||
685 | |||
686 | /** |
||
687 | * Retrieve the class name of the metadata object. |
||
688 | * |
||
689 | * @return string |
||
690 | */ |
||
691 | protected function getStructureMetadataClass() |
||
692 | { |
||
693 | return StructureMetadata::class; |
||
694 | } |
||
695 | |||
696 | /** |
||
697 | * Create a data-model structure. |
||
698 | * |
||
699 | * @todo Add support for simple {@see ArrayAccess} models. |
||
700 | * @throws UnexpectedValueException If the structure is invalid. |
||
701 | * @return ArrayAccess |
||
702 | */ |
||
703 | View Code Duplication | private function createStructureModel() |
|
704 | { |
||
705 | $structClass = $this->getStructureModelClass(); |
||
706 | $structure = $this->structureModelFactory()->create($structClass); |
||
707 | |||
708 | if (!$structure instanceof ArrayAccess) { |
||
709 | throw new UnexpectedValueException(sprintf( |
||
710 | 'Structure [%s] must implement [%s]', |
||
711 | $structClass, |
||
712 | ArrayAccess::class |
||
713 | )); |
||
714 | } |
||
715 | |||
716 | return $structure; |
||
717 | } |
||
718 | |||
719 | /** |
||
720 | * Create a data-model structure. |
||
721 | * |
||
722 | * @param MetadataInterface $metadata The model's definition. |
||
723 | * @param array ...$datasets The dataset(s) to modelize. |
||
724 | * @throws UnexpectedValueException If the structure is invalid. |
||
725 | * @return DescribableInterface |
||
726 | */ |
||
727 | private function createStructureModelWith( |
||
728 | MetadataInterface $metadata, |
||
729 | array ...$datasets |
||
730 | ) { |
||
731 | $model = $this->createStructureModel(); |
||
732 | if (!$model instanceof DescribableInterface) { |
||
733 | throw new UnexpectedValueException(sprintf( |
||
734 | 'Structure [%s] must implement [%s]', |
||
735 | get_class($model), |
||
736 | DescribableInterface::class |
||
737 | )); |
||
738 | } |
||
739 | |||
740 | $model->setMetadata($metadata); |
||
741 | |||
742 | if ($datasets) { |
||
743 | foreach ($datasets as $data) { |
||
744 | $model->setData($data); |
||
745 | } |
||
746 | } |
||
747 | |||
748 | return $model; |
||
749 | } |
||
750 | } |
||
751 |
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.