vendor/twig/twig/src/Template.php line 404

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;
  12. use Twig\Error\Error;
  13. use Twig\Error\RuntimeError;
  14. /**
  15.  * Default base class for compiled templates.
  16.  *
  17.  * This class is an implementation detail of how template compilation currently
  18.  * works, which might change. It should never be used directly. Use $twig->load()
  19.  * instead, which returns an instance of \Twig\TemplateWrapper.
  20.  *
  21.  * @author Fabien Potencier <fabien@symfony.com>
  22.  *
  23.  * @internal
  24.  */
  25. abstract class Template
  26. {
  27.     public const ANY_CALL 'any';
  28.     public const ARRAY_CALL 'array';
  29.     public const METHOD_CALL 'method';
  30.     protected $parent;
  31.     protected $parents = [];
  32.     protected $blocks = [];
  33.     protected $traits = [];
  34.     protected $traitAliases = [];
  35.     protected $extensions = [];
  36.     protected $sandbox;
  37.     private $useYield;
  38.     public function __construct(
  39.         protected Environment $env,
  40.     ) {
  41.         $this->useYield $env->useYield();
  42.         $this->extensions $env->getExtensions();
  43.     }
  44.     /**
  45.      * Returns the template name.
  46.      */
  47.     abstract public function getTemplateName(): string;
  48.     /**
  49.      * Returns debug information about the template.
  50.      *
  51.      * @return array<int, int> Debug information
  52.      */
  53.     abstract public function getDebugInfo(): array;
  54.     /**
  55.      * Returns information about the original template source code.
  56.      */
  57.     abstract public function getSourceContext(): Source;
  58.     /**
  59.      * Returns the parent template.
  60.      *
  61.      * This method is for internal use only and should never be called
  62.      * directly.
  63.      *
  64.      * @return self|TemplateWrapper|false The parent template or false if there is no parent
  65.      */
  66.     public function getParent(array $context): self|TemplateWrapper|false
  67.     {
  68.         if (null !== $this->parent) {
  69.             return $this->parent;
  70.         }
  71.         // The compiled doGetParent() may evaluate user expressions (filters,
  72.         // functions, method calls) when the parent name is dynamic. Make sure
  73.         // the sandbox security check runs first so those expressions cannot
  74.         // bypass the allow-list when getParent() is reached before the first
  75.         // ensureSecurityChecked() call on this template (e.g. via
  76.         // getTemplateForMacro() or yieldBlock() into a pre-warmed instance).
  77.         $this->ensureSecurityChecked();
  78.         if (!$parent $this->doGetParent($context)) {
  79.             return false;
  80.         }
  81.         if ($parent instanceof self || $parent instanceof TemplateWrapper) {
  82.             return $this->parents[$parent->getSourceContext()->getName()] = $parent;
  83.         }
  84.         if (!isset($this->parents[$parent])) {
  85.             $this->parents[$parent] = $this->load($parent, -1);
  86.         }
  87.         return $this->parents[$parent];
  88.     }
  89.     protected function doGetParent(array $context): bool|string|self|TemplateWrapper
  90.     {
  91.         return false;
  92.     }
  93.     public function isTraitable(): bool
  94.     {
  95.         return true;
  96.     }
  97.     /**
  98.      * Displays a parent block.
  99.      *
  100.      * This method is for internal use only and should never be called
  101.      * directly.
  102.      *
  103.      * @param string $name    The block name to display from the parent
  104.      * @param array  $context The context
  105.      * @param array  $blocks  The current set of blocks
  106.      */
  107.     public function displayParentBlock($name, array $context, array $blocks = []): void
  108.     {
  109.         foreach ($this->yieldParentBlock($name$context$blocks) as $data) {
  110.             echo $data;
  111.         }
  112.     }
  113.     /**
  114.      * Displays a block.
  115.      *
  116.      * This method is for internal use only and should never be called
  117.      * directly.
  118.      *
  119.      * @param string $name      The block name to display
  120.      * @param array  $context   The context
  121.      * @param array  $blocks    The current set of blocks
  122.      * @param bool   $useBlocks Whether to use the current set of blocks
  123.      */
  124.     public function displayBlock($name, array $context, array $blocks = [], $useBlocks true, ?self $templateContext null): void
  125.     {
  126.         foreach ($this->yieldBlock($name$context$blocks$useBlocks$templateContext) as $data) {
  127.             echo $data;
  128.         }
  129.     }
  130.     /**
  131.      * Renders a parent block.
  132.      *
  133.      * This method is for internal use only and should never be called
  134.      * directly.
  135.      *
  136.      * @param string $name    The block name to render from the parent
  137.      * @param array  $context The context
  138.      * @param array  $blocks  The current set of blocks
  139.      *
  140.      * @return string The rendered block
  141.      */
  142.     public function renderParentBlock($name, array $context, array $blocks = []): string
  143.     {
  144.         if (!$this->useYield) {
  145.             if ($this->env->isDebug()) {
  146.                 ob_start();
  147.             } else {
  148.                 ob_start(static function () { return ''; });
  149.             }
  150.             $this->displayParentBlock($name$context$blocks);
  151.             return ob_get_clean();
  152.         }
  153.         $content '';
  154.         foreach ($this->yieldParentBlock($name$context$blocks) as $data) {
  155.             $content .= $data;
  156.         }
  157.         return $content;
  158.     }
  159.     /**
  160.      * Renders a block.
  161.      *
  162.      * This method is for internal use only and should never be called
  163.      * directly.
  164.      *
  165.      * @param string $name      The block name to render
  166.      * @param array  $context   The context
  167.      * @param array  $blocks    The current set of blocks
  168.      * @param bool   $useBlocks Whether to use the current set of blocks
  169.      *
  170.      * @return string The rendered block
  171.      */
  172.     public function renderBlock($name, array $context, array $blocks = [], $useBlocks true): string
  173.     {
  174.         if (!$this->useYield) {
  175.             $level ob_get_level();
  176.             if ($this->env->isDebug()) {
  177.                 ob_start();
  178.             } else {
  179.                 ob_start(static function () { return ''; });
  180.             }
  181.             try {
  182.                 $this->displayBlock($name$context$blocks$useBlocks);
  183.             } catch (\Throwable $e) {
  184.                 while (ob_get_level() > $level) {
  185.                     ob_end_clean();
  186.                 }
  187.                 throw $e;
  188.             }
  189.             return ob_get_clean();
  190.         }
  191.         $content '';
  192.         foreach ($this->yieldBlock($name$context$blocks$useBlocks) as $data) {
  193.             $content .= $data;
  194.         }
  195.         return $content;
  196.     }
  197.     /**
  198.      * Returns whether a block exists or not in the current context of the template.
  199.      *
  200.      * This method checks blocks defined in the current template
  201.      * or defined in "used" traits or defined in parent templates.
  202.      *
  203.      * @param string $name    The block name
  204.      * @param array  $context The context
  205.      * @param array  $blocks  The current set of blocks
  206.      *
  207.      * @return bool true if the block exists, false otherwise
  208.      */
  209.     public function hasBlock($name, array $context, array $blocks = []): bool
  210.     {
  211.         if (isset($blocks[$name])) {
  212.             return $blocks[$name][0] instanceof self;
  213.         }
  214.         if (isset($this->blocks[$name])) {
  215.             return true;
  216.         }
  217.         if ($parent $this->getParent($context)) {
  218.             return $parent->hasBlock($name$context);
  219.         }
  220.         return false;
  221.     }
  222.     /**
  223.      * Returns all block names in the current context of the template.
  224.      *
  225.      * This method checks blocks defined in the current template
  226.      * or defined in "used" traits or defined in parent templates.
  227.      *
  228.      * @param array $context The context
  229.      * @param array $blocks  The current set of blocks
  230.      *
  231.      * @return array<string> An array of block names
  232.      */
  233.     public function getBlockNames(array $context, array $blocks = []): array
  234.     {
  235.         $names array_merge(array_keys($blocks), array_keys($this->blocks));
  236.         if ($parent $this->getParent($context)) {
  237.             $names array_merge($names$parent->getBlockNames($context));
  238.         }
  239.         return array_unique($names);
  240.     }
  241.     /**
  242.      * @param string|TemplateWrapper|array<string|TemplateWrapper> $template
  243.      */
  244.     protected function load(string|TemplateWrapper|array $templateint $line, ?int $index null): self
  245.     {
  246.         try {
  247.             if (\is_array($template)) {
  248.                 return $this->env->resolveTemplate($template)->unwrap();
  249.             }
  250.             if ($template instanceof TemplateWrapper) {
  251.                 return $template->unwrap();
  252.             }
  253.             if ($template === $this->getTemplateName()) {
  254.                 $class = static::class;
  255.                 if (false !== $pos strrpos($class'___', -1)) {
  256.                     $class substr($class0$pos);
  257.                 }
  258.             } else {
  259.                 $class $this->env->getTemplateClass($template);
  260.             }
  261.             return $this->env->loadTemplate($class$template$index);
  262.         } catch (Error $e) {
  263.             if (!$e->getSourceContext()) {
  264.                 $e->setSourceContext($this->getSourceContext());
  265.             }
  266.             if ($e->getTemplateLine() > 0) {
  267.                 throw $e;
  268.             }
  269.             if (-=== $line) {
  270.                 $e->guess();
  271.             } else {
  272.                 $e->setTemplateLine($line);
  273.             }
  274.             throw $e;
  275.         }
  276.     }
  277.     /**
  278.      * @param string|TemplateWrapper|array<string|TemplateWrapper> $template
  279.      *
  280.      * @deprecated since Twig 3.21 and will be removed in 4.0. Use Template::load() instead.
  281.      */
  282.     protected function loadTemplate($template$templateName null, ?int $line null, ?int $index null): self|TemplateWrapper
  283.     {
  284.         trigger_deprecation('twig/twig''3.21''The "%s" method is deprecated.'__METHOD__);
  285.         if (null === $line) {
  286.             $line = -1;
  287.         }
  288.         if ($template instanceof self) {
  289.             return $template;
  290.         }
  291.         return $this->load($template$line$index);
  292.     }
  293.     /**
  294.      * @internal
  295.      *
  296.      * @return $this
  297.      */
  298.     public function unwrap(): self
  299.     {
  300.         return $this;
  301.     }
  302.     /**
  303.      * Returns all blocks.
  304.      *
  305.      * This method is for internal use only and should never be called
  306.      * directly.
  307.      *
  308.      * @return array An array of blocks
  309.      */
  310.     public function getBlocks(): array
  311.     {
  312.         return $this->blocks;
  313.     }
  314.     public function display(array $context, array $blocks = []): void
  315.     {
  316.         foreach ($this->yield($context$blocks) as $data) {
  317.             echo $data;
  318.         }
  319.     }
  320.     public function render(array $context): string
  321.     {
  322.         if (!$this->useYield) {
  323.             $level ob_get_level();
  324.             if ($this->env->isDebug()) {
  325.                 ob_start();
  326.             } else {
  327.                 ob_start(static function () { return ''; });
  328.             }
  329.             try {
  330.                 $this->display($context);
  331.             } catch (\Throwable $e) {
  332.                 while (ob_get_level() > $level) {
  333.                     ob_end_clean();
  334.                 }
  335.                 throw $e;
  336.             }
  337.             return ob_get_clean();
  338.         }
  339.         $content '';
  340.         foreach ($this->yield($context) as $data) {
  341.             $content .= $data;
  342.         }
  343.         return $content;
  344.     }
  345.     /**
  346.      * @return iterable<scalar|\Stringable|null>
  347.      */
  348.     public function yield(array $context, array $blocks = []): iterable
  349.     {
  350.         $context += $this->env->getGlobals();
  351.         $blocks array_merge($this->blocks$blocks);
  352.         try {
  353.             $this->ensureSecurityChecked();
  354.             yield from $this->doDisplay($context$blocks);
  355.         } catch (Error $e) {
  356.             if (!$e->getSourceContext()) {
  357.                 $e->setSourceContext($this->getSourceContext());
  358.             }
  359.             // this is mostly useful for \Twig\Error\LoaderError exceptions
  360.             // see \Twig\Error\LoaderError
  361.             if (-=== $e->getTemplateLine()) {
  362.                 $e->guess();
  363.             }
  364.             throw $e;
  365.         } catch (\Throwable $e) {
  366.             $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").'$e->getMessage()), -1$this->getSourceContext(), $e);
  367.             $e->guess();
  368.             throw $e;
  369.         }
  370.     }
  371.     /**
  372.      * @return iterable<scalar|\Stringable|null>
  373.      */
  374.     public function yieldBlock($name, array $context, array $blocks = [], $useBlocks true, ?self $templateContext null): iterable
  375.     {
  376.         if ($useBlocks && isset($blocks[$name])) {
  377.             $template $blocks[$name][0];
  378.             $block $blocks[$name][1];
  379.         } elseif (isset($this->blocks[$name])) {
  380.             $template $this->blocks[$name][0];
  381.             $block $this->blocks[$name][1];
  382.         } else {
  383.             $template null;
  384.             $block null;
  385.         }
  386.         // avoid RCEs when sandbox is enabled
  387.         if (null !== $template && !$template instanceof self) {
  388.             throw new \LogicException('A block must be a method on a \Twig\Template instance.');
  389.         }
  390.         if (null !== $template) {
  391.             try {
  392.                 $template->ensureSecurityChecked();
  393.                 yield from $template->$block($context$blocks);
  394.             } catch (Error $e) {
  395.                 if (!$e->getSourceContext()) {
  396.                     $e->setSourceContext($template->getSourceContext());
  397.                 }
  398.                 // this is mostly useful for \Twig\Error\LoaderError exceptions
  399.                 // see \Twig\Error\LoaderError
  400.                 if (-=== $e->getTemplateLine()) {
  401.                     $e->guess();
  402.                 }
  403.                 throw $e;
  404.             } catch (\Throwable $e) {
  405.                 $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").'$e->getMessage()), -1$template->getSourceContext(), $e);
  406.                 $e->guess();
  407.                 throw $e;
  408.             }
  409.         } elseif ($parent $this->getParent($context)) {
  410.             yield from $parent->unwrap()->yieldBlock($name$contextarray_merge($this->blocks$blocks), false$templateContext ?? $this);
  411.         } elseif (isset($blocks[$name])) {
  412.             throw new RuntimeError(\sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".'$name$blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1$blocks[$name][0]->getSourceContext());
  413.         } else {
  414.             throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.'$name$this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
  415.         }
  416.     }
  417.     /**
  418.      * Yields a parent block.
  419.      *
  420.      * This method is for internal use only and should never be called
  421.      * directly.
  422.      *
  423.      * @param string $name    The block name to display from the parent
  424.      * @param array  $context The context
  425.      * @param array  $blocks  The current set of blocks
  426.      *
  427.      * @return iterable<scalar|\Stringable|null>
  428.      */
  429.     public function yieldParentBlock($name, array $context, array $blocks = []): iterable
  430.     {
  431.         if (isset($this->traits[$name])) {
  432.             yield from $this->traits[$name][0]->yieldBlock($this->traitAliases[$name] ?? $name$context$blocksfalse);
  433.         } elseif ($parent $this->getParent($context)) {
  434.             yield from $parent->unwrap()->yieldBlock($name$context$blocksfalse);
  435.         } else {
  436.             throw new RuntimeError(\sprintf('The template has no parent and no traits defining the "%s" block.'$name), -1$this->getSourceContext());
  437.         }
  438.     }
  439.     protected function hasMacro(string $name, array $context): bool
  440.     {
  441.         if (method_exists($this$name)) {
  442.             return true;
  443.         }
  444.         if (!$parent $this->getParent($context)) {
  445.             return false;
  446.         }
  447.         return $parent->hasMacro($name$context);
  448.     }
  449.     protected function getTemplateForMacro(string $name, array $contextint $lineSource $source): self
  450.     {
  451.         if (method_exists($this$name)) {
  452.             $this->ensureSecurityChecked();
  453.             return $this;
  454.         }
  455.         $parent $this;
  456.         while ($parent $parent->getParent($context)) {
  457.             if (method_exists($parent$name)) {
  458.                 $parent->ensureSecurityChecked();
  459.                 return $parent;
  460.             }
  461.         }
  462.         throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".'substr($name\strlen('macro_')), $this->getTemplateName()), $line$source);
  463.     }
  464.     /**
  465.      * Runs the sandbox security check against the current sandbox state.
  466.      *
  467.      * @internal
  468.      */
  469.     public function ensureSecurityChecked(): void
  470.     {
  471.     }
  472.     /**
  473.      * Auto-generated method to display the template with the given context.
  474.      *
  475.      * @param array $context An array of parameters to pass to the template
  476.      * @param array $blocks  An array of blocks to pass to the template
  477.      *
  478.      * @return iterable<scalar|\Stringable|null>
  479.      */
  480.     abstract protected function doDisplay(array $context, array $blocks = []): iterable;
  481. }