blob: e972b6ba5820f3e45c229f0504dddc87b715efe8 [file] [log] [blame]
Matthias Andreas Benkard12a57352021-12-28 18:02:04 +01001<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 * (c) Armin Ronacher
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13namespace Twig\Node;
14
15use Twig\Compiler;
16use Twig\Node\Expression\AbstractExpression;
17use Twig\Node\Expression\ConstantExpression;
18use Twig\Source;
19
20/**
21 * Represents a module node.
22 *
23 * Consider this class as being final. If you need to customize the behavior of
24 * the generated class, consider adding nodes to the following nodes: display_start,
25 * display_end, constructor_start, constructor_end, and class_end.
26 *
27 * @author Fabien Potencier <fabien@symfony.com>
28 */
29final class ModuleNode extends Node
30{
31 public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
32 {
33 $nodes = [
34 'body' => $body,
35 'blocks' => $blocks,
36 'macros' => $macros,
37 'traits' => $traits,
38 'display_start' => new Node(),
39 'display_end' => new Node(),
40 'constructor_start' => new Node(),
41 'constructor_end' => new Node(),
42 'class_end' => new Node(),
43 ];
44 if (null !== $parent) {
45 $nodes['parent'] = $parent;
46 }
47
48 // embedded templates are set as attributes so that they are only visited once by the visitors
49 parent::__construct($nodes, [
50 'index' => null,
51 'embedded_templates' => $embeddedTemplates,
52 ], 1);
53
54 // populate the template name of all node children
55 $this->setSourceContext($source);
56 }
57
58 public function setIndex($index)
59 {
60 $this->setAttribute('index', $index);
61 }
62
63 public function compile(Compiler $compiler): void
64 {
65 $this->compileTemplate($compiler);
66
67 foreach ($this->getAttribute('embedded_templates') as $template) {
68 $compiler->subcompile($template);
69 }
70 }
71
72 protected function compileTemplate(Compiler $compiler)
73 {
74 if (!$this->getAttribute('index')) {
75 $compiler->write('<?php');
76 }
77
78 $this->compileClassHeader($compiler);
79
80 $this->compileConstructor($compiler);
81
82 $this->compileGetParent($compiler);
83
84 $this->compileDisplay($compiler);
85
86 $compiler->subcompile($this->getNode('blocks'));
87
88 $this->compileMacros($compiler);
89
90 $this->compileGetTemplateName($compiler);
91
92 $this->compileIsTraitable($compiler);
93
94 $this->compileDebugInfo($compiler);
95
96 $this->compileGetSourceContext($compiler);
97
98 $this->compileClassFooter($compiler);
99 }
100
101 protected function compileGetParent(Compiler $compiler)
102 {
103 if (!$this->hasNode('parent')) {
104 return;
105 }
106 $parent = $this->getNode('parent');
107
108 $compiler
109 ->write("protected function doGetParent(array \$context)\n", "{\n")
110 ->indent()
111 ->addDebugInfo($parent)
112 ->write('return ')
113 ;
114
115 if ($parent instanceof ConstantExpression) {
116 $compiler->subcompile($parent);
117 } else {
118 $compiler
119 ->raw('$this->loadTemplate(')
120 ->subcompile($parent)
121 ->raw(', ')
122 ->repr($this->getSourceContext()->getName())
123 ->raw(', ')
124 ->repr($parent->getTemplateLine())
125 ->raw(')')
126 ;
127 }
128
129 $compiler
130 ->raw(";\n")
131 ->outdent()
132 ->write("}\n\n")
133 ;
134 }
135
136 protected function compileClassHeader(Compiler $compiler)
137 {
138 $compiler
139 ->write("\n\n")
140 ;
141 if (!$this->getAttribute('index')) {
142 $compiler
143 ->write("use Twig\Environment;\n")
144 ->write("use Twig\Error\LoaderError;\n")
145 ->write("use Twig\Error\RuntimeError;\n")
146 ->write("use Twig\Extension\SandboxExtension;\n")
147 ->write("use Twig\Markup;\n")
148 ->write("use Twig\Sandbox\SecurityError;\n")
149 ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n")
150 ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n")
151 ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n")
152 ->write("use Twig\Source;\n")
153 ->write("use Twig\Template;\n\n")
154 ;
155 }
156 $compiler
157 // if the template name contains */, add a blank to avoid a PHP parse error
158 ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n")
159 ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index')))
160 ->raw(" extends Template\n")
161 ->write("{\n")
162 ->indent()
163 ->write("private \$source;\n")
164 ->write("private \$macros = [];\n\n")
165 ;
166 }
167
168 protected function compileConstructor(Compiler $compiler)
169 {
170 $compiler
171 ->write("public function __construct(Environment \$env)\n", "{\n")
172 ->indent()
173 ->subcompile($this->getNode('constructor_start'))
174 ->write("parent::__construct(\$env);\n\n")
175 ->write("\$this->source = \$this->getSourceContext();\n\n")
176 ;
177
178 // parent
179 if (!$this->hasNode('parent')) {
180 $compiler->write("\$this->parent = false;\n\n");
181 }
182
183 $countTraits = \count($this->getNode('traits'));
184 if ($countTraits) {
185 // traits
186 foreach ($this->getNode('traits') as $i => $trait) {
187 $node = $trait->getNode('template');
188
189 $compiler
190 ->addDebugInfo($node)
191 ->write(sprintf('$_trait_%s = $this->loadTemplate(', $i))
192 ->subcompile($node)
193 ->raw(', ')
194 ->repr($node->getTemplateName())
195 ->raw(', ')
196 ->repr($node->getTemplateLine())
197 ->raw(");\n")
198 ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
199 ->indent()
200 ->write("throw new RuntimeError('Template \"'.")
201 ->subcompile($trait->getNode('template'))
202 ->raw(".'\" cannot be used as a trait.', ")
203 ->repr($node->getTemplateLine())
204 ->raw(", \$this->source);\n")
205 ->outdent()
206 ->write("}\n")
207 ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
208 ;
209
210 foreach ($trait->getNode('targets') as $key => $value) {
211 $compiler
212 ->write(sprintf('if (!isset($_trait_%s_blocks[', $i))
213 ->string($key)
214 ->raw("])) {\n")
215 ->indent()
216 ->write("throw new RuntimeError('Block ")
217 ->string($key)
218 ->raw(' is not defined in trait ')
219 ->subcompile($trait->getNode('template'))
220 ->raw(".', ")
221 ->repr($node->getTemplateLine())
222 ->raw(", \$this->source);\n")
223 ->outdent()
224 ->write("}\n\n")
225
226 ->write(sprintf('$_trait_%s_blocks[', $i))
227 ->subcompile($value)
228 ->raw(sprintf('] = $_trait_%s_blocks[', $i))
229 ->string($key)
230 ->raw(sprintf(']; unset($_trait_%s_blocks[', $i))
231 ->string($key)
232 ->raw("]);\n\n")
233 ;
234 }
235 }
236
237 if ($countTraits > 1) {
238 $compiler
239 ->write("\$this->traits = array_merge(\n")
240 ->indent()
241 ;
242
243 for ($i = 0; $i < $countTraits; ++$i) {
244 $compiler
245 ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i))
246 ;
247 }
248
249 $compiler
250 ->outdent()
251 ->write(");\n\n")
252 ;
253 } else {
254 $compiler
255 ->write("\$this->traits = \$_trait_0_blocks;\n\n")
256 ;
257 }
258
259 $compiler
260 ->write("\$this->blocks = array_merge(\n")
261 ->indent()
262 ->write("\$this->traits,\n")
263 ->write("[\n")
264 ;
265 } else {
266 $compiler
267 ->write("\$this->blocks = [\n")
268 ;
269 }
270
271 // blocks
272 $compiler
273 ->indent()
274 ;
275
276 foreach ($this->getNode('blocks') as $name => $node) {
277 $compiler
278 ->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name))
279 ;
280 }
281
282 if ($countTraits) {
283 $compiler
284 ->outdent()
285 ->write("]\n")
286 ->outdent()
287 ->write(");\n")
288 ;
289 } else {
290 $compiler
291 ->outdent()
292 ->write("];\n")
293 ;
294 }
295
296 $compiler
297 ->subcompile($this->getNode('constructor_end'))
298 ->outdent()
299 ->write("}\n\n")
300 ;
301 }
302
303 protected function compileDisplay(Compiler $compiler)
304 {
305 $compiler
306 ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
307 ->indent()
308 ->write("\$macros = \$this->macros;\n")
309 ->subcompile($this->getNode('display_start'))
310 ->subcompile($this->getNode('body'))
311 ;
312
313 if ($this->hasNode('parent')) {
314 $parent = $this->getNode('parent');
315
316 $compiler->addDebugInfo($parent);
317 if ($parent instanceof ConstantExpression) {
318 $compiler
319 ->write('$this->parent = $this->loadTemplate(')
320 ->subcompile($parent)
321 ->raw(', ')
322 ->repr($this->getSourceContext()->getName())
323 ->raw(', ')
324 ->repr($parent->getTemplateLine())
325 ->raw(");\n")
326 ;
327 $compiler->write('$this->parent');
328 } else {
329 $compiler->write('$this->getParent($context)');
330 }
331 $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
332 }
333
334 $compiler
335 ->subcompile($this->getNode('display_end'))
336 ->outdent()
337 ->write("}\n\n")
338 ;
339 }
340
341 protected function compileClassFooter(Compiler $compiler)
342 {
343 $compiler
344 ->subcompile($this->getNode('class_end'))
345 ->outdent()
346 ->write("}\n")
347 ;
348 }
349
350 protected function compileMacros(Compiler $compiler)
351 {
352 $compiler->subcompile($this->getNode('macros'));
353 }
354
355 protected function compileGetTemplateName(Compiler $compiler)
356 {
357 $compiler
358 ->write("public function getTemplateName()\n", "{\n")
359 ->indent()
360 ->write('return ')
361 ->repr($this->getSourceContext()->getName())
362 ->raw(";\n")
363 ->outdent()
364 ->write("}\n\n")
365 ;
366 }
367
368 protected function compileIsTraitable(Compiler $compiler)
369 {
370 // A template can be used as a trait if:
371 // * it has no parent
372 // * it has no macros
373 // * it has no body
374 //
375 // Put another way, a template can be used as a trait if it
376 // only contains blocks and use statements.
377 $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros'));
378 if ($traitable) {
379 if ($this->getNode('body') instanceof BodyNode) {
380 $nodes = $this->getNode('body')->getNode(0);
381 } else {
382 $nodes = $this->getNode('body');
383 }
384
385 if (!\count($nodes)) {
386 $nodes = new Node([$nodes]);
387 }
388
389 foreach ($nodes as $node) {
390 if (!\count($node)) {
391 continue;
392 }
393
394 if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) {
395 continue;
396 }
397
398 if ($node instanceof BlockReferenceNode) {
399 continue;
400 }
401
402 $traitable = false;
403 break;
404 }
405 }
406
407 if ($traitable) {
408 return;
409 }
410
411 $compiler
412 ->write("public function isTraitable()\n", "{\n")
413 ->indent()
414 ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
415 ->outdent()
416 ->write("}\n\n")
417 ;
418 }
419
420 protected function compileDebugInfo(Compiler $compiler)
421 {
422 $compiler
423 ->write("public function getDebugInfo()\n", "{\n")
424 ->indent()
425 ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
426 ->outdent()
427 ->write("}\n\n")
428 ;
429 }
430
431 protected function compileGetSourceContext(Compiler $compiler)
432 {
433 $compiler
434 ->write("public function getSourceContext()\n", "{\n")
435 ->indent()
436 ->write('return new Source(')
437 ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '')
438 ->raw(', ')
439 ->string($this->getSourceContext()->getName())
440 ->raw(', ')
441 ->string($this->getSourceContext()->getPath())
442 ->raw(");\n")
443 ->outdent()
444 ->write("}\n")
445 ;
446 }
447
448 protected function compileLoadTemplate(Compiler $compiler, $node, $var)
449 {
450 if ($node instanceof ConstantExpression) {
451 $compiler
452 ->write(sprintf('%s = $this->loadTemplate(', $var))
453 ->subcompile($node)
454 ->raw(', ')
455 ->repr($node->getTemplateName())
456 ->raw(', ')
457 ->repr($node->getTemplateLine())
458 ->raw(");\n")
459 ;
460 } else {
461 throw new \LogicException('Trait templates can only be constant nodes.');
462 }
463 }
464}