vendor/twig/twig/src/Node/ModuleNode.php line 74

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