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 Traversable; |
||
6 | use RuntimeException; |
||
7 | use InvalidArgumentException; |
||
8 | |||
9 | use PDO; |
||
10 | |||
11 | // From PSR-6 |
||
12 | use Psr\Cache\CacheItemPoolInterface; |
||
13 | |||
14 | // From Pimple |
||
15 | use Pimple\Container; |
||
16 | |||
17 | // From 'charcoal-core' |
||
18 | use Charcoal\Loader\CollectionLoader; |
||
19 | use Charcoal\Model\ModelInterface; |
||
20 | use Charcoal\Model\Service\ModelLoader; |
||
21 | use Charcoal\Source\StorableInterface; |
||
22 | |||
23 | // From 'charcoal-factory' |
||
24 | use Charcoal\Factory\FactoryInterface; |
||
25 | |||
26 | // From 'charcoal-view' |
||
27 | use Charcoal\View\ViewableInterface; |
||
28 | |||
29 | // From 'charcoal-translator' |
||
30 | use Charcoal\Translator\Translation; |
||
31 | |||
32 | // From 'charcoal-property' |
||
33 | use Charcoal\Property\AbstractProperty; |
||
34 | use Charcoal\Property\SelectablePropertyInterface; |
||
35 | |||
36 | /** |
||
37 | * Object Property holds a reference to an external object. |
||
38 | * |
||
39 | * The object property implements the full `SelectablePropertyInterface` without using |
||
40 | * its accompanying trait. (`set_choices`, `add_choice`, `choices`, `has_choice`, `choice`). |
||
41 | */ |
||
42 | class ObjectProperty extends AbstractProperty implements SelectablePropertyInterface |
||
43 | { |
||
44 | const DEFAULT_PATTERN = '{{name}}'; |
||
45 | |||
46 | /** |
||
47 | * The object type to build the choices from. |
||
48 | * |
||
49 | * @var string |
||
50 | */ |
||
51 | private $objType; |
||
52 | |||
53 | /** |
||
54 | * The pattern for rendering the choice as a label. |
||
55 | * |
||
56 | * @var string |
||
57 | */ |
||
58 | private $pattern = self::DEFAULT_PATTERN; |
||
59 | |||
60 | /** |
||
61 | * The available selectable choices. |
||
62 | * |
||
63 | * This collection is built from selected {@see self::$objType}. |
||
64 | * |
||
65 | * @var array |
||
66 | */ |
||
67 | protected $choices = []; |
||
68 | |||
69 | /** |
||
70 | * Store the collection loader for the current class. |
||
71 | * |
||
72 | * @var CollectionLoader |
||
73 | */ |
||
74 | private $collectionLoader; |
||
75 | |||
76 | /** |
||
77 | * The rules for pagination the collection of objects. |
||
78 | * |
||
79 | * @var array|null |
||
80 | */ |
||
81 | protected $pagination; |
||
82 | |||
83 | /** |
||
84 | * The rules for sorting the collection of objects. |
||
85 | * |
||
86 | * @var array|null |
||
87 | */ |
||
88 | protected $orders; |
||
89 | |||
90 | /** |
||
91 | * The rules for filtering the collection of objects. |
||
92 | * |
||
93 | * @var array|null |
||
94 | */ |
||
95 | protected $filters; |
||
96 | |||
97 | /** |
||
98 | * Store the PSR-6 caching service. |
||
99 | * |
||
100 | * @var CacheItemPoolInterface |
||
101 | */ |
||
102 | private $cachePool; |
||
103 | |||
104 | /** |
||
105 | * Store all model loaders. |
||
106 | * |
||
107 | * @var ModelLoader[] |
||
108 | */ |
||
109 | protected static $modelLoaders = []; |
||
110 | |||
111 | /** |
||
112 | * Store the factory instance for the current class. |
||
113 | * |
||
114 | * @var FactoryInterface |
||
115 | */ |
||
116 | private $modelFactory; |
||
117 | |||
118 | /** |
||
119 | * @return string |
||
120 | */ |
||
121 | public function type() |
||
122 | { |
||
123 | return 'object'; |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Set the object type to build the choices from. |
||
128 | * |
||
129 | * @param string $objType The object type. |
||
130 | * @throws InvalidArgumentException If the object type is not a string. |
||
131 | * @return self |
||
132 | */ |
||
133 | View Code Duplication | public function setObjType($objType) |
|
0 ignored issues
–
show
|
|||
134 | { |
||
135 | if (!is_string($objType)) { |
||
136 | throw new InvalidArgumentException(sprintf( |
||
137 | 'Object Property "%s": Object type ("obj_type") must be a string, received %s', |
||
138 | $this->ident(), |
||
139 | gettype($objType) |
||
140 | )); |
||
141 | } |
||
142 | |||
143 | $this->objType = $objType; |
||
144 | |||
145 | return $this; |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Retrieve the object type to build the choices from. |
||
150 | * |
||
151 | * @throws RuntimeException If the object type was not previously set. |
||
152 | * @return string |
||
153 | */ |
||
154 | public function getObjType() |
||
155 | { |
||
156 | if ($this->objType === null) { |
||
157 | throw new RuntimeException(sprintf( |
||
158 | 'Object Property "%s": Missing object type ("obj_type")', |
||
159 | $this->ident() |
||
160 | )); |
||
161 | } |
||
162 | |||
163 | return $this->objType; |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * @param string $pattern The render pattern. |
||
168 | * @throws InvalidArgumentException If the pattern is not a string. |
||
169 | * @return ObjectProperty Chainable |
||
170 | */ |
||
171 | public function setPattern($pattern) |
||
172 | { |
||
173 | if (!is_string($pattern)) { |
||
174 | throw new InvalidArgumentException( |
||
175 | 'The render pattern must be a string.' |
||
176 | ); |
||
177 | } |
||
178 | |||
179 | $this->pattern = $pattern; |
||
180 | |||
181 | return $this; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * @return string |
||
186 | */ |
||
187 | public function getPattern() |
||
188 | { |
||
189 | return $this->pattern; |
||
190 | } |
||
191 | |||
192 | |||
193 | /** |
||
194 | * @return string |
||
195 | */ |
||
196 | View Code Duplication | public function sqlType() |
|
197 | { |
||
198 | if ($this['multiple'] === true) { |
||
199 | return 'TEXT'; |
||
200 | } else { |
||
201 | // Read from proto's key |
||
202 | $proto = $this->proto(); |
||
203 | $key = $proto->p($proto->key()); |
||
204 | |||
205 | return $key->sqlType(); |
||
206 | } |
||
207 | } |
||
208 | |||
209 | /** |
||
210 | * @return integer |
||
211 | */ |
||
212 | View Code Duplication | public function sqlPdoType() |
|
213 | { |
||
214 | if ($this['multiple'] === true) { |
||
215 | return PDO::PARAM_STR; |
||
216 | } else { |
||
217 | // Read from proto's key |
||
218 | $proto = $this->proto(); |
||
219 | $key = $proto->p($proto->key()); |
||
220 | |||
221 | return $key->sqlPdoType(); |
||
222 | } |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * Always return IDs. |
||
227 | * |
||
228 | * @param mixed $val Value to be parsed. |
||
229 | * @return mixed |
||
230 | */ |
||
231 | public function parseOne($val) |
||
232 | { |
||
233 | if ($val instanceof StorableInterface) { |
||
234 | return $val->id(); |
||
235 | } else { |
||
236 | return $val; |
||
237 | } |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Get the property's value in a format suitable for storage. |
||
242 | * |
||
243 | * @param mixed $val Optional. The value to convert to storage value. |
||
244 | * @return mixed |
||
245 | */ |
||
246 | public function storageVal($val) |
||
247 | { |
||
248 | if ($val === null || $val === '') { |
||
249 | // Do not json_encode NULL values |
||
250 | return null; |
||
251 | } |
||
252 | |||
253 | $val = $this->parseVal($val); |
||
254 | |||
255 | if ($this['multiple']) { |
||
256 | if (is_array($val)) { |
||
257 | $val = implode($this->multipleSeparator(), $val); |
||
258 | } |
||
259 | } |
||
260 | |||
261 | if (!is_scalar($val)) { |
||
262 | return json_encode($val); |
||
263 | } |
||
264 | |||
265 | return $val; |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * Retrieve a singleton of the {self::$objType} for prototyping. |
||
270 | * |
||
271 | * @return ModelInterface |
||
272 | */ |
||
273 | public function proto() |
||
274 | { |
||
275 | return $this->modelFactory()->get($this->getObjType()); |
||
276 | } |
||
277 | |||
278 | /** |
||
279 | * @param mixed $val Optional. The value to to convert for input. |
||
280 | * @param array $options Unused input options. |
||
281 | * @return string |
||
282 | */ |
||
283 | public function inputVal($val, array $options = []) |
||
284 | { |
||
285 | unset($options); |
||
286 | |||
287 | if ($val === null) { |
||
288 | return ''; |
||
289 | } |
||
290 | |||
291 | if (is_string($val)) { |
||
292 | return $val; |
||
293 | } |
||
294 | |||
295 | $val = $this->parseVal($val); |
||
296 | |||
297 | if ($this['multiple']) { |
||
298 | if (is_array($val)) { |
||
299 | $val = implode($this->multipleSeparator(), $val); |
||
300 | } |
||
301 | } |
||
302 | |||
303 | if (!is_scalar($val)) { |
||
304 | return json_encode($val); |
||
305 | } |
||
306 | |||
307 | return $val; |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * @param mixed $val The value to to convert for display. |
||
312 | * @param array $options Optional display options. |
||
313 | * @return string |
||
314 | */ |
||
315 | public function displayVal($val, array $options = []) |
||
316 | { |
||
317 | if ($val === null) { |
||
318 | return ''; |
||
319 | } |
||
320 | |||
321 | if (isset($options['pattern'])) { |
||
322 | $pattern = $options['pattern']; |
||
323 | } else { |
||
324 | $pattern = null; |
||
325 | } |
||
326 | |||
327 | if (isset($options['lang'])) { |
||
328 | $lang = $options['lang']; |
||
329 | } else { |
||
330 | $lang = null; |
||
331 | } |
||
332 | |||
333 | if ($val instanceof ModelInterface) { |
||
334 | $propertyVal = $this->renderObjPattern($val, $pattern, $lang); |
||
335 | |||
336 | if (empty($propertyVal) && !is_numeric($propertyVal)) { |
||
337 | $propertyVal = $val->id(); |
||
338 | } |
||
339 | |||
340 | return $propertyVal; |
||
341 | } |
||
342 | |||
343 | /** Parse multilingual values */ |
||
344 | View Code Duplication | if ($this['l10n']) { |
|
345 | $propertyValue = $this->l10nVal($val, $options); |
||
346 | if ($propertyValue === null) { |
||
347 | return ''; |
||
348 | } |
||
349 | } elseif ($val instanceof Translation) { |
||
350 | $propertyValue = (string)$val; |
||
351 | } else { |
||
352 | $propertyValue = $val; |
||
353 | } |
||
354 | |||
355 | /** Parse multiple values / ensure they are of array type. */ |
||
356 | if ($this['multiple']) { |
||
357 | if (!is_array($propertyValue)) { |
||
358 | $propertyValue = $this->parseValAsMultiple($propertyValue); |
||
359 | } |
||
360 | } else { |
||
361 | $propertyValue = (array)$propertyValue; |
||
362 | } |
||
363 | |||
364 | $values = []; |
||
365 | foreach ($propertyValue as $val) { |
||
366 | $label = null; |
||
367 | if ($val instanceof ModelInterface) { |
||
368 | $label = $this->renderObjPattern($val, $pattern, $lang); |
||
369 | } else { |
||
370 | $obj = $this->loadObject($val); |
||
371 | if (is_object($obj)) { |
||
372 | $label = $this->renderObjPattern($obj, $pattern, $lang); |
||
373 | } |
||
374 | } |
||
375 | |||
376 | if (empty($label) && !is_numeric($label)) { |
||
377 | $label = $val; |
||
378 | } |
||
379 | |||
380 | $values[] = $label; |
||
381 | } |
||
382 | |||
383 | $separator = $this->multipleSeparator(); |
||
384 | if ($separator === ',') { |
||
385 | $separator = ', '; |
||
386 | } |
||
387 | |||
388 | return implode($separator, $values); |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Set the available choices. |
||
393 | * |
||
394 | * @param array $choices One or more choice structures. |
||
395 | * @return self |
||
396 | */ |
||
397 | public function setChoices(array $choices) |
||
398 | { |
||
399 | unset($choices); |
||
400 | |||
401 | $this->logger->debug( |
||
402 | 'Choices can not be set for object properties. They are auto-generated from objects.' |
||
403 | ); |
||
404 | |||
405 | return $this; |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Merge the available choices. |
||
410 | * |
||
411 | * @param array $choices One or more choice structures. |
||
412 | * @return self |
||
413 | */ |
||
414 | public function addChoices(array $choices) |
||
415 | { |
||
416 | unset($choices); |
||
417 | |||
418 | $this->logger->debug( |
||
419 | 'Choices can not be added for object properties. They are auto-generated from objects.' |
||
420 | ); |
||
421 | |||
422 | return $this; |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Add a choice to the available choices. |
||
427 | * |
||
428 | * @param string $choiceIdent The choice identifier (will be key / default ident). |
||
429 | * @param string|array $choice A string representing the choice label or a structure. |
||
430 | * @return self |
||
431 | */ |
||
432 | public function addChoice($choiceIdent, $choice) |
||
433 | { |
||
434 | unset($choiceIdent, $choice); |
||
435 | |||
436 | $this->logger->debug( |
||
437 | 'Choices can not be added for object properties. They are auto-generated from objects.' |
||
438 | ); |
||
439 | |||
440 | return $this; |
||
441 | } |
||
442 | |||
443 | /** |
||
444 | * Set the rules for pagination the collection of objects. |
||
445 | * |
||
446 | * @param array $pagination Pagination settings. |
||
447 | * @return ObjectProperty Chainable |
||
448 | */ |
||
449 | public function setPagination(array $pagination) |
||
450 | { |
||
451 | $this->pagination = $pagination; |
||
452 | |||
453 | return $this; |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Retrieve the rules for pagination the collection of objects. |
||
458 | * |
||
459 | * @return array|null |
||
460 | */ |
||
461 | public function pagination() |
||
462 | { |
||
463 | return $this->pagination; |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Set the rules for sorting the collection of objects. |
||
468 | * |
||
469 | * @param array $orders An array of orders. |
||
470 | * @return ObjectProperty Chainable |
||
471 | */ |
||
472 | public function setOrders(array $orders) |
||
473 | { |
||
474 | $this->orders = $orders; |
||
475 | |||
476 | return $this; |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * Retrieve the rules for sorting the collection of objects. |
||
481 | * |
||
482 | * @return array|null |
||
483 | */ |
||
484 | public function orders() |
||
485 | { |
||
486 | return $this->orders; |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Set the rules for filtering the collection of objects. |
||
491 | * |
||
492 | * @param array $filters An array of filters. |
||
493 | * @return ObjectProperty Chainable |
||
494 | */ |
||
495 | public function setFilters(array $filters) |
||
496 | { |
||
497 | $this->filters = $filters; |
||
498 | |||
499 | return $this; |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * Retrieve the rules for filtering the collection of objects. |
||
504 | * |
||
505 | * @return array|null |
||
506 | */ |
||
507 | public function filters() |
||
508 | { |
||
509 | return $this->filters; |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Determine if choices are available. |
||
514 | * |
||
515 | * @return boolean |
||
516 | */ |
||
517 | public function hasChoices() |
||
518 | { |
||
519 | if (!$this->proto()->source()->tableExists()) { |
||
520 | return false; |
||
521 | } |
||
522 | |||
523 | return ($this->collectionModelLoader()->loadCount() > 0); |
||
524 | } |
||
525 | |||
526 | /** |
||
527 | * Retrieve the available choice structures. |
||
528 | * |
||
529 | * @see SelectablePropertyInterface::choices() |
||
530 | * @return array |
||
531 | */ |
||
532 | public function choices() |
||
533 | { |
||
534 | $proto = $this->proto(); |
||
535 | if (!$proto->source()->tableExists()) { |
||
536 | return []; |
||
537 | } |
||
538 | |||
539 | $objects = $this->collectionModelLoader()->load(); |
||
540 | $choices = $this->parseChoices($objects); |
||
541 | |||
542 | return $choices; |
||
543 | } |
||
544 | |||
545 | /** |
||
546 | * Determine if the given choice is available. |
||
547 | * |
||
548 | * @see SelectablePropertyInterface::hasChoice() |
||
549 | * @param string $choiceIdent The choice identifier to lookup. |
||
550 | * @return boolean |
||
551 | */ |
||
552 | public function hasChoice($choiceIdent) |
||
553 | { |
||
554 | $obj = $this->loadObject($choiceIdent); |
||
555 | |||
556 | return ($obj instanceof ModelInterface && $obj->id() == $choiceIdent); |
||
557 | } |
||
558 | |||
559 | /** |
||
560 | * Retrieve the structure for a given choice. |
||
561 | * |
||
562 | * The method can be used to format an object into a choice structure. |
||
563 | * |
||
564 | * @see SelectablePropertyInterface::choice() |
||
565 | * @param string $choiceIdent The choice identifier to lookup or object to format. |
||
566 | * @return mixed The matching choice. |
||
567 | */ |
||
568 | public function choice($choiceIdent) |
||
569 | { |
||
570 | $obj = $this->loadObject($choiceIdent); |
||
571 | |||
572 | if ($obj === null) { |
||
573 | return null; |
||
574 | } |
||
575 | |||
576 | $choice = $this->parseChoice($obj); |
||
577 | |||
578 | return $choice; |
||
579 | } |
||
580 | |||
581 | /** |
||
582 | * Retrieve the label for a given choice. |
||
583 | * |
||
584 | * @see SelectablePropertyInterface::choiceLabel() |
||
585 | * @param string|array|ModelInterface $choice The choice identifier to lookup. |
||
586 | * @throws InvalidArgumentException If the choice is invalid. |
||
587 | * @return string|null Returns the label. Otherwise, returns the raw value. |
||
588 | */ |
||
589 | public function choiceLabel($choice) |
||
590 | { |
||
591 | if ($choice === null) { |
||
592 | return null; |
||
593 | } |
||
594 | |||
595 | View Code Duplication | if (is_array($choice)) { |
|
596 | if (isset($choice['label'])) { |
||
597 | return $choice['label']; |
||
598 | } elseif (isset($choice['value'])) { |
||
599 | return $choice['value']; |
||
600 | } else { |
||
601 | throw new InvalidArgumentException( |
||
602 | 'Choice structure must contain a "label" or "value".' |
||
603 | ); |
||
604 | } |
||
605 | } |
||
606 | |||
607 | $obj = $this->loadObject($choice); |
||
608 | |||
609 | if ($obj === null) { |
||
610 | return $choice; |
||
611 | } |
||
612 | |||
613 | return $this->renderObjPattern($obj); |
||
614 | } |
||
615 | |||
616 | /** |
||
617 | * Inject dependencies from a DI Container. |
||
618 | * |
||
619 | * @param Container $container A dependencies container instance. |
||
620 | * @return void |
||
621 | */ |
||
622 | protected function setDependencies(Container $container) |
||
623 | { |
||
624 | parent::setDependencies($container); |
||
625 | |||
626 | $this->setModelFactory($container['model/factory']); |
||
627 | $this->setCollectionLoader($container['model/collection/loader']); |
||
628 | $this->setCachePool($container['cache']); |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * Retrieve the cache service. |
||
633 | * |
||
634 | * @throws RuntimeException If the cache service was not previously set. |
||
635 | * @return CacheItemPoolInterface |
||
636 | */ |
||
637 | protected function cachePool() |
||
638 | { |
||
639 | if (!isset($this->cachePool)) { |
||
640 | throw new RuntimeException(sprintf( |
||
641 | 'Cache Pool is not defined for "%s"', |
||
642 | get_class($this) |
||
643 | )); |
||
644 | } |
||
645 | |||
646 | return $this->cachePool; |
||
647 | } |
||
648 | |||
649 | /** |
||
650 | * Parse the given objects into choice structures. |
||
651 | * |
||
652 | * @param ModelInterface[]|Traversable $objs One or more objects to format. |
||
653 | * @throws InvalidArgumentException If the collection of objects is not iterable. |
||
654 | * @return array Returns a collection of choice structures. |
||
655 | */ |
||
656 | protected function parseChoices($objs) |
||
657 | { |
||
658 | if (!is_array($objs) && !$objs instanceof Traversable) { |
||
659 | throw new InvalidArgumentException('Must be iterable'); |
||
660 | } |
||
661 | |||
662 | $parsed = []; |
||
663 | View Code Duplication | foreach ($objs as $choice) { |
|
664 | $choice = $this->parseChoice($choice); |
||
665 | if ($choice !== null) { |
||
666 | $choiceIdent = $choice['value']; |
||
667 | $parsed[$choiceIdent] = $choice; |
||
668 | } |
||
669 | } |
||
670 | |||
671 | return $parsed; |
||
672 | } |
||
673 | |||
674 | |||
675 | /** |
||
676 | * Parse the given value into a choice structure. |
||
677 | * |
||
678 | * @param ModelInterface $obj An object to format. |
||
679 | * @return array Returns a choice structure. |
||
680 | */ |
||
681 | protected function parseChoice(ModelInterface $obj) |
||
682 | { |
||
683 | $label = $this->renderObjPattern($obj); |
||
684 | $choice = [ |
||
685 | 'value' => $obj->id(), |
||
686 | 'label' => $label, |
||
687 | 'title' => $label |
||
688 | ]; |
||
689 | |||
690 | /** @todo Move to {@see \Charcoal\Admin\Property\AbstractSelectableInput::choiceObjMap()} */ |
||
691 | if (is_callable([$obj, 'icon'])) { |
||
692 | $choice['icon'] = $obj->icon(); |
||
693 | } |
||
694 | |||
695 | return $choice; |
||
696 | } |
||
697 | |||
698 | /** |
||
699 | * Retrieve the model collection loader. |
||
700 | * |
||
701 | * @throws RuntimeException If the collection loader was not previously set. |
||
702 | * @return CollectionLoader |
||
703 | */ |
||
704 | protected function collectionLoader() |
||
705 | { |
||
706 | if ($this->collectionLoader === null) { |
||
707 | throw new RuntimeException(sprintf( |
||
708 | 'Collection Loader is not defined for "%s"', |
||
709 | get_class($this) |
||
710 | )); |
||
711 | } |
||
712 | |||
713 | return $this->collectionLoader; |
||
714 | } |
||
715 | |||
716 | /** |
||
717 | * Retrieve the prepared model collection loader. |
||
718 | * |
||
719 | * @return CollectionLoader |
||
720 | */ |
||
721 | protected function collectionModelLoader() |
||
722 | { |
||
723 | $loader = $this->collectionLoader(); |
||
724 | |||
725 | if (!$loader->hasModel()) { |
||
726 | $loader->setModel($this->proto()); |
||
727 | |||
728 | $pagination = $this->pagination(); |
||
729 | if (!empty($pagination)) { |
||
730 | $loader->setPagination($pagination); |
||
731 | } |
||
732 | |||
733 | $orders = $this->orders(); |
||
734 | if (!empty($orders)) { |
||
735 | $loader->setOrders($orders); |
||
736 | } |
||
737 | |||
738 | $filters = $this->filters(); |
||
739 | if (!empty($filters)) { |
||
740 | $loader->setFilters($filters); |
||
741 | } |
||
742 | } |
||
743 | |||
744 | return $loader; |
||
745 | } |
||
746 | |||
747 | /** |
||
748 | * Render the given object. |
||
749 | * |
||
750 | * @see Move to \Charcoal\Admin\Property\AbstractSelectableInput::choiceObjMap() |
||
751 | * @param ModelInterface $obj The object or view to render as a label. |
||
752 | * @param string|null $pattern Optional. The render pattern to render. |
||
753 | * @param string|null $lang The language to return the value in. |
||
754 | * @throws InvalidArgumentException If the pattern is not a string. |
||
755 | * @return string |
||
756 | */ |
||
757 | protected function renderObjPattern(ModelInterface $obj, $pattern = null, $lang = null) |
||
758 | { |
||
759 | if ($pattern === null) { |
||
760 | $pattern = $this->getPattern(); |
||
761 | } elseif (!is_string($pattern)) { |
||
762 | throw new InvalidArgumentException( |
||
763 | 'The render pattern must be a string.' |
||
764 | ); |
||
765 | } |
||
766 | |||
767 | if ($pattern === '') { |
||
768 | return ''; |
||
769 | } |
||
770 | |||
771 | View Code Duplication | if ($lang === null) { |
|
772 | $lang = $this->translator()->getLocale(); |
||
773 | } elseif (!is_string($lang)) { |
||
774 | throw new InvalidArgumentException( |
||
775 | 'The language to render as must be a string.' |
||
776 | ); |
||
777 | } |
||
778 | |||
779 | $origLang = $this->translator()->getLocale(); |
||
780 | $this->translator()->setLocale($lang); |
||
781 | |||
782 | if (strpos($pattern, '{{') === false) { |
||
783 | $output = (string)$obj[$pattern]; |
||
784 | } elseif (($obj instanceof ViewableInterface) && $obj->view()) { |
||
785 | $output = $obj->renderTemplate($pattern); |
||
786 | } else { |
||
787 | $callback = function($matches) use ($obj) { |
||
788 | $prop = trim($matches[1]); |
||
789 | return (string)$obj[$prop]; |
||
790 | }; |
||
791 | |||
792 | $output = preg_replace_callback('~\{\{\s*(.*?)\s*\}\}~i', $callback, $pattern); |
||
793 | } |
||
794 | |||
795 | $this->translator()->setLocale($origLang); |
||
796 | |||
797 | return $output; |
||
798 | } |
||
799 | |||
800 | /** |
||
801 | * Retrieve an object by its ID. |
||
802 | * |
||
803 | * Loads the object from the cache store or from the storage source. |
||
804 | * |
||
805 | * @param mixed $objId Object id. |
||
806 | * @return ModelInterface |
||
807 | */ |
||
808 | protected function loadObject($objId) |
||
809 | { |
||
810 | if ($objId instanceof ModelInterface) { |
||
811 | return $objId; |
||
812 | } |
||
813 | |||
814 | $obj = $this->modelLoader()->load($objId); |
||
815 | if (!$obj->id()) { |
||
816 | return null; |
||
817 | } else { |
||
818 | return $obj; |
||
819 | } |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Retrieve the model loader. |
||
824 | * |
||
825 | * @param string $objType The object type. |
||
826 | * @throws InvalidArgumentException If the object type is invalid. |
||
827 | * @return ModelLoader |
||
828 | */ |
||
829 | protected function modelLoader($objType = null) |
||
830 | { |
||
831 | if ($objType === null) { |
||
832 | $objType = $this->getObjType(); |
||
833 | } elseif (!is_string($objType)) { |
||
834 | throw new InvalidArgumentException( |
||
835 | 'Object type must be a string.' |
||
836 | ); |
||
837 | } |
||
838 | |||
839 | if (isset(self::$modelLoaders[$objType])) { |
||
840 | return self::$modelLoaders[$objType]; |
||
841 | } |
||
842 | |||
843 | self::$modelLoaders[$objType] = new ModelLoader([ |
||
844 | 'logger' => $this->logger, |
||
845 | 'obj_type' => $objType, |
||
846 | 'factory' => $this->modelFactory(), |
||
847 | 'cache' => $this->cachePool() |
||
848 | ]); |
||
849 | |||
850 | return self::$modelLoaders[$objType]; |
||
851 | } |
||
852 | |||
853 | /** |
||
854 | * Retrieve the object model factory. |
||
855 | * |
||
856 | * @throws RuntimeException If the model factory was not previously set. |
||
857 | * @return FactoryInterface |
||
858 | */ |
||
859 | protected function modelFactory() |
||
860 | { |
||
861 | if (!isset($this->modelFactory)) { |
||
862 | throw new RuntimeException(sprintf( |
||
863 | 'Model Factory is not defined for "%s"', |
||
864 | get_class($this) |
||
865 | )); |
||
866 | } |
||
867 | |||
868 | return $this->modelFactory; |
||
869 | } |
||
870 | |||
871 | /** |
||
872 | * Set an object model factory. |
||
873 | * |
||
874 | * @param FactoryInterface $factory The model factory, to create objects. |
||
875 | * @return void |
||
876 | */ |
||
877 | private function setModelFactory(FactoryInterface $factory) |
||
878 | { |
||
879 | $this->modelFactory = $factory; |
||
880 | } |
||
881 | |||
882 | /** |
||
883 | * Set a model collection loader. |
||
884 | * |
||
885 | * @param CollectionLoader $loader The collection loader. |
||
886 | * @return void |
||
887 | */ |
||
888 | private function setCollectionLoader(CollectionLoader $loader) |
||
889 | { |
||
890 | $this->collectionLoader = $loader; |
||
891 | } |
||
892 | |||
893 | /** |
||
894 | * Set the cache service. |
||
895 | * |
||
896 | * @param CacheItemPoolInterface $cache A PSR-6 compliant cache pool instance. |
||
897 | * @return void |
||
898 | */ |
||
899 | private function setCachePool(CacheItemPoolInterface $cache) |
||
900 | { |
||
901 | $this->cachePool = $cache; |
||
902 | } |
||
903 | } |
||
904 |
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.