1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
namespace TYPO3Fluid\Fluid\Core\ViewHelper; |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file belongs to the package "TYPO3 Fluid". |
7
|
|
|
* See LICENSE.txt that was shipped with this package. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
use Closure; |
11
|
|
|
use TYPO3Fluid\Fluid\Component\AbstractComponent; |
12
|
|
|
use TYPO3Fluid\Fluid\Component\Argument\ArgumentCollection; |
13
|
|
|
use TYPO3Fluid\Fluid\Component\Argument\ArgumentDefinition; |
14
|
|
|
use TYPO3Fluid\Fluid\Component\ComponentInterface; |
15
|
|
|
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* The abstract base class for all view helpers. |
19
|
|
|
*/ |
20
|
|
|
abstract class AbstractViewHelper extends AbstractComponent |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* @var RenderingContextInterface |
24
|
|
|
*/ |
25
|
|
|
protected $renderingContext; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var Closure |
29
|
|
|
*/ |
30
|
|
|
protected $renderChildrenClosure = null; |
31
|
|
|
|
32
|
|
|
protected $escapeOutput = true; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Execute via Component API implementation. |
36
|
|
|
* |
37
|
|
|
* @param RenderingContextInterface $renderingContext |
38
|
|
|
* @return mixed |
39
|
|
|
*/ |
40
|
|
|
public function evaluate(RenderingContextInterface $renderingContext) |
41
|
|
|
{ |
42
|
|
|
$this->renderingContext = $renderingContext; |
43
|
|
|
$this->getArguments()->setRenderingContext($renderingContext); |
44
|
|
|
return $this->callRenderMethod(); |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
public function onOpen(RenderingContextInterface $renderingContext): ComponentInterface |
48
|
|
|
{ |
49
|
|
|
$this->getArguments()->setRenderingContext($renderingContext); |
50
|
|
|
return parent::onOpen($renderingContext); |
|
|
|
|
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function getArguments(): ArgumentCollection |
54
|
|
|
{ |
55
|
|
|
if ($this->arguments === null) { |
56
|
|
|
$this->arguments = new ArgumentCollection(); |
57
|
|
|
$this->initializeArguments(); |
58
|
|
|
} |
59
|
|
|
return $this->arguments; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Register a new argument. Call this method from your ViewHelper subclass |
64
|
|
|
* inside the initializeArguments() method. |
65
|
|
|
* |
66
|
|
|
* @param string $name Name of the argument |
67
|
|
|
* @param string $type Type of the argument |
68
|
|
|
* @param string $description Description of the argument |
69
|
|
|
* @param boolean $required If TRUE, argument is required. Defaults to FALSE. |
70
|
|
|
* @param mixed $defaultValue Default value of argument |
71
|
|
|
* @return AbstractViewHelper $this, to allow chaining. |
72
|
|
|
* @throws Exception |
73
|
|
|
*/ |
74
|
|
|
protected function registerArgument(string $name, string $type, string $description, bool $required = false, $defaultValue = null): self |
75
|
|
|
{ |
76
|
|
|
$this->getArguments()->addDefinition(new ArgumentDefinition($name, $type, $description, $required, $defaultValue)); |
77
|
|
|
return $this; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Overrides a registered argument. Call this method from your ViewHelper subclass |
82
|
|
|
* inside the initializeArguments() method if you want to override a previously registered argument. |
83
|
|
|
* |
84
|
|
|
* @param string $name Name of the argument |
85
|
|
|
* @param string $type Type of the argument |
86
|
|
|
* @param string $description Description of the argument |
87
|
|
|
* @param boolean $required If TRUE, argument is required. Defaults to FALSE. |
88
|
|
|
* @param mixed $defaultValue Default value of argument |
89
|
|
|
* @return AbstractViewHelper $this, to allow chaining. |
90
|
|
|
* @throws Exception |
91
|
|
|
* @see registerArgument() |
92
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
93
|
|
|
*/ |
94
|
|
|
protected function overrideArgument(string $name, string $type, string $description, bool $required = false, $defaultValue = null): self |
95
|
|
|
{ |
96
|
|
|
$this->getArguments()->addDefinition(new ArgumentDefinition($name, $type, $description, $required, $defaultValue)); |
97
|
|
|
return $this; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Called when being inside a cached template. |
102
|
|
|
* |
103
|
|
|
* @param Closure $renderChildrenClosure |
104
|
|
|
* @return void |
105
|
|
|
*/ |
106
|
|
|
public function setRenderChildrenClosure(Closure $renderChildrenClosure) |
107
|
|
|
{ |
108
|
|
|
$this->renderChildrenClosure = $renderChildrenClosure; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Call the render() method and handle errors. |
113
|
|
|
* |
114
|
|
|
* @return mixed the rendered ViewHelper |
115
|
|
|
* @throws Exception |
116
|
|
|
*/ |
117
|
|
|
protected function callRenderMethod() |
118
|
|
|
{ |
119
|
|
|
if (method_exists($this, 'render')) { |
120
|
|
|
return call_user_func([$this, 'render']); |
121
|
|
|
} |
122
|
|
|
if (method_exists($this, 'renderStatic')) { |
123
|
|
|
// Method is safe to call - will not recurse through ViewHelperInvoker via the default |
124
|
|
|
// implementation of renderStatic() on this class. |
125
|
|
|
return call_user_func_array([static::class, 'renderStatic'], [$this->arguments->getArrayCopy(), $this->buildRenderChildrenClosure(), $this->arguments->getRenderingContext()]); |
126
|
|
|
} |
127
|
|
|
return $this->renderChildren(); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Helper method which triggers the rendering of everything between the |
132
|
|
|
* opening and the closing tag. |
133
|
|
|
* |
134
|
|
|
* @return mixed The finally rendered child nodes. |
135
|
|
|
*/ |
136
|
|
|
protected function renderChildren() |
137
|
|
|
{ |
138
|
|
|
if ($this->renderChildrenClosure !== null) { |
139
|
|
|
$closure = $this->renderChildrenClosure; |
140
|
|
|
return $closure(); |
141
|
|
|
} |
142
|
|
|
return $this->evaluateChildren($this->renderingContext); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Helper which is mostly needed when calling renderStatic() from within |
147
|
|
|
* render(). |
148
|
|
|
* |
149
|
|
|
* No public API yet. |
150
|
|
|
* |
151
|
|
|
* @return Closure |
152
|
|
|
*/ |
153
|
|
|
protected function buildRenderChildrenClosure() |
154
|
|
|
{ |
155
|
|
|
$self = clone $this; |
156
|
|
|
$renderChildrenClosure = function () use ($self) { |
157
|
|
|
return $self->renderChildren(); |
158
|
|
|
}; |
159
|
|
|
return $renderChildrenClosure; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Initialize all arguments. You need to override this method and call |
164
|
|
|
* $this->registerArgument(...) inside this method, to register all your arguments. |
165
|
|
|
* |
166
|
|
|
* @return void |
167
|
|
|
*/ |
168
|
|
|
protected function initializeArguments() |
169
|
|
|
{ |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
public function allowUndeclaredArgument(string $argumentName): bool |
173
|
|
|
{ |
174
|
|
|
return false; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
protected function hasArgument(string $argumentName): bool |
178
|
|
|
{ |
179
|
|
|
return $this->getArguments()->getRaw($argumentName) !== null; |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.