Completed
Pull Request — master (#258)
by Claus
02:24
created

AbstractConditionViewHelper::initializeArguments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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