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

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