1 | <?php declare(strict_types = 1); |
||||
2 | namespace Templado\Engine; |
||||
3 | |||||
4 | use DOMAttr; |
||||
5 | use DOMDocumentFragment; |
||||
6 | use DOMElement; |
||||
7 | use DOMNode; |
||||
8 | |||||
9 | class ViewModelRenderer { |
||||
10 | |||||
11 | /** @var array */ |
||||
12 | private $stack = []; |
||||
13 | |||||
14 | /** @var string[] */ |
||||
15 | private $stackNames = []; |
||||
16 | |||||
17 | /** @var SnapshotDOMNodelist[] */ |
||||
18 | private $listStack = []; |
||||
19 | |||||
20 | /** @var object */ |
||||
21 | private $resourceModel; |
||||
22 | |||||
23 | /** @var array */ |
||||
24 | private $prefixes = []; |
||||
25 | |||||
26 | /** |
||||
27 | * @throws ViewModelRendererException |
||||
28 | */ |
||||
29 | 102 | public function render(DOMNode $context, object $model): void { |
|||
30 | 102 | $this->resourceModel = $model; |
|||
31 | 102 | $this->stack = [$model]; |
|||
32 | 102 | $this->stackNames = []; |
|||
33 | 102 | $this->listStack = []; |
|||
34 | 102 | $this->prefixes = []; |
|||
35 | 102 | $this->walk($context); |
|||
36 | 54 | } |
|||
37 | |||||
38 | /** |
||||
39 | * @throws ViewModelRendererException |
||||
40 | */ |
||||
41 | 102 | private function walk(DOMNode $context): void { |
|||
42 | 102 | if (!$context instanceof DOMElement) { |
|||
43 | 48 | return; |
|||
44 | } |
||||
45 | |||||
46 | 102 | $stackAdded = 0; |
|||
47 | |||||
48 | 102 | if ($context->hasAttribute('prefix')) { |
|||
49 | 18 | $this->resolvePrefixDefinition($context->getAttribute('prefix')); |
|||
50 | } |
||||
51 | |||||
52 | 96 | if ($context->hasAttribute('resource')) { |
|||
53 | 9 | $this->addResourceToStack($context); |
|||
54 | 6 | $stackAdded++; |
|||
55 | } |
||||
56 | |||||
57 | 93 | if ($context->hasAttribute('property')) { |
|||
58 | 93 | $this->addToStack($context); |
|||
59 | 87 | $stackAdded++; |
|||
60 | 87 | $context = $this->applyCurrent($context); |
|||
61 | } |
||||
62 | |||||
63 | 60 | if ($context->hasChildNodes()) { |
|||
64 | 54 | $list = new SnapshotDOMNodelist($context->childNodes); |
|||
65 | 54 | $this->listStack[] = $list; |
|||
66 | |||||
67 | 54 | while ($list->hasNext()) { |
|||
68 | 54 | $childNode = $list->getNext(); |
|||
69 | /* @var \DOMNode $childNode */ |
||||
70 | 54 | $this->walk($childNode); |
|||
71 | } |
||||
72 | 48 | \array_pop($this->listStack); |
|||
73 | } |
||||
74 | |||||
75 | 54 | while ($stackAdded > 0) { |
|||
76 | 54 | $this->dropFromStack(); |
|||
77 | 54 | $stackAdded--; |
|||
78 | } |
||||
79 | 54 | } |
|||
80 | |||||
81 | /** |
||||
82 | * @throws ViewModelRendererException |
||||
83 | */ |
||||
84 | 93 | private function addToStack(DOMElement $context): void { |
|||
85 | 93 | $model = $this->current(); |
|||
86 | 93 | $property = $context->getAttribute('property'); |
|||
87 | |||||
88 | 93 | if (\substr_count($property, ':') === 1) { |
|||
89 | 15 | [$prefix, $property] = \explode(':', $property); |
|||
90 | |||||
91 | 15 | if (!\array_key_exists($prefix, $this->prefixes)) { |
|||
92 | 3 | throw new ViewModelRendererException(\sprintf('Undefined prefix %s', $prefix)); |
|||
93 | } |
||||
94 | |||||
95 | 12 | $model = $this->prefixes[$prefix]; |
|||
96 | |||||
97 | 12 | if ($model === null) { |
|||
98 | 3 | return; |
|||
99 | } |
||||
100 | } |
||||
101 | |||||
102 | 87 | $this->ensureIsObject($model, $property); |
|||
103 | |||||
104 | 87 | $this->stackNames[] = $property; |
|||
105 | |||||
106 | 87 | foreach ([$property, 'get' . \ucfirst($property)] as $method) { |
|||
107 | 87 | if (\method_exists($model, $method)) { |
|||
108 | 78 | $this->stack[] = $model->{$method}($context->nodeValue); |
|||
109 | |||||
110 | 81 | return; |
|||
111 | } |
||||
112 | } |
||||
113 | |||||
114 | 15 | if (\method_exists($model, '__call')) { |
|||
115 | 12 | $this->stack[] = $model->{$property}($context->nodeValue); |
|||
116 | |||||
117 | 12 | return; |
|||
118 | } |
||||
119 | |||||
120 | 3 | throw new ViewModelRendererException( |
|||
121 | 3 | \sprintf('Viewmodel method missing: $model->%s', \implode('()->', $this->stackNames) . '()') |
|||
122 | ); |
||||
123 | } |
||||
124 | |||||
125 | 93 | private function current() { |
|||
126 | 93 | return \end($this->stack); |
|||
127 | } |
||||
128 | |||||
129 | 54 | private function dropFromStack(): void { |
|||
130 | 54 | \array_pop($this->stack); |
|||
131 | 54 | \array_pop($this->stackNames); |
|||
132 | 54 | } |
|||
133 | |||||
134 | /** |
||||
135 | * @throws ViewModelRendererException |
||||
136 | */ |
||||
137 | 87 | private function applyCurrent(DOMElement $context): DOMNode { |
|||
138 | /** @psalm-suppress MixedAssignment */ |
||||
139 | 87 | $model = $this->current(); |
|||
140 | |||||
141 | 87 | switch (\gettype($model)) { |
|||
142 | 87 | case 'boolean': { |
|||
143 | /** @var bool $model */ |
||||
144 | 9 | return $this->processBoolean($context, $model); |
|||
145 | } |
||||
146 | 81 | case 'string': { |
|||
147 | /** @var string $model */ |
||||
148 | 39 | $this->processString($context, $model); |
|||
149 | |||||
150 | 39 | return $context; |
|||
151 | } |
||||
152 | 75 | case 'object': { |
|||
153 | /** @var object $model */ |
||||
154 | 63 | return $this->processObject($context, $model); |
|||
155 | } |
||||
156 | |||||
157 | 39 | case 'array': { |
|||
158 | 36 | return $this->processArray($context, $model); |
|||
159 | } |
||||
160 | |||||
161 | default: { |
||||
162 | 3 | throw new ViewModelRendererException( |
|||
163 | 3 | \sprintf( |
|||
164 | 3 | 'Value returned by $model->%s must not be of type %s', |
|||
165 | 3 | \implode('()->', $this->stackNames) . '()', |
|||
166 | 3 | \gettype($model) |
|||
167 | ) |
||||
168 | ); |
||||
169 | } |
||||
170 | } |
||||
171 | } |
||||
172 | |||||
173 | /** |
||||
174 | * @throws ViewModelRendererException |
||||
175 | * |
||||
176 | * @return DOMDocumentFragment|DOMElement |
||||
177 | */ |
||||
178 | 12 | private function processBoolean(DOMElement $context, bool $model) { |
|||
179 | 12 | if ($model === true) { |
|||
180 | 6 | return $context; |
|||
181 | } |
||||
182 | |||||
183 | 6 | if ($context->isSameNode($context->ownerDocument->documentElement)) { |
|||
184 | 3 | throw new ViewModelRendererException('Cannot remove root element'); |
|||
185 | } |
||||
186 | |||||
187 | 3 | $this->removeNodeFromCurrentSnapshotList($context); |
|||
188 | 3 | $context->parentNode->removeChild($context); |
|||
189 | |||||
190 | 3 | return $context->ownerDocument->createDocumentFragment(); |
|||
191 | } |
||||
192 | |||||
193 | 39 | private function processString(DOMElement $context, string $model): void { |
|||
194 | 39 | $context->nodeValue = ''; |
|||
195 | 39 | $context->textContent = $model; |
|||
196 | 39 | } |
|||
197 | |||||
198 | /** |
||||
199 | * @throws ViewModelRendererException |
||||
200 | * |
||||
201 | * @return \DOMDocumentFragment|\DOMElement |
||||
202 | */ |
||||
203 | 63 | private function processObject(DOMElement $context, object $model) { |
|||
204 | 63 | if (\is_iterable($model)) { |
|||
205 | /** @var iterable $model */ |
||||
206 | 6 | return $this->processArray($context, $model); |
|||
207 | } |
||||
208 | |||||
209 | 57 | return $this->processObjectAsModel($context, $model); |
|||
210 | } |
||||
211 | |||||
212 | /** |
||||
213 | * @throws ViewModelRendererException |
||||
214 | */ |
||||
215 | 57 | private function processObjectAsModel(DOMElement $context, object $model): DOMElement { |
|||
216 | 57 | $container = $this->moveToContainer($context, false); |
|||
217 | 57 | $workContext = $this->selectMatchingWorkContext($container->firstChild, $model); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
218 | |||||
219 | 48 | if (\method_exists($model, 'asString') || |
|||
220 | 48 | \method_exists($model, '__call') |
|||
221 | ) { |
||||
222 | /** @psalm-suppress MixedAssignment */ |
||||
223 | 30 | $value = $model->asString($workContext->nodeValue); |
|||
224 | |||||
225 | 30 | if (!\is_null($value) && !\is_string($value)) { |
|||
226 | 3 | throw new ViewModelRendererException( |
|||
227 | 3 | \sprintf( |
|||
228 | 3 | "Method \$model->%s must return 'null' or 'string', got '%s'", |
|||
229 | 3 | \implode('()->', $this->stackNames) . '()->asString()', |
|||
230 | 3 | \gettype($value) |
|||
231 | ) |
||||
232 | ); |
||||
233 | } |
||||
234 | |||||
235 | /** @psalm-var null|string $value */ |
||||
236 | 27 | if ($value !== null) { |
|||
237 | 27 | $workContext->nodeValue = ''; |
|||
238 | 27 | $workContext->textContent = $value; |
|||
239 | } |
||||
240 | } |
||||
241 | |||||
242 | 45 | foreach (new SnapshotAttributeList($workContext->attributes) as $attribute) { |
|||
0 ignored issues
–
show
It seems like
$workContext->attributes can also be of type null ; however, parameter $map of Templado\Engine\Snapshot...buteList::__construct() does only seem to accept DOMNamedNodeMap , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
243 | 45 | $this->processAttribute($attribute, $model); |
|||
244 | } |
||||
245 | |||||
246 | 42 | $container->parentNode->insertBefore($workContext, $container); |
|||
247 | 42 | $container->parentNode->removeChild($container); |
|||
248 | |||||
249 | 42 | return $workContext; |
|||
250 | } |
||||
251 | |||||
252 | /** |
||||
253 | * @throws ViewModelRendererException |
||||
254 | */ |
||||
255 | 42 | private function processArray(DOMElement $context, iterable $model): DOMDocumentFragment { |
|||
256 | 42 | $count = $this->getElementCount($model); |
|||
257 | |||||
258 | 39 | if ($count > 1 && $context->isSameNode($context->ownerDocument->documentElement)) { |
|||
259 | 3 | throw new ViewModelRendererException( |
|||
260 | 3 | 'Cannot render multiple copies of root element' |
|||
261 | ); |
||||
262 | } |
||||
263 | |||||
264 | 36 | if ($count === 0) { |
|||
265 | 3 | return $this->processBoolean($context, false); |
|||
266 | } |
||||
267 | |||||
268 | 33 | $container = $this->moveToContainer($context, true); |
|||
269 | |||||
270 | /** |
||||
271 | * @psalm-suppress MixedAssignment |
||||
272 | * @psalm-var int $pos |
||||
273 | */ |
||||
274 | 33 | foreach ($model as $pos => $entry) { |
|||
275 | 33 | $subcontext = $container->cloneNode(true); |
|||
276 | 33 | $container->parentNode->insertBefore($subcontext, $container); |
|||
277 | |||||
278 | 33 | $result = $this->processArrayEntry($subcontext->firstChild, $entry, $pos); |
|||
0 ignored issues
–
show
It seems like
$subcontext->firstChild can also be of type null ; however, parameter $context of Templado\Engine\ViewMode...er::processArrayEntry() does only seem to accept DOMElement , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
279 | |||||
280 | 30 | $container->parentNode->insertBefore($result, $subcontext); |
|||
281 | 30 | $container->parentNode->removeChild($subcontext); |
|||
282 | } |
||||
283 | |||||
284 | 30 | $fragment = $container->ownerDocument->createDocumentFragment(); |
|||
285 | 30 | $container->parentNode->removeChild($container); |
|||
286 | |||||
287 | 30 | return $fragment; |
|||
288 | } |
||||
289 | |||||
290 | /** |
||||
291 | * @throws ViewModelRendererException |
||||
292 | * @psalm-suppress MissingParamType |
||||
293 | */ |
||||
294 | 33 | private function processArrayEntry(DOMElement $context, $entry, int $pos): DOMElement { |
|||
295 | 33 | $workContext = $this->selectMatchingWorkContext($context, $entry); |
|||
296 | /* @var DOMElement $clone */ |
||||
297 | 30 | $this->stack[] = $entry; |
|||
298 | 30 | $this->stackNames[] = (string)$pos; |
|||
299 | |||||
300 | 30 | $this->applyCurrent($workContext); |
|||
301 | |||||
302 | 30 | if ($workContext->hasChildNodes()) { |
|||
303 | 30 | $list = new SnapshotDOMNodelist($workContext->childNodes); |
|||
304 | 30 | $this->listStack[] = $list; |
|||
305 | |||||
306 | 30 | while ($list->hasNext()) { |
|||
307 | 30 | $this->walk($list->getNext()); |
|||
308 | } |
||||
309 | 30 | \array_pop($this->listStack); |
|||
310 | } |
||||
311 | 30 | $this->dropFromStack(); |
|||
312 | |||||
313 | 30 | return $workContext; |
|||
314 | } |
||||
315 | |||||
316 | /** |
||||
317 | * @throws ViewModelRendererException |
||||
318 | */ |
||||
319 | 45 | private function processAttribute(DOMAttr $attribute, object $model): void { |
|||
320 | 45 | $attributeName = $attribute->nodeName; |
|||
321 | |||||
322 | 45 | if (\strpos($attributeName, '-') !== false) { |
|||
323 | 3 | $parts = \explode('-', $attributeName); |
|||
324 | 3 | \array_walk( |
|||
325 | 3 | $parts, |
|||
326 | static function (string &$value, int $pos): void { |
||||
327 | 3 | $value = \ucfirst($value); |
|||
328 | 3 | } |
|||
329 | ); |
||||
330 | 3 | $attributeName = \implode('', $parts); |
|||
331 | } |
||||
332 | |||||
333 | 45 | foreach ([$attributeName, 'get' . \ucfirst($attributeName), '__call'] as $method) { |
|||
334 | 45 | if (!\method_exists($model, $method)) { |
|||
335 | 45 | continue; |
|||
336 | } |
||||
337 | |||||
338 | 39 | if ($method === '__call') { |
|||
339 | 3 | $method = $attribute->name; |
|||
340 | } |
||||
341 | |||||
342 | /** @psalm-var null|bool|string $value */ |
||||
343 | 39 | $value = $model->{$method}($attribute->value); |
|||
344 | |||||
345 | 39 | if ($value === null) { |
|||
346 | 3 | return; |
|||
347 | } |
||||
348 | |||||
349 | /** @var DOMElement $parent */ |
||||
350 | 39 | $parent = $attribute->parentNode; |
|||
351 | |||||
352 | 39 | if ($value === false) { |
|||
353 | 18 | $parent->removeAttribute($attribute->name); |
|||
354 | |||||
355 | 18 | return; |
|||
356 | } |
||||
357 | |||||
358 | 39 | if (!\is_string($value)) { |
|||
359 | 3 | throw new ViewModelRendererException( |
|||
360 | 3 | \sprintf( |
|||
361 | 3 | 'Attribute value must be string or boolean false - type %s received from $model->%s', |
|||
362 | 3 | \gettype($value), |
|||
363 | 3 | \implode('()->', $this->stackNames) . '()' |
|||
364 | ) |
||||
365 | ); |
||||
366 | } |
||||
367 | |||||
368 | 36 | $parent->setAttribute($attribute->name, $value); |
|||
369 | |||||
370 | 36 | return; |
|||
371 | } |
||||
372 | 42 | } |
|||
373 | |||||
374 | /** |
||||
375 | * @throws ViewModelRendererException |
||||
376 | * @psalm-assert object $mode |
||||
377 | * @psalm-suppress MissingParamType |
||||
378 | */ |
||||
379 | 87 | private function ensureIsObject($model, string $property): void { |
|||
380 | 87 | if (!\is_object($model)) { |
|||
381 | 3 | throw new ViewModelRendererException( |
|||
382 | 3 | \sprintf( |
|||
383 | 3 | 'Trying to add "%s" failed - Non object (%s) on stack: $%s', |
|||
384 | 3 | $property, |
|||
385 | 3 | \gettype($model), |
|||
386 | 3 | \implode('()->', $this->stackNames) . '() ' |
|||
387 | ) |
||||
388 | ); |
||||
389 | } |
||||
390 | 87 | } |
|||
391 | |||||
392 | /** |
||||
393 | * @throws ViewModelRendererException |
||||
394 | * @psalm-suppress MissingParamType |
||||
395 | */ |
||||
396 | 63 | private function selectMatchingWorkContext(DOMElement $context, $entry): DOMElement { |
|||
397 | 63 | if (!$context->hasAttribute('typeof')) { |
|||
398 | 42 | return $context; |
|||
399 | } |
||||
400 | |||||
401 | 24 | if (!\is_object($entry)) { |
|||
402 | 3 | throw new ViewModelRendererException( |
|||
403 | 3 | \sprintf( |
|||
404 | 3 | "Cannot call 'typeOf' on none object type '%s' returned from \$model->%s()", |
|||
405 | 3 | \gettype($entry), |
|||
406 | 3 | \implode('()->', $this->stackNames) |
|||
407 | ) |
||||
408 | ); |
||||
409 | } |
||||
410 | |||||
411 | 21 | if (!\method_exists($entry, 'typeOf')) { |
|||
412 | 3 | throw new ViewModelRendererException( |
|||
413 | 3 | \sprintf( |
|||
414 | 3 | "No 'typeOf' method in model returned from \$model->%s() but current context is conditional", |
|||
415 | 3 | \implode('()->', $this->stackNames) |
|||
416 | ) |
||||
417 | ); |
||||
418 | } |
||||
419 | |||||
420 | /** @psalm-suppress MixedAssignment */ |
||||
421 | 18 | $requestedTypeOf = $entry->typeOf(); |
|||
422 | |||||
423 | 18 | if (!\is_string($requestedTypeOf)) { |
|||
424 | 3 | throw new ViewModelRendererException( |
|||
425 | 3 | \sprintf( |
|||
426 | 3 | "Return value of \$model->%s()->typeOf() must be string, got '%s'", |
|||
427 | 3 | \implode('()->', $this->stackNames), |
|||
428 | 3 | \gettype($entry) |
|||
429 | ) |
||||
430 | ); |
||||
431 | } |
||||
432 | |||||
433 | 15 | if ($context->getAttribute('typeof') === $requestedTypeOf) { |
|||
434 | 6 | return $context; |
|||
435 | } |
||||
436 | |||||
437 | 15 | $xp = new \DOMXPath($context->ownerDocument); |
|||
0 ignored issues
–
show
It seems like
$context->ownerDocument can also be of type null ; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
438 | 15 | $list = $xp->query( |
|||
439 | 15 | \sprintf( |
|||
440 | 15 | '(following-sibling::*)[@property="%s" and @typeof="%s"]', |
|||
441 | 15 | $context->getAttribute('property'), |
|||
442 | 15 | $requestedTypeOf |
|||
443 | ), |
||||
444 | 5 | $context |
|||
445 | ); |
||||
446 | |||||
447 | 15 | $newContext = $list->item(0); |
|||
448 | |||||
449 | 15 | if (!$newContext instanceof DOMElement) { |
|||
450 | 3 | throw new ViewModelRendererException( |
|||
451 | 3 | \sprintf( |
|||
452 | 3 | "Context for type '%s' not found.", |
|||
453 | 3 | $requestedTypeOf |
|||
454 | ) |
||||
455 | ); |
||||
456 | } |
||||
457 | |||||
458 | 12 | return $newContext; |
|||
459 | } |
||||
460 | |||||
461 | 63 | private function moveToContainer(DOMElement $context, bool $greedy = true): DOMElement { |
|||
462 | 63 | $container = $context->ownerDocument->createElement('container'); |
|||
463 | 63 | $context->parentNode->insertBefore($container, $context); |
|||
464 | |||||
465 | 63 | if (!$greedy && !$context->hasAttribute('typeof')) { |
|||
466 | 63 | $container->appendChild($context); |
|||
467 | 63 | $this->removeNodeFromCurrentSnapshotList($context); |
|||
468 | 63 | ||||
469 | return $container; |
||||
470 | } |
||||
471 | 63 | ||||
472 | 63 | $xp = new \DOMXPath($container->ownerDocument); |
|||
0 ignored issues
–
show
It seems like
$container->ownerDocument can also be of type null ; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
473 | 63 | $list = $xp->query( |
|||
474 | \sprintf('*[@property="%s"]', $context->getAttribute('property')), |
||||
475 | $context->parentNode |
||||
476 | 63 | ); |
|||
477 | |||||
478 | foreach ($list as $node) { |
||||
479 | 63 | $container->appendChild($node); |
|||
480 | 63 | $this->removeNodeFromCurrentSnapshotList($node); |
|||
481 | } |
||||
482 | 63 | ||||
483 | 51 | return $container; |
|||
484 | } |
||||
485 | 39 | ||||
486 | 39 | private function removeNodeFromCurrentSnapshotList(DOMElement $context): void { |
|||
487 | $stackList = \end($this->listStack); |
||||
488 | 42 | ||||
489 | 42 | if ((!$stackList instanceof SnapshotDOMNodelist) || !$stackList->hasNode($context)) { |
|||
490 | 39 | return; |
|||
491 | } |
||||
492 | $stackList->removeNode($context); |
||||
493 | 3 | } |
|||
494 | 3 | ||||
495 | 3 | private function getElementCount(iterable $model): int { |
|||
496 | 3 | if (\is_array($model) || $model instanceof \Countable) { |
|||
497 | return \count($model); |
||||
498 | } |
||||
499 | |||||
500 | throw new ViewModelRendererException( |
||||
501 | 9 | \sprintf( |
|||
502 | 9 | 'Class %s must implement \Countable to be used as array', |
|||
503 | \get_class($model) |
||||
0 ignored issues
–
show
$model of type iterable is incompatible with the type object expected by parameter $object of get_class() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
504 | 9 | ) |
|||
505 | ); |
||||
506 | 9 | } |
|||
507 | 9 | ||||
508 | 3 | private function addResourceToStack(DOMElement $context): void { |
|||
509 | $resource = $context->getAttribute('resource'); |
||||
510 | 5 | ||||
511 | $this->stackNames[] = $resource; |
||||
512 | |||||
513 | foreach ([$resource, 'get' . \ucfirst($resource)] as $method) { |
||||
514 | 6 | if (\method_exists($this->resourceModel, $method)) { |
|||
515 | 3 | $this->stack[] = $this->resourceModel->{$method}(); |
|||
516 | |||||
517 | 3 | return; |
|||
518 | } |
||||
519 | } |
||||
520 | 3 | ||||
521 | 3 | if (\method_exists($this->resourceModel, '__call')) { |
|||
522 | $this->stack[] = $this->resourceModel->{$resource}(); |
||||
523 | |||||
524 | return; |
||||
525 | 18 | } |
|||
526 | 18 | ||||
527 | throw new ViewModelRendererException( |
||||
528 | 18 | \sprintf('Resource Viewmodel method missing: $model->%s', \implode('()->', $this->stackNames) . '()') |
|||
529 | 3 | ); |
|||
530 | 3 | } |
|||
531 | |||||
532 | private function resolvePrefixDefinition(string $prefixDefinition): void { |
||||
533 | $parts = \explode(' ', $prefixDefinition); |
||||
534 | 15 | ||||
535 | 15 | if (\count($parts) !== 2) { |
|||
536 | throw new ViewModelRendererException( |
||||
537 | 15 | \sprintf('Invalid prefix definition "%s" - must be of format "prefix resourcename"', $prefixDefinition) |
|||
538 | 3 | ); |
|||
539 | } |
||||
540 | 3 | ||||
541 | [$prefix, $resource] = $parts; |
||||
542 | $prefix = \rtrim($prefix, ':'); |
||||
543 | 12 | ||||
544 | 12 | if (\strpos($resource, ':') !== false) { |
|||
545 | 6 | $this->prefixes[$prefix] = null; |
|||
546 | |||||
547 | 8 | return; |
|||
548 | } |
||||
549 | |||||
550 | foreach ([$resource, 'get' . \ucfirst($resource)] as $method) { |
||||
551 | 6 | if (\method_exists($this->resourceModel, $method)) { |
|||
552 | 3 | $this->prefixes[$prefix] = $this->resourceModel->{$method}(); |
|||
553 | |||||
554 | 3 | return; |
|||
555 | } |
||||
556 | } |
||||
557 | 3 | ||||
558 | 3 | if (\method_exists($this->resourceModel, '__call')) { |
|||
559 | $this->prefixes[$prefix] = $this->resourceModel->{$resource}(); |
||||
560 | |||||
561 | return; |
||||
562 | } |
||||
563 | |||||
564 | throw new ViewModelRendererException( |
||||
565 | \sprintf('No method %s to resolve prefix %s', $resource, $prefix) |
||||
566 | ); |
||||
567 | } |
||||
568 | } |
||||
569 |