Completed
Pull Request — master (#315)
by Oliver
02:12
created

TemplateCompiler::wrapViewHelperNodeArgumentEvaluationInClosure()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
nc 2
nop 2
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 55 and the first side effect is on line 20.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
namespace TYPO3Fluid\Fluid\Core\Compiler;
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\Parser\ParsedTemplateInterface;
10
use TYPO3Fluid\Fluid\Core\Parser\ParsingState;
11
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ArrayNode;
12
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\NodeInterface;
13
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\RootNode;
14
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
15
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
16
17
/**
18
 * Class TemplateCompiler
19
 */
20
class TemplateCompiler
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
21
{
22
23
    const SHOULD_GENERATE_VIEWHELPER_INVOCATION = '##should_gen_viewhelper##';
24
    const MODE_NORMAL = 'normal';
25
    const MODE_WARMUP = 'warmup';
26
27
    /**
28
     * @var array
29
     */
30
    protected $syntaxTreeInstanceCache = [];
31
32
    /**
33
     * @var NodeConverter
34
     */
35
    protected $nodeConverter;
36
37
    /**
38
     * @var RenderingContextInterface
39
     */
40
    protected $renderingContext;
41
42
    /**
43
     * @var string
44
     */
45
    protected $mode = self::MODE_NORMAL;
46
47
    /**
48
     * @var ParsedTemplateInterface
49
     */
50
    protected $currentlyProcessingState;
51
52
    /**
53
     * Constructor
54
     */
55
    public function __construct()
56
    {
57
        $this->nodeConverter = new NodeConverter($this);
58
    }
59
60
    /**
61
     * Instruct the TemplateCompiler to enter warmup mode, assigning
62
     * additional context allowing cache-related implementations to
63
     * subsequently check the mode.
64
     *
65
     * Cannot be reversed once done - should only be used from within
66
     * FluidCacheWarmerInterface implementations!
67
     */
68
    public function enterWarmupMode()
69
    {
70
        $this->mode = static::MODE_WARMUP;
71
    }
72
73
    /**
74
     * Returns TRUE only if the TemplateCompiler is in warmup mode.
75
     */
76
    public function isWarmupMode()
77
    {
78
        return $this->mode === static::MODE_WARMUP;
79
    }
80
81
    /**
82
     * @return ParsedTemplateInterface|NULL
83
     */
84
    public function getCurrentlyProcessingState()
85
    {
86
        return $this->currentlyProcessingState;
87
    }
88
89
    /**
90
     * @param RenderingContextInterface $renderingContext
91
     * @return void
92
     */
93
    public function setRenderingContext(RenderingContextInterface $renderingContext)
94
    {
95
        $this->renderingContext = $renderingContext;
96
    }
97
98
    /**
99
     * @return RenderingContextInterface
100
     */
101
    public function getRenderingContext()
102
    {
103
        return $this->renderingContext;
104
    }
105
106
    /**
107
     * @param NodeConverter $nodeConverter
108
     * @return void
109
     */
110
    public function setNodeConverter(NodeConverter $nodeConverter)
111
    {
112
        $this->nodeConverter = $nodeConverter;
113
    }
114
115
    /**
116
     * @return NodeConverter
117
     */
118
    public function getNodeConverter()
119
    {
120
        return $this->nodeConverter;
121
    }
122
123
    /**
124
     * @return void
125
     */
126
    public function disable()
127
    {
128
        throw new StopCompilingException('Compiling stopped');
129
    }
130
131
    /**
132
     * @return boolean
133
     */
134
    public function isDisabled()
135
    {
136
        return !$this->renderingContext->isCacheEnabled();
137
    }
138
139
    /**
140
     * @param string $identifier
141
     * @return boolean
142
     */
143
    public function has($identifier)
144
    {
145
        $identifier = $this->sanitizeIdentifier($identifier);
146
147
        if (isset($this->syntaxTreeInstanceCache[$identifier])) {
148
            return true;
149
        }
150
        if (!$this->renderingContext->isCacheEnabled()) {
151
            return false;
152
        }
153
<<<<<<< HEAD
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_SL
Loading history...
154
        $identifier = $this->sanitizeIdentifier($identifier);
155
        return !empty($identifier) && (class_exists($identifier, false) || $this->renderingContext->getCache()->get($identifier));
156
=======
157
        return !empty($identifier) && $this->renderingContext->getCache()->get($identifier);
158
>>>>>>> master
159
    }
160
161
    /**
162
     * @param string $identifier
163
     * @return ParsedTemplateInterface
164
     */
165
    public function get($identifier)
166
    {
167
        $identifier = $this->sanitizeIdentifier($identifier);
168
169
        if (!isset($this->syntaxTreeInstanceCache[$identifier])) {
170
            if (!class_exists($identifier, false)) {
171
                $this->renderingContext->getCache()->get($identifier);
172
            }
173
            $this->syntaxTreeInstanceCache[$identifier] = new $identifier();
174
        }
175
176
177
        return $this->syntaxTreeInstanceCache[$identifier];
178
    }
179
180
    /**
181
     * Resets the currently processing state
182
     *
183
     * @return void
184
     */
185
    public function reset()
186
    {
187
        $this->currentlyProcessingState = null;
188
    }
189
190
    /**
191
     * @param string $identifier
192
     * @param ParsingState $parsingState
193
     * @return void
194
     */
195
    public function store($identifier, ParsingState $parsingState)
196
    {
197
        $identifier = $this->sanitizeIdentifier($identifier);
198
199
        if ($this->isDisabled()) {
200
            $cache = $this->renderingContext->getCache();
201
            if ($cache) {
202
                // Compiler is disabled but cache is enabled. Flush cache to make sure.
203
                $cache->flush($identifier);
204
            }
205
            $parsingState->setCompilable(false);
206
            return;
207
        }
208
209
        $this->currentlyProcessingState = $parsingState;
210
        $this->nodeConverter->setVariableCounter(0);
211
        $generatedRenderFunctions = $this->generateSectionCodeFromParsingState($parsingState);
212
213
        $generatedRenderFunctions .= $this->generateCodeForSection(
214
            $this->nodeConverter->convertListOfSubNodes($parsingState->getRootNode()),
215
            'render',
216
            'Main Render function'
217
        );
218
219
        $classDefinition = 'class ' . $identifier . ' extends \TYPO3Fluid\Fluid\Core\Compiler\AbstractCompiledTemplate';
220
221
        $templateCode = <<<EOD
222
<?php
223
224
%s {
225
226
public function getLayoutName(\TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
227
\$self = \$this; 
228
%s;
229
}
230
public function hasLayout() {
231
return %s;
232
}
233
public function addCompiledNamespaces(\TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
234
\$renderingContext->getViewHelperResolver()->addNamespaces(%s);
235
}
236
237
%s
238
239
}
240
EOD;
241
        $storedLayoutName = $parsingState->getVariableContainer()->get('layoutName');
242
        $templateCode = sprintf(
243
            $templateCode,
244
            $classDefinition,
245
            $this->generateCodeForLayoutName($storedLayoutName),
246
            ($parsingState->hasLayout() ? 'TRUE' : 'FALSE'),
247
            var_export($this->renderingContext->getViewHelperResolver()->getNamespaces(), true),
248
            $generatedRenderFunctions
249
        );
250
        $this->renderingContext->getCache()->set($identifier, $templateCode);
251
    }
252
253
    /**
254
     * @param RootNode|string $storedLayoutNameArgument
255
     * @return string
256
     */
257
    protected function generateCodeForLayoutName($storedLayoutNameArgument)
258
    {
259
        if ($storedLayoutNameArgument instanceof RootNode) {
260
            list ($initialization, $execution) = array_values($this->nodeConverter->convertListOfSubNodes($storedLayoutNameArgument));
261
            return $initialization . PHP_EOL . 'return ' . $execution;
262
        } else {
263
            return 'return (string) \'' . $storedLayoutNameArgument . '\'';
264
        }
265
    }
266
267
    /**
268
     * @param ParsingState $parsingState
269
     * @return string
270
     */
271
    protected function generateSectionCodeFromParsingState(ParsingState $parsingState)
272
    {
273
        $generatedRenderFunctions = '';
274
        if ($parsingState->getVariableContainer()->exists('1457379500_sections')) {
275
            $sections = $parsingState->getVariableContainer()->get('1457379500_sections'); // TODO: refactor to $parsedTemplate->getSections()
276
            foreach ($sections as $sectionName => $sectionRootNode) {
277
                $generatedRenderFunctions .= $this->generateCodeForSection(
278
                    $this->nodeConverter->convertListOfSubNodes($sectionRootNode),
279
                    'section_' . sha1($sectionName),
280
                    'section ' . $sectionName
281
                );
282
            }
283
        }
284
        return $generatedRenderFunctions;
285
    }
286
287
    /**
288
     * Replaces special characters by underscores
289
     * @see http://www.php.net/manual/en/language.variables.basics.php
290
     *
291
     * @param string $identifier
292
     * @return string the sanitized identifier
293
     */
294
    protected function sanitizeIdentifier($identifier)
295
    {
296
        return preg_replace('([^a-zA-Z0-9_\x7f-\xff])', '_', $identifier);
297
    }
298
299
    /**
300
     * @param array $converted
301
     * @param string $expectedFunctionName
302
     * @param string $comment
303
     * @return string
304
     */
305
    protected function generateCodeForSection(array $converted, $expectedFunctionName, $comment)
306
    {
307
        $templateCode = <<<EOD
308
/**
309
 * %s
310
 */
311
public function %s(\TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
312
\$self = \$this;
313
%s
314
return %s;
315
}
316
317
EOD;
318
        return sprintf($templateCode, $comment, $expectedFunctionName, $converted['initialization'], $converted['execution']);
319
    }
320
321
    /**
322
     * Returns a unique variable name by appending a global index to the given prefix
323
     *
324
     * @param string $prefix
325
     * @return string
326
     */
327
    public function variableName($prefix)
328
    {
329
        return $this->nodeConverter->variableName($prefix);
330
    }
331
332
    /**
333
     * @param NodeInterface $node
334
     * @return string
335
     */
336
    public function wrapChildNodesInClosure(NodeInterface $node)
337
    {
338
        $closure = '';
339
        $closure .= 'function() use ($renderingContext, $self) {' . chr(10);
340
        $convertedSubNodes = $this->nodeConverter->convertListOfSubNodes($node);
341
        $closure .= $convertedSubNodes['initialization'];
342
        $closure .= sprintf('return %s;', $convertedSubNodes['execution']) . chr(10);
343
        $closure .= '}';
344
        return $closure;
345
    }
346
347
    /**
348
     * Wraps one ViewHelper argument evaluation in a closure that can be
349
     * rendered by passing a rendering context.
350
     *
351
     * @param ViewHelperNode $node
352
     * @param string $argumentName
353
     * @return string
354
     */
355
    public function wrapViewHelperNodeArgumentEvaluationInClosure(ViewHelperNode $node, $argumentName)
356
    {
357
        $arguments = $node->getArguments();
358
        $argument = $arguments[$argumentName];
359
        $closure = 'function() use ($renderingContext, $self) {' . chr(10);
360
        if ($node->getArgumentDefinition($argumentName)->getType() === 'boolean') {
361
            // We treat boolean nodes by compiling a closure to evaluate the stack of the boolean argument
362
            $compiledIfArgumentStack = $this->nodeConverter->convert(new ArrayNode($argument->getStack()));
363
            $closure .= $compiledIfArgumentStack['initialization'] . chr(10);
364
            $closure .= sprintf(
365
                'return \TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\BooleanNode::evaluateStack($renderingContext, %s);',
366
                $compiledIfArgumentStack['execution']
367
            ) . chr(10);
368
        } else {
369
            $closure .= sprintf('$argument = unserialize(\'%s\'); return $argument->evaluate($renderingContext);', serialize($argument)) . chr(10);
370
        }
371
        $closure .= '}';
372
        return $closure;
373
    }
374
}
375