Completed
Pull Request — master (#393)
by Claus
05:28
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
        $this->registerArgument('condition', 'boolean', 'Condition expression conforming to Fluid boolean rules', false, false);
48
    }
49
50
    /**
51
     * @param array $arguments
52
     * @param \Closure $renderChildrenClosure
53
     * @param RenderingContextInterface $renderingContext
54
     * @return mixed
55
     */
56
    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
57
    {
58
        if (static::verdict($arguments, $renderingContext)) {
59
            if (isset($arguments['then'])) {
60
                return $arguments['then'];
61
            }
62
            if (isset($arguments['__thenClosure'])) {
63
                return $arguments['__thenClosure']();
64
            }
65
        } elseif (!empty($arguments['__elseClosures'])) {
66
            $elseIfClosures = isset($arguments['__elseifClosures']) ? $arguments['__elseifClosures'] : [];
67
            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...
68
        } elseif (array_key_exists('else', $arguments)) {
69
            return $arguments['else'];
70
        }
71
        return '';
72
    }
73
74
    /**
75
     * Static method which can be overridden by subclasses. If a subclass
76
     * requires a different (or faster) decision then this method is the one
77
     * to override and implement.
78
     *
79
     * @param array $arguments
80
     * @param RenderingContextInterface $renderingContext
81
     * @return bool
82
     */
83
    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...
84
    {
85
        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...
86
    }
87
88
    /**
89
     * Static method which can be overridden by subclasses. If a subclass
90
     * requires a different (or faster) decision then this method is the one
91
     * to override and implement.
92
     *
93
     * Note: method signature does not type-hint that an array is desired,
94
     * and as such, *appears* to accept any input type. There is no type hint
95
     * here for legacy reasons - the signature is kept compatible with third
96
     * party packages which depending on PHP version would error out if this
97
     * signature was not compatible with that of existing and in-production
98
     * subclasses that will be using this base class in the future. Let this
99
     * be a warning if someone considers changing this method signature!
100
     *
101
     * @deprecated Deprecated in favor of ClassName::verdict($arguments, renderingContext), will no longer be called in 3.0
102
     * @param array|NULL $arguments
103
     * @return boolean
104
     * @api
105
     */
106
    protected static function evaluateCondition($arguments = null)
107
    {
108
        return (boolean) $arguments['condition'];
109
    }
110
111
    /**
112
     * @param array $closures
113
     * @param array $conditionClosures
114
     * @param RenderingContextInterface $renderingContext
115
     * @return string
116
     */
117
    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...
118
    {
119
        foreach ($closures as $elseNodeIndex => $elseNodeClosure) {
120
            if (!isset($conditionClosures[$elseNodeIndex])) {
121
                return $elseNodeClosure();
122
            } else {
123
                if ($conditionClosures[$elseNodeIndex]()) {
124
                    return $elseNodeClosure();
125
                }
126
            }
127
        }
128
        return '';
129
    }
130
131
    /**
132
     * Returns value of "then" attribute.
133
     * If then attribute is not set, iterates through child nodes and renders ThenViewHelper.
134
     * If then attribute is not set and no ThenViewHelper and no ElseViewHelper is found, all child nodes are rendered
135
     *
136
     * @return mixed rendered ThenViewHelper or contents of <f:if> if no ThenViewHelper was found
137
     * @api
138
     */
139
    protected function renderThenChild()
140
    {
141
        if ($this->hasArgument('then')) {
142
            return $this->arguments['then'];
143
        }
144
145
        $elseViewHelperEncountered = false;
146
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
147
            if ($childNode instanceof ViewHelperNode
148
                && substr($childNode->getViewHelperClassName(), -14) === 'ThenViewHelper') {
149
                $data = $childNode->evaluate($this->renderingContext);
150
                return $data;
151
            }
152
            if ($childNode instanceof ViewHelperNode
153
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
154
                $elseViewHelperEncountered = true;
155
            }
156
        }
157
158
        if ($elseViewHelperEncountered) {
159
            return '';
160
        } else {
161
            return $this->renderChildren();
162
        }
163
    }
164
165
    /**
166
     * Returns value of "else" attribute.
167
     * If else attribute is not set, iterates through child nodes and renders ElseViewHelper.
168
     * If else attribute is not set and no ElseViewHelper is found, an empty string will be returned.
169
     *
170
     * @return string rendered ElseViewHelper or an empty string if no ThenViewHelper was found
171
     * @api
172
     */
173
    protected function renderElseChild()
174
    {
175
176
        if ($this->hasArgument('else')) {
177
            return $this->arguments['else'];
178
        }
179
180
        /** @var ViewHelperNode|NULL $elseNode */
181
        $elseNode = null;
182
        foreach ($this->viewHelperNode->getChildNodes() as $childNode) {
183
            if ($childNode instanceof ViewHelperNode
184
                && substr($childNode->getViewHelperClassName(), -14) === 'ElseViewHelper') {
185
                $arguments = $childNode->getArguments();
186
                if (isset($arguments['if'])) {
187
                    if ($arguments['if']->evaluate($this->renderingContext)) {
188
                        return $childNode->evaluate($this->renderingContext);
189
                    }
190
                } else {
191
                    $elseNode = $childNode;
192
                }
193
            }
194
        }
195
196
        return $elseNode instanceof ViewHelperNode ? $elseNode->evaluate($this->renderingContext) : '';
197
    }
198
199
    /**
200
     * The compiled ViewHelper adds two new ViewHelper arguments: __thenClosure and __elseClosure.
201
     * These contain closures which are be executed to render the then(), respectively else() case.
202
     *
203
     * @param string $argumentsName
204
     * @param string $closureName
205
     * @param string $initializationPhpCode
206
     * @param ViewHelperNode $node
207
     * @param TemplateCompiler $compiler
208
     * @return string
209
     */
210
    public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler)
211
    {
212
        $thenViewHelperEncountered = $elseViewHelperEncountered = false;
213
        foreach ($node->getChildNodes() as $childNode) {
214
            if ($childNode instanceof ViewHelperNode) {
215
                $viewHelperClassName = $childNode->getViewHelperClassName();
216
                if (substr($viewHelperClassName, -14) === 'ThenViewHelper') {
217
                    $thenViewHelperEncountered = true;
218
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
219
                    $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
220
                } elseif (substr($viewHelperClassName, -14) === 'ElseViewHelper') {
221
                    $elseViewHelperEncountered = true;
222
                    $childNodesAsClosure = $compiler->wrapChildNodesInClosure($childNode);
223
                    $initializationPhpCode .= sprintf('%s[\'__elseClosures\'][] = %s;', $argumentsName, $childNodesAsClosure) . chr(10);
224
                    $arguments = $childNode->getArguments();
225
                    if (isset($arguments['if'])) {
226
                        // The "else" has an argument, indicating it has a secondary (elseif) condition.
227
                        // Compile a closure which will evaluate the condition.
228
                        $elseIfConditionAsClosure = $compiler->wrapViewHelperNodeArgumentEvaluationInClosure($childNode, 'if');
229
                        $initializationPhpCode .= sprintf('%s[\'__elseifClosures\'][] = %s;', $argumentsName, $elseIfConditionAsClosure) . chr(10);
230
                    }
231
                }
232
            }
233
        }
234
        if (!$thenViewHelperEncountered && !$elseViewHelperEncountered && !isset($node->getArguments()['then'])) {
235
            $initializationPhpCode .= sprintf('%s[\'__thenClosure\'] = %s;', $argumentsName, $closureName) . chr(10);
236
        }
237
        return parent::compile($argumentsName, $closureName, $initializationPhpCode, $node, $compiler);
238
    }
239
}
240