Completed
Pull Request — master (#393)
by Claus
10:00 queued 01:48
created

AbstractConditionViewHelper::verdict()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3Fluid\Fluid\Core\ViewHelper;
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\SyntaxTree\ViewHelperNode;
11
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
12
13
/**
14
 * This view helper is an abstract ViewHelper which implements an if/else condition.
15
 *
16
 * = Usage =
17
 *
18
 * To create a custom Condition ViewHelper, you need to subclass this class, and
19
 * implement your own render() method. Inside there, you should call $this->renderThenChild()
20
 * if the condition evaluated to TRUE, and $this->renderElseChild() if the condition evaluated
21
 * to FALSE.
22
 *
23
 * Every Condition ViewHelper has a "then" and "else" argument, so it can be used like:
24
 * <[aConditionViewHelperName] .... then="condition true" else="condition false" />,
25
 * or as well use the "then" and "else" child nodes.
26
 *
27
 * @see TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper for a more detailed explanation and a simple usage example.
28
 * Make sure to NOT OVERRIDE the constructor.
29
 *
30
 * @api
31
 */
32
abstract class AbstractConditionViewHelper extends AbstractViewHelper
33
{
34
35
    /**
36
     * @var boolean
37
     */
38
    protected $escapeOutput = false;
39
40
    /**
41
     * Initializes the "then" and "else" arguments
42
     */
43
    public function initializeArguments()
44
    {
45
        $this->registerArgument('then', 'mixed', 'Value to be returned if the condition if met.', false);
46
        $this->registerArgument('else', 'mixed', 'Value to be returned if the condition if not met.', false);
47
    }
48
49
    /**
50
     * Renders <f:then> child if $condition is true, otherwise renders <f:else> child.
51
     * Method which only gets called if the template is not compiled. For static calling,
52
     * the then/else nodes are converted to closures and condition evaluation closures.
53
     *
54
     * @return string the rendered string
55
     * @api
56
     */
57
    public function render()
58
    {
59
        if (static::verdict($this->arguments, $this->renderingContext)) {
60
            return $this->renderThenChild();
61
        }
62
        return $this->renderElseChild();
63
    }
64
65
    /**
66
     * @param array $arguments
67
     * @param \Closure $renderChildrenClosure
68
     * @param RenderingContextInterface $renderingContext
69
     * @return mixed
70
     */
71
    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
72
    {
73
        if (static::verdict($arguments, $renderingContext)) {
74
            if (isset($arguments['then'])) {
75
                return $arguments['then'];
76
            }
77
            if (isset($arguments['__thenClosure'])) {
78
                return $arguments['__thenClosure']();
79
            }
80
        } elseif (!empty($arguments['__elseClosures'])) {
81
            $elseIfClosures = isset($arguments['__elseifClosures']) ? $arguments['__elseifClosures'] : [];
82
            return static::evaluateElseClosures($arguments['__elseClosures'], $elseIfClosures, $renderingContext);
0 ignored issues
show
Bug introduced by
Since evaluateElseClosures() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of evaluateElseClosures() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
83
        } elseif (array_key_exists('else', $arguments)) {
84
            return $arguments['else'];
85
        }
86
        return '';
87
    }
88
89
    /**
90
     * Static method which can be overridden by subclasses. If a subclass
91
     * requires a different (or faster) decision then this method is the one
92
     * to override and implement.
93
     *
94
     * @param array $arguments
95
     * @param RenderingContextInterface $renderingContext
96
     * @return bool
97
     */
98
    public static function verdict(array $arguments, RenderingContextInterface $renderingContext)
0 ignored issues
show
Unused Code introduced by
The parameter $renderingContext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
99
    {
100
        return static::evaluateCondition($arguments);
0 ignored issues
show
Deprecated Code introduced by
The method TYPO3Fluid\Fluid\Core\Vi...er::evaluateCondition() has been deprecated with message: Deprecated in favor of ClassName::verdict($arguments, renderingContext), will no longer be called in 3.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
101
    }
102
103
    /**
104
     * Static method which can be overridden by subclasses. If a subclass
105
     * requires a different (or faster) decision then this method is the one
106
     * to override and implement.
107
     *
108
     * Note: method signature does not type-hint that an array is desired,
109
     * and as such, *appears* to accept any input type. There is no type hint
110
     * here for legacy reasons - the signature is kept compatible with third
111
     * party packages which depending on PHP version would error out if this
112
     * signature was not compatible with that of existing and in-production
113
     * subclasses that will be using this base class in the future. Let this
114
     * be a warning if someone considers changing this method signature!
115
     *
116
     * @deprecated Deprecated in favor of ClassName::verdict($arguments, renderingContext), will no longer be called in 3.0
117
     * @param array|NULL $arguments
118
     * @return boolean
119
     * @api
120
     */
121
    protected static function evaluateCondition($arguments = null)
122
    {
123
        return isset($arguments['condition']) && (bool)($arguments['condition']);
124
    }
125
126
    /**
127
     * @param array $closures
128
     * @param array $conditionClosures
129
     * @param RenderingContextInterface $renderingContext
130
     * @return string
131
     */
132
    private static function evaluateElseClosures(array $closures, array $conditionClosures, RenderingContextInterface $renderingContext)
0 ignored issues
show
Unused Code introduced by
The parameter $renderingContext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
133
    {
134
        foreach ($closures as $elseNodeIndex => $elseNodeClosure) {
135
            if (!isset($conditionClosures[$elseNodeIndex])) {
136
                return $elseNodeClosure();
137
            } else {
138
                if ($conditionClosures[$elseNodeIndex]()) {
139
                    return $elseNodeClosure();
140
                }
141
            }
142
        }
143
        return '';
144
    }
145
146
    /**
147
     * Returns value of "then" attribute.
148
     * If then attribute is not set, iterates through child nodes and renders ThenViewHelper.
149
     * If then attribute is not set and no ThenViewHelper and no ElseViewHelper is found, all child nodes are rendered
150
     *
151
     * @return mixed rendered ThenViewHelper or contents of <f:if> if no ThenViewHelper was found
152
     * @api
153
     */
154
    protected function renderThenChild()
155
    {
156
        if ($this->hasArgument('then')) {
157
            return $this->arguments['then'];
158
        }
159
160
        $elseViewHelperEncountered = false;
161
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
162
            if ($childNode instanceof ViewHelperNode
163
                && substr($childNode->getViewHelperClassName(), -14) === 'ThenViewHelper') {
164
                $data = $childNode->evaluate($this->renderingContext);
165
                return $data;
166
            }
167
            if ($childNode instanceof ViewHelperNode
168
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
169
                $elseViewHelperEncountered = true;
170
            }
171
        }
172
173
        if ($elseViewHelperEncountered) {
174
            return '';
175
        } else {
176
            return $this->renderChildren();
177
        }
178
    }
179
180
    /**
181
     * Returns value of "else" attribute.
182
     * If else attribute is not set, iterates through child nodes and renders ElseViewHelper.
183
     * If else attribute is not set and no ElseViewHelper is found, an empty string will be returned.
184
     *
185
     * @return string rendered ElseViewHelper or an empty string if no ThenViewHelper was found
186
     * @api
187
     */
188
    protected function renderElseChild()
189
    {
190
191
        if ($this->hasArgument('else')) {
192
            return $this->arguments['else'];
193
        }
194
195
        /** @var ViewHelperNode|NULL $elseNode */
196
        $elseNode = null;
197
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
198
            if ($childNode instanceof ViewHelperNode
199
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
200
                $arguments = $childNode->getArguments();
201
                if (isset($arguments['if'])) {
202
                    if ($arguments['if']->evaluate($this->renderingContext)) {
203
                        return $childNode->evaluate($this->renderingContext);
204
                    }
205
                } else {
206
                    $elseNode = $childNode;
207
                }
208
            }
209
        }
210
211
        return $elseNode instanceof ViewHelperNode ? $elseNode->evaluate($this->renderingContext) : '';
212
    }
213
214
    /**
215
     * The compiled ViewHelper adds two new ViewHelper arguments: __thenClosure and __elseClosure.
216
     * These contain closures which are be executed to render the then(), respectively else() case.
217
     *
218
     * @param string $argumentsName
219
     * @param string $closureName
220
     * @param string $initializationPhpCode
221
     * @param ViewHelperNode $node
222
     * @param TemplateCompiler $compiler
223
     * @return string
224
     */
225
    public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler)
226
    {
227
        $thenViewHelperEncountered = $elseViewHelperEncountered = false;
228
        foreach ($node->getChildNodes() as $childNode) {
229
            if ($childNode instanceof ViewHelperNode) {
230
                $viewHelperClassName = $childNode->getViewHelperClassName();
231
                if (substr($viewHelperClassName, -14) === 'ThenViewHelper') {
232
                    $thenViewHelperEncountered = true;
233
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
234
                    $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
235
                } elseif (substr($viewHelperClassName, -14) === 'ElseViewHelper') {
236
                    $elseViewHelperEncountered = true;
237
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
238
                    $initializationPhpCode .= sprintf('%s[\'__elseClosures\'][] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
239
                    $arguments = $childNode->getArguments();
240
                    if (isset($arguments['if'])) {
241
                        // The "else" has an argument, indicating it has a secondary (elseif) condition.
242
                        // Compile a closure which will evaluate the condition.
243
                        $elseIfConditionAsClosure = $compiler->wrapViewHelperNodeArgumentEvaluationInClosure($childNode, 'if');
244
                        $initializationPhpCode .= sprintf('%s[\'__elseifClosures\'][] = %s;', $argumentsName, $elseIfConditionAsClosure) . chr(10);
245
                    }
246
                }
247
            }
248
        }
249
        if (!$thenViewHelperEncountered && !$elseViewHelperEncountered && !isset($node->getArguments()['then'])) {
250
            $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $closureName) . chr(10);
251
        }
252
        return parent::compile($argumentsName, $closureName, $initializationPhpCode, $node, $compiler);
253
    }
254
}
255