1
|
|
|
<?php |
2
|
|
|
namespace TYPO3Fluid\Fluid\ViewHelpers; |
3
|
|
|
|
4
|
|
|
/* |
5
|
|
|
* This file belongs to the package "TYPO3 Fluid". |
6
|
|
|
* See LICENSE.txt that was shipped with this package. |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler; |
10
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\NodeFilterInterface; |
11
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\NodeInterface; |
12
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode; |
13
|
|
|
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Switch view helper which can be used to render content depending on a value or expression. |
17
|
|
|
* Implements what a basic switch()-PHP-method does. |
18
|
|
|
* |
19
|
|
|
* An optional default case can be specified which is rendered if none of the "f:case" conditions matches. |
20
|
|
|
* |
21
|
|
|
* = Examples = |
22
|
|
|
* |
23
|
|
|
* <code title="Simple Switch statement"> |
24
|
|
|
* <f:switch expression="{person.gender}"> |
25
|
|
|
* <f:case value="male">Mr.</f:case> |
26
|
|
|
* <f:case value="female">Mrs.</f:case> |
27
|
|
|
* <f:defaultCase>Mr. / Mrs.</f:defaultCase> |
28
|
|
|
* </f:switch> |
29
|
|
|
* </code> |
30
|
|
|
* <output> |
31
|
|
|
* "Mr.", "Mrs." or "Mr. / Mrs." (depending on the value of {person.gender}) |
32
|
|
|
* </output> |
33
|
|
|
* |
34
|
|
|
* Note: Using this view helper can be a sign of weak architecture. If you end up using it extensively |
35
|
|
|
* you might want to consider restructuring your controllers/actions and/or use partials and sections. |
36
|
|
|
* E.g. the above example could be achieved with <f:render partial="title.{person.gender}" /> and the partials |
37
|
|
|
* "title.male.html", "title.female.html", ... |
38
|
|
|
* Depending on the scenario this can be easier to extend and possibly contains less duplication. |
39
|
|
|
* |
40
|
|
|
* @api |
41
|
|
|
*/ |
42
|
|
|
class SwitchViewHelper extends AbstractViewHelper implements NodeFilterInterface |
43
|
|
|
{ |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var boolean |
47
|
|
|
*/ |
48
|
|
|
protected $escapeOutput = false; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var mixed |
52
|
|
|
*/ |
53
|
|
|
protected $backupSwitchExpression = null; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var boolean |
57
|
|
|
*/ |
58
|
|
|
protected $backupBreakState = false; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @return void |
62
|
|
|
*/ |
63
|
|
|
public function initializeArguments() |
64
|
|
|
{ |
65
|
|
|
parent::initializeArguments(); |
66
|
|
|
$this->registerArgument('expression', 'mixed', 'Expression to switch', true); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @return string the rendered string |
71
|
|
|
* @api |
72
|
|
|
*/ |
73
|
|
|
public function render() |
74
|
|
|
{ |
75
|
|
|
$expression = $this->arguments['expression']; |
76
|
|
|
$this->backupSwitchState(); |
77
|
|
|
$variableContainer = $this->renderingContext->getViewHelperVariableContainer(); |
78
|
|
|
|
79
|
|
|
$variableContainer->addOrUpdate(SwitchViewHelper::class, 'switchExpression', $expression); |
80
|
|
|
$variableContainer->addOrUpdate(SwitchViewHelper::class, 'break', false); |
81
|
|
|
|
82
|
|
|
$content = $this->retrieveContentFromChildNodes($this->viewHelperNode->getChildNodes()); |
83
|
|
|
|
84
|
|
|
if ($variableContainer->exists(SwitchViewHelper::class, 'switchExpression')) { |
85
|
|
|
$variableContainer->remove(SwitchViewHelper::class, 'switchExpression'); |
86
|
|
|
} |
87
|
|
|
if ($variableContainer->exists(SwitchViewHelper::class, 'break')) { |
88
|
|
|
$variableContainer->remove(SwitchViewHelper::class, 'break'); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
$this->restoreSwitchState(); |
92
|
|
|
return $content; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @param NodeInterface[] $childNodes |
97
|
|
|
* @return mixed |
98
|
|
|
*/ |
99
|
|
|
protected function retrieveContentFromChildNodes(array $childNodes) |
100
|
|
|
{ |
101
|
|
|
$content = null; |
102
|
|
|
$defaultCaseViewHelperNode = null; |
103
|
|
|
foreach ($childNodes as $childNode) { |
104
|
|
|
if ($this->isDefaultCaseNode($childNode)) { |
105
|
|
|
$defaultCaseViewHelperNode = $childNode; |
106
|
|
|
} |
107
|
|
|
if (!$this->isCaseNode($childNode)) { |
108
|
|
|
continue; |
109
|
|
|
} |
110
|
|
|
$content = $childNode->evaluate($this->renderingContext); |
111
|
|
|
if ($this->viewHelperVariableContainer->get(SwitchViewHelper::class, 'break') === true) { |
112
|
|
|
$defaultCaseViewHelperNode = null; |
113
|
|
|
break; |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
if ($defaultCaseViewHelperNode !== null) { |
118
|
|
|
$content = $defaultCaseViewHelperNode->evaluate($this->renderingContext); |
119
|
|
|
} |
120
|
|
|
return $content; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @param NodeInterface $node |
125
|
|
|
* @return boolean |
126
|
|
|
*/ |
127
|
|
|
protected function isDefaultCaseNode(NodeInterface $node) |
128
|
|
|
{ |
129
|
|
|
return ($node instanceof ViewHelperNode && $node->getViewHelperClassName() === DefaultCaseViewHelper::class); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param NodeInterface $node |
134
|
|
|
* @return boolean |
135
|
|
|
*/ |
136
|
|
|
protected function isCaseNode(NodeInterface $node) |
137
|
|
|
{ |
138
|
|
|
return ($node instanceof ViewHelperNode && $node->getViewHelperClassName() === CaseViewHelper::class); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Backups "switch expression" and "break" state of a possible parent switch ViewHelper to support nesting |
143
|
|
|
* |
144
|
|
|
* @return void |
145
|
|
|
*/ |
146
|
|
|
protected function backupSwitchState() |
147
|
|
|
{ |
148
|
|
View Code Duplication |
if ($this->renderingContext->getViewHelperVariableContainer()->exists(SwitchViewHelper::class, 'switchExpression')) { |
|
|
|
|
149
|
|
|
$this->backupSwitchExpression = $this->renderingContext->getViewHelperVariableContainer()->get(SwitchViewHelper::class, 'switchExpression'); |
150
|
|
|
} |
151
|
|
View Code Duplication |
if ($this->renderingContext->getViewHelperVariableContainer()->exists(SwitchViewHelper::class, 'break')) { |
|
|
|
|
152
|
|
|
$this->backupBreakState = $this->renderingContext->getViewHelperVariableContainer()->get(SwitchViewHelper::class, 'break'); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Restores "switch expression" and "break" states that might have been backed up in backupSwitchState() before |
158
|
|
|
* |
159
|
|
|
* @return void |
160
|
|
|
*/ |
161
|
|
|
protected function restoreSwitchState() |
162
|
|
|
{ |
163
|
|
|
if ($this->backupSwitchExpression !== null) { |
164
|
|
|
$this->renderingContext->getViewHelperVariableContainer()->addOrUpdate(SwitchViewHelper::class, 'switchExpression', $this->backupSwitchExpression); |
165
|
|
|
} |
166
|
|
|
if ($this->backupBreakState !== false) { |
167
|
|
|
$this->renderingContext->getViewHelperVariableContainer()->addOrUpdate(SwitchViewHelper::class, 'break', true); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* The switch ViewHelper type only allows the "case" and "defaultCase" ViewHelper as child nodes. Anything else, |
173
|
|
|
* including text nodes (whitespace) is ignored and never gets compiled. |
174
|
|
|
* |
175
|
|
|
* @param NodeInterface $node |
176
|
|
|
* @return bool |
177
|
|
|
*/ |
178
|
|
|
public function allowsChildNodeType(NodeInterface $node): bool |
179
|
|
|
{ |
180
|
|
|
return $this->isCaseNode($node) || $this->isDefaultCaseNode($node); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Compiles the node structure to a native switch |
185
|
|
|
* statement which evaluates closures for each |
186
|
|
|
* case comparison and renders child node closures |
187
|
|
|
* only when value matches. |
188
|
|
|
* |
189
|
|
|
* @param string $argumentsName |
190
|
|
|
* @param string $closureName |
191
|
|
|
* @param string $initializationPhpCode |
192
|
|
|
* @param ViewHelperNode $node |
193
|
|
|
* @param TemplateCompiler $compiler |
194
|
|
|
* @return string |
195
|
|
|
*/ |
196
|
|
|
public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler) |
197
|
|
|
{ |
198
|
|
|
$phpCode = 'call_user_func_array(function($arguments) use ($renderingContext, $self) {' . PHP_EOL . |
199
|
|
|
'switch ($arguments[\'expression\']) {' . PHP_EOL; |
200
|
|
|
foreach ($node->getChildNodes() as $childNode) { |
201
|
|
|
if ($this->isDefaultCaseNode($childNode)) { |
202
|
|
|
$childrenClosure = $compiler->wrapChildNodesInClosure($childNode); |
203
|
|
|
$phpCode .= sprintf('default: return call_user_func(%s);', $childrenClosure) . PHP_EOL; |
204
|
|
|
} elseif ($this->isCaseNode($childNode)) { |
205
|
|
|
/** @var ViewHelperNode $childNode */ |
206
|
|
|
$valueClosure = $compiler->wrapViewHelperNodeArgumentEvaluationInClosure($childNode, 'value'); |
207
|
|
|
$childrenClosure = $compiler->wrapChildNodesInClosure($childNode); |
208
|
|
|
$phpCode .= sprintf( |
209
|
|
|
'case call_user_func(%s): return call_user_func(%s);', |
210
|
|
|
$valueClosure, |
211
|
|
|
$childrenClosure, |
212
|
|
|
$compiler->getNodeConverter()->convert($childNode) |
213
|
|
|
) . PHP_EOL; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
$phpCode .= '}' . PHP_EOL; |
217
|
|
|
$phpCode .= sprintf('}, array(%s))', $argumentsName); |
218
|
|
|
return $phpCode; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
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.