TYPO3  7.6
TemplateCompiler.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Fluid\Core\Compiler;
3 
4 /* *
5  * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6  * *
7  * It is free software; you can redistribute it and/or modify it under *
8  * the terms of the GNU Lesser General Public License, either version 3 *
9  * of the License, or (at your option) any later version. *
10  * *
11  * The TYPO3 project - inspiring people to share! *
12  * */
13 
15 {
16  const SHOULD_GENERATE_VIEWHELPER_INVOCATION = '##should_gen_viewhelper##';
17 
21  protected $templateCache;
22 
26  protected $variableCounter = 0;
27 
31  protected $syntaxTreeInstanceCache = array();
32 
37  public function setTemplateCache(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $templateCache)
38  {
39  $this->templateCache = $templateCache;
40  }
41 
46  public function has($identifier)
47  {
48  $identifier = $this->sanitizeIdentifier($identifier);
49  return $this->templateCache->has($identifier);
50  }
51 
56  public function get($identifier)
57  {
58  $identifier = $this->sanitizeIdentifier($identifier);
59  if (!isset($this->syntaxTreeInstanceCache[$identifier])) {
60  $this->templateCache->requireOnce($identifier);
61  $templateClassName = 'FluidCache_' . $identifier;
62  $this->syntaxTreeInstanceCache[$identifier] = new $templateClassName();
63  }
64  return $this->syntaxTreeInstanceCache[$identifier];
65  }
66 
72  public function store($identifier, \TYPO3\CMS\Fluid\Core\Parser\ParsingState $parsingState)
73  {
74  $identifier = $this->sanitizeIdentifier($identifier);
75  $this->variableCounter = 0;
76  $generatedRenderFunctions = '';
77 
78  if ($parsingState->getVariableContainer()->exists('sections')) {
79  $sections = $parsingState->getVariableContainer()->get('sections');
80  // @todo refactor to $parsedTemplate->getSections()
81  foreach ($sections as $sectionName => $sectionRootNode) {
82  $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($sectionRootNode), 'section_' . sha1($sectionName), 'section ' . $sectionName);
83  }
84  }
85  $generatedRenderFunctions .= $this->generateCodeForSection($this->convertListOfSubNodes($parsingState->getRootNode()), 'render', 'Main Render function');
86  $convertedLayoutNameNode = $parsingState->hasLayout() ? $this->convert($parsingState->getLayoutNameNode()) : array('initialization' => '', 'execution' => 'NULL');
87 
88  $classDefinition = 'class FluidCache_' . $identifier . ' extends \\TYPO3\\CMS\\Fluid\\Core\\Compiler\\AbstractCompiledTemplate';
89 
90  $templateCode = <<<EOD
91 %s {
92 
93 public function getVariableContainer() {
94  // @todo
95  return new \TYPO3\CMS\Fluid\Core\ViewHelper\TemplateVariableContainer();
96 }
97 public function getLayoutName(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
98 \$currentVariableContainer = \$renderingContext->getTemplateVariableContainer();
99 %s
100 return %s;
101 }
102 public function hasLayout() {
103 return %s;
104 }
105 
106 %s
107 
108 }
109 EOD;
110  $templateCode = sprintf($templateCode,
111  $classDefinition,
112  $convertedLayoutNameNode['initialization'],
113  $convertedLayoutNameNode['execution'],
114  ($parsingState->hasLayout() ? 'TRUE' : 'FALSE'),
115  $generatedRenderFunctions);
116  $this->templateCache->set($identifier, $templateCode);
117  }
118 
126  protected function sanitizeIdentifier($identifier)
127  {
128  return preg_replace('([^a-zA-Z0-9_\\x7f-\\xff])', '_', $identifier);
129  }
130 
137  protected function generateCodeForSection(array $converted, $expectedFunctionName, $comment)
138  {
139  $templateCode = <<<EOD
143 public function %s(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface \$renderingContext) {
144 \$self = \$this;
145 \$currentVariableContainer = \$renderingContext->getTemplateVariableContainer();
146 
147 %s
148 
149 return %s;
150 }
151 
152 EOD;
153  return sprintf($templateCode, $comment, $expectedFunctionName, $converted['initialization'], $converted['execution']);
154  }
155 
165  protected function convert(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
166  {
167  if ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode) {
168  return $this->convertTextNode($node);
169  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode) {
170  return $this->convertNumericNode($node);
171  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode) {
172  return $this->convertViewHelperNode($node);
173  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode) {
174  return $this->convertObjectAccessorNode($node);
175  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode) {
176  return $this->convertArrayNode($node);
177  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode) {
178  return $this->convertListOfSubNodes($node);
179  } elseif ($node instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode) {
180  return $this->convertBooleanNode($node);
181  } else {
182  throw new \TYPO3\CMS\Fluid\Exception('Syntax tree node type "' . get_class($node) . '" is not supported.');
183  }
184  }
185 
191  protected function convertTextNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode $node)
192  {
193  return array(
194  'initialization' => '',
195  'execution' => '\'' . $this->escapeTextForUseInSingleQuotes($node->getText()) . '\''
196  );
197  }
198 
204  protected function convertNumericNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode $node)
205  {
206  return array(
207  'initialization' => '',
208  'execution' => $node->getValue()
209  );
210  }
211 
220  protected function convertViewHelperNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ViewHelperNode $node)
221  {
222  $initializationPhpCode = '// Rendering ViewHelper ' . $node->getViewHelperClassName() . LF;
223 
224  // Build up $arguments array
225  $argumentsVariableName = $this->variableName('arguments');
226  $initializationPhpCode .= sprintf('%s = array();', $argumentsVariableName) . LF;
227 
228  $alreadyBuiltArguments = array();
229  foreach ($node->getArguments() as $argumentName => $argumentValue) {
230  $converted = $this->convert($argumentValue);
231  $initializationPhpCode .= $converted['initialization'];
232  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, $converted['execution']) . LF;
233  $alreadyBuiltArguments[$argumentName] = true;
234  }
235 
236  foreach ($node->getUninitializedViewHelper()->prepareArguments() as $argumentName => $argumentDefinition) {
237  if (!isset($alreadyBuiltArguments[$argumentName])) {
238  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $argumentsVariableName, $argumentName, var_export($argumentDefinition->getDefaultValue(), true)) . LF;
239  }
240  }
241 
242  // Build up closure which renders the child nodes
243  $renderChildrenClosureVariableName = $this->variableName('renderChildrenClosure');
244  $initializationPhpCode .= sprintf('%s = %s;', $renderChildrenClosureVariableName, $this->wrapChildNodesInClosure($node)) . LF;
245 
246  if ($node->getUninitializedViewHelper() instanceof \TYPO3\CMS\Fluid\Core\ViewHelper\Facets\CompilableInterface) {
247  // ViewHelper is compilable
248  $viewHelperInitializationPhpCode = '';
249  $convertedViewHelperExecutionCode = $node->getUninitializedViewHelper()->compile($argumentsVariableName, $renderChildrenClosureVariableName, $viewHelperInitializationPhpCode, $node, $this);
250  $initializationPhpCode .= $viewHelperInitializationPhpCode;
251  if ($convertedViewHelperExecutionCode !== self::SHOULD_GENERATE_VIEWHELPER_INVOCATION) {
252  return array(
253  'initialization' => $initializationPhpCode,
254  'execution' => $convertedViewHelperExecutionCode
255  );
256  }
257  }
258 
259  // ViewHelper is not compilable, so we need to instanciate it directly and render it.
260  $viewHelperVariableName = $this->variableName('viewHelper');
261 
262  $initializationPhpCode .= sprintf('%s = $self->getViewHelper(\'%s\', $renderingContext, \'%s\');', $viewHelperVariableName, $viewHelperVariableName, $node->getViewHelperClassName()) . LF;
263  $initializationPhpCode .= sprintf('%s->setArguments(%s);', $viewHelperVariableName, $argumentsVariableName) . LF;
264  $initializationPhpCode .= sprintf('%s->setRenderingContext($renderingContext);', $viewHelperVariableName) . LF;
265 
266  $initializationPhpCode .= sprintf('%s->setRenderChildrenClosure(%s);', $viewHelperVariableName, $renderChildrenClosureVariableName) . LF;
267 
268  $initializationPhpCode .= '// End of ViewHelper ' . $node->getViewHelperClassName() . LF;
269 
270  return array(
271  'initialization' => $initializationPhpCode,
272  'execution' => sprintf('%s->initializeArgumentsAndRender()', $viewHelperVariableName)
273  );
274  }
275 
281  protected function convertObjectAccessorNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode $node)
282  {
283  $objectPathSegments = explode('.', $node->getObjectPath());
284  $firstPathElement = array_shift($objectPathSegments);
285  if ($objectPathSegments === array()) {
286  return array(
287  'initialization' => '',
288  'execution' => sprintf('$currentVariableContainer->getOrNull(\'%s\')', $firstPathElement)
289  );
290  } else {
291  $executionCode = '\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ObjectAccessorNode::getPropertyPath($currentVariableContainer->getOrNull(\'%s\'), \'%s\', $renderingContext)';
292  return array(
293  'initialization' => '',
294  'execution' => sprintf($executionCode, $firstPathElement, implode('.', $objectPathSegments))
295  );
296  }
297  }
298 
304  protected function convertArrayNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\ArrayNode $node)
305  {
306  $initializationPhpCode = '// Rendering Array' . LF;
307  $arrayVariableName = $this->variableName('array');
308 
309  $initializationPhpCode .= sprintf('%s = array();', $arrayVariableName) . LF;
310 
311  foreach ($node->getInternalArray() as $key => $value) {
312  if ($value instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode) {
313  $converted = $this->convert($value);
314  $initializationPhpCode .= $converted['initialization'];
315  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $converted['execution']) . LF;
316  } elseif (is_numeric($value)) {
317  // this case might happen for simple values
318  $initializationPhpCode .= sprintf('%s[\'%s\'] = %s;', $arrayVariableName, $key, $value) . LF;
319  } else {
320  // this case might happen for simple values
321  $initializationPhpCode .= sprintf('%s[\'%s\'] = \'%s\';', $arrayVariableName, $key, $this->escapeTextForUseInSingleQuotes($value)) . LF;
322  }
323  }
324  return array(
325  'initialization' => $initializationPhpCode,
326  'execution' => $arrayVariableName
327  );
328  }
329 
335  public function convertListOfSubNodes(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
336  {
337  switch (count($node->getChildNodes())) {
338  case 0:
339  return array(
340  'initialization' => '',
341  'execution' => 'NULL'
342  );
343  case 1:
344  $converted = $this->convert(current($node->getChildNodes()));
345 
346  return $converted;
347  default:
348  $outputVariableName = $this->variableName('output');
349  $initializationPhpCode = sprintf('%s = \'\';', $outputVariableName) . LF;
350 
351  foreach ($node->getChildNodes() as $childNode) {
352  $converted = $this->convert($childNode);
353 
354  $initializationPhpCode .= $converted['initialization'] . LF;
355  $initializationPhpCode .= sprintf('%s .= %s;', $outputVariableName, $converted['execution']) . LF;
356  }
357 
358  return array(
359  'initialization' => $initializationPhpCode,
360  'execution' => $outputVariableName
361  );
362  }
363  }
364 
370  protected function convertBooleanNode(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode $node)
371  {
372  $initializationPhpCode = '// Rendering Boolean node' . LF;
373  if ($node->getComparator() !== null) {
374  $convertedLeftSide = $this->convert($node->getLeftSide());
375  $convertedRightSide = $this->convert($node->getRightSide());
376 
377  return array(
378  'initialization' => $initializationPhpCode . $convertedLeftSide['initialization'] . $convertedRightSide['initialization'],
379  'execution' => sprintf(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode::class . '::evaluateComparator(\'%s\', %s, %s)', $node->getComparator(), $convertedLeftSide['execution'], $convertedRightSide['execution'])
380  );
381  } else {
382  // simple case, no comparator.
383  $converted = $this->convert($node->getSyntaxTreeNode());
384  return array(
385  'initialization' => $initializationPhpCode . $converted['initialization'],
386  'execution' => sprintf(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\BooleanNode::class . '::convertToBoolean(%s)', $converted['execution'])
387  );
388  }
389  }
390 
395  protected function escapeTextForUseInSingleQuotes($text)
396  {
397  return str_replace(array('\\', '\''), array('\\\\', '\\\''), $text);
398  }
399 
404  public function wrapChildNodesInClosure(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $node)
405  {
406  $convertedSubNodes = $this->convertListOfSubNodes($node);
407  if ($convertedSubNodes['execution'] === 'NULL') {
408  return 'function() {return NULL;}';
409  }
410 
411  $closure = '';
412  $closure .= 'function() use ($renderingContext, $self) {' . LF;
413  $closure .= '$currentVariableContainer = $renderingContext->getTemplateVariableContainer();' . LF;
414  $closure .= $convertedSubNodes['initialization'];
415  $closure .= sprintf('return %s;', $convertedSubNodes['execution']) . LF;
416  $closure .= '}';
417  return $closure;
418  }
419 
426  public function variableName($prefix)
427  {
428  return '$' . $prefix . $this->variableCounter++;
429  }
430 }