1 | <?php |
||
20 | class TemplateCompiler |
||
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 |
||
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); |
||
375 |
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.