vendor/symfony/symfony/src/Symfony/Component/Templating/PhpEngine.php line 113

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Templating;
  11. use Symfony\Component\Templating\Helper\HelperInterface;
  12. use Symfony\Component\Templating\Loader\LoaderInterface;
  13. use Symfony\Component\Templating\Storage\FileStorage;
  14. use Symfony\Component\Templating\Storage\Storage;
  15. use Symfony\Component\Templating\Storage\StringStorage;
  16. /**
  17.  * PhpEngine is an engine able to render PHP templates.
  18.  *
  19.  * @author Fabien Potencier <fabien@symfony.com>
  20.  */
  21. class PhpEngine implements EngineInterface, \ArrayAccess
  22. {
  23.     protected $loader;
  24.     protected $current;
  25.     /**
  26.      * @var HelperInterface[]
  27.      */
  28.     protected $helpers = [];
  29.     protected $parents = [];
  30.     protected $stack = [];
  31.     protected $charset 'UTF-8';
  32.     protected $cache = [];
  33.     protected $escapers = [];
  34.     protected static $escaperCache = [];
  35.     protected $globals = [];
  36.     protected $parser;
  37.     private $evalTemplate;
  38.     private $evalParameters;
  39.     /**
  40.      * @param HelperInterface[] $helpers An array of helper instances
  41.      */
  42.     public function __construct(TemplateNameParserInterface $parserLoaderInterface $loader, array $helpers = [])
  43.     {
  44.         $this->parser $parser;
  45.         $this->loader $loader;
  46.         $this->addHelpers($helpers);
  47.         $this->initializeEscapers();
  48.         foreach ($this->escapers as $context => $escaper) {
  49.             $this->setEscaper($context$escaper);
  50.         }
  51.     }
  52.     /**
  53.      * {@inheritdoc}
  54.      *
  55.      * @throws \InvalidArgumentException if the template does not exist
  56.      */
  57.     public function render($name, array $parameters = [])
  58.     {
  59.         $storage $this->load($name);
  60.         $key hash('sha256'serialize($storage));
  61.         $this->current $key;
  62.         $this->parents[$key] = null;
  63.         // attach the global variables
  64.         $parameters array_replace($this->getGlobals(), $parameters);
  65.         // render
  66.         if (false === $content $this->evaluate($storage$parameters)) {
  67.             throw new \RuntimeException(sprintf('The template "%s" cannot be rendered.'$this->parser->parse($name)));
  68.         }
  69.         // decorator
  70.         if ($this->parents[$key]) {
  71.             $slots $this->get('slots');
  72.             $this->stack[] = $slots->get('_content');
  73.             $slots->set('_content'$content);
  74.             $content $this->render($this->parents[$key], $parameters);
  75.             $slots->set('_content'array_pop($this->stack));
  76.         }
  77.         return $content;
  78.     }
  79.     /**
  80.      * {@inheritdoc}
  81.      */
  82.     public function exists($name)
  83.     {
  84.         try {
  85.             $this->load($name);
  86.         } catch (\InvalidArgumentException $e) {
  87.             return false;
  88.         }
  89.         return true;
  90.     }
  91.     /**
  92.      * {@inheritdoc}
  93.      */
  94.     public function supports($name)
  95.     {
  96.         $template $this->parser->parse($name);
  97.         return 'php' === $template->get('engine');
  98.     }
  99.     /**
  100.      * Evaluates a template.
  101.      *
  102.      * @param array $parameters An array of parameters to pass to the template
  103.      *
  104.      * @return string|false The evaluated template, or false if the engine is unable to render the template
  105.      *
  106.      * @throws \InvalidArgumentException
  107.      */
  108.     protected function evaluate(Storage $template, array $parameters = [])
  109.     {
  110.         $this->evalTemplate $template;
  111.         $this->evalParameters $parameters;
  112.         unset($template$parameters);
  113.         if (isset($this->evalParameters['this'])) {
  114.             throw new \InvalidArgumentException('Invalid parameter (this).');
  115.         }
  116.         if (isset($this->evalParameters['view'])) {
  117.             throw new \InvalidArgumentException('Invalid parameter (view).');
  118.         }
  119.         // the view variable is exposed to the require file below
  120.         $view $this;
  121.         if ($this->evalTemplate instanceof FileStorage) {
  122.             extract($this->evalParameters, \EXTR_SKIP);
  123.             $this->evalParameters null;
  124.             ob_start();
  125.             require $this->evalTemplate;
  126.             $this->evalTemplate null;
  127.             return ob_get_clean();
  128.         } elseif ($this->evalTemplate instanceof StringStorage) {
  129.             extract($this->evalParameters, \EXTR_SKIP);
  130.             $this->evalParameters null;
  131.             ob_start();
  132.             eval('; ?>'.$this->evalTemplate.'<?php ;');
  133.             $this->evalTemplate null;
  134.             return ob_get_clean();
  135.         }
  136.         return false;
  137.     }
  138.     /**
  139.      * Gets a helper value.
  140.      *
  141.      * @param string $name The helper name
  142.      *
  143.      * @return HelperInterface The helper value
  144.      *
  145.      * @throws \InvalidArgumentException if the helper is not defined
  146.      */
  147.     public function offsetGet($name)
  148.     {
  149.         return $this->get($name);
  150.     }
  151.     /**
  152.      * Returns true if the helper is defined.
  153.      *
  154.      * @param string $name The helper name
  155.      *
  156.      * @return bool true if the helper is defined, false otherwise
  157.      */
  158.     public function offsetExists($name)
  159.     {
  160.         return isset($this->helpers[$name]);
  161.     }
  162.     /**
  163.      * Sets a helper.
  164.      *
  165.      * @param HelperInterface $name  The helper instance
  166.      * @param string          $value An alias
  167.      */
  168.     public function offsetSet($name$value)
  169.     {
  170.         $this->set($name$value);
  171.     }
  172.     /**
  173.      * Removes a helper.
  174.      *
  175.      * @param string $name The helper name
  176.      *
  177.      * @throws \LogicException
  178.      */
  179.     public function offsetUnset($name)
  180.     {
  181.         throw new \LogicException(sprintf('You can\'t unset a helper (%s).'$name));
  182.     }
  183.     /**
  184.      * Adds some helpers.
  185.      *
  186.      * @param HelperInterface[] $helpers An array of helper
  187.      */
  188.     public function addHelpers(array $helpers)
  189.     {
  190.         foreach ($helpers as $alias => $helper) {
  191.             $this->set($helper, \is_int($alias) ? null $alias);
  192.         }
  193.     }
  194.     /**
  195.      * Sets the helpers.
  196.      *
  197.      * @param HelperInterface[] $helpers An array of helper
  198.      */
  199.     public function setHelpers(array $helpers)
  200.     {
  201.         $this->helpers = [];
  202.         $this->addHelpers($helpers);
  203.     }
  204.     /**
  205.      * Sets a helper.
  206.      *
  207.      * @param string $alias An alias
  208.      */
  209.     public function set(HelperInterface $helper$alias null)
  210.     {
  211.         $this->helpers[$helper->getName()] = $helper;
  212.         if (null !== $alias) {
  213.             $this->helpers[$alias] = $helper;
  214.         }
  215.         $helper->setCharset($this->charset);
  216.     }
  217.     /**
  218.      * Returns true if the helper if defined.
  219.      *
  220.      * @param string $name The helper name
  221.      *
  222.      * @return bool true if the helper is defined, false otherwise
  223.      */
  224.     public function has($name)
  225.     {
  226.         return isset($this->helpers[$name]);
  227.     }
  228.     /**
  229.      * Gets a helper value.
  230.      *
  231.      * @param string $name The helper name
  232.      *
  233.      * @return HelperInterface The helper instance
  234.      *
  235.      * @throws \InvalidArgumentException if the helper is not defined
  236.      */
  237.     public function get($name)
  238.     {
  239.         if (!isset($this->helpers[$name])) {
  240.             throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.'$name));
  241.         }
  242.         return $this->helpers[$name];
  243.     }
  244.     /**
  245.      * Decorates the current template with another one.
  246.      *
  247.      * @param string $template The decorator logical name
  248.      */
  249.     public function extend($template)
  250.     {
  251.         $this->parents[$this->current] = $template;
  252.     }
  253.     /**
  254.      * Escapes a string by using the current charset.
  255.      *
  256.      * @param mixed  $value   A variable to escape
  257.      * @param string $context The context name
  258.      *
  259.      * @return mixed The escaped value
  260.      */
  261.     public function escape($value$context 'html')
  262.     {
  263.         if (is_numeric($value)) {
  264.             return $value;
  265.         }
  266.         // If we deal with a scalar value, we can cache the result to increase
  267.         // the performance when the same value is escaped multiple times (e.g. loops)
  268.         if (is_scalar($value)) {
  269.             if (!isset(self::$escaperCache[$context][$value])) {
  270.                 self::$escaperCache[$context][$value] = $this->getEscaper($context)($value);
  271.             }
  272.             return self::$escaperCache[$context][$value];
  273.         }
  274.         return $this->getEscaper($context)($value);
  275.     }
  276.     /**
  277.      * Sets the charset to use.
  278.      *
  279.      * @param string $charset The charset
  280.      */
  281.     public function setCharset($charset)
  282.     {
  283.         if ('UTF8' === $charset strtoupper($charset)) {
  284.             $charset 'UTF-8'// iconv on Windows requires "UTF-8" instead of "UTF8"
  285.         }
  286.         $this->charset $charset;
  287.         foreach ($this->helpers as $helper) {
  288.             $helper->setCharset($this->charset);
  289.         }
  290.     }
  291.     /**
  292.      * Gets the current charset.
  293.      *
  294.      * @return string The current charset
  295.      */
  296.     public function getCharset()
  297.     {
  298.         return $this->charset;
  299.     }
  300.     /**
  301.      * Adds an escaper for the given context.
  302.      *
  303.      * @param string   $context The escaper context (html, js, ...)
  304.      * @param callable $escaper A PHP callable
  305.      */
  306.     public function setEscaper($context, callable $escaper)
  307.     {
  308.         $this->escapers[$context] = $escaper;
  309.         self::$escaperCache[$context] = [];
  310.     }
  311.     /**
  312.      * Gets an escaper for a given context.
  313.      *
  314.      * @param string $context The context name
  315.      *
  316.      * @return callable A PHP callable
  317.      *
  318.      * @throws \InvalidArgumentException
  319.      */
  320.     public function getEscaper($context)
  321.     {
  322.         if (!isset($this->escapers[$context])) {
  323.             throw new \InvalidArgumentException(sprintf('No registered escaper for context "%s".'$context));
  324.         }
  325.         return $this->escapers[$context];
  326.     }
  327.     /**
  328.      * @param string $name
  329.      * @param mixed  $value
  330.      */
  331.     public function addGlobal($name$value)
  332.     {
  333.         $this->globals[$name] = $value;
  334.     }
  335.     /**
  336.      * Returns the assigned globals.
  337.      *
  338.      * @return array
  339.      */
  340.     public function getGlobals()
  341.     {
  342.         return $this->globals;
  343.     }
  344.     /**
  345.      * Initializes the built-in escapers.
  346.      *
  347.      * Each function specifies a way for applying a transformation to a string
  348.      * passed to it. The purpose is for the string to be "escaped" so it is
  349.      * suitable for the format it is being displayed in.
  350.      *
  351.      * For example, the string: "It's required that you enter a username & password.\n"
  352.      * If this were to be displayed as HTML it would be sensible to turn the
  353.      * ampersand into '&amp;' and the apostrophe into '&aps;'. However if it were
  354.      * going to be used as a string in JavaScript to be displayed in an alert box
  355.      * it would be right to leave the string as-is, but c-escape the apostrophe and
  356.      * the new line.
  357.      *
  358.      * For each function there is a define to avoid problems with strings being
  359.      * incorrectly specified.
  360.      */
  361.     protected function initializeEscapers()
  362.     {
  363.         $flags = \ENT_QUOTES | \ENT_SUBSTITUTE;
  364.         $this->escapers = [
  365.             'html' =>
  366.                 /**
  367.                  * Runs the PHP function htmlspecialchars on the value passed.
  368.                  *
  369.                  * @param string $value The value to escape
  370.                  *
  371.                  * @return string the escaped value
  372.                  */
  373.                 function ($value) use ($flags) {
  374.                     // Numbers and Boolean values get turned into strings which can cause problems
  375.                     // with type comparisons (e.g. === or is_int() etc).
  376.                     return \is_string($value) ? htmlspecialchars($value$flags$this->getCharset(), false) : $value;
  377.                 },
  378.             'js' =>
  379.                 /**
  380.                  * A function that escape all non-alphanumeric characters
  381.                  * into their \xHH or \uHHHH representations.
  382.                  *
  383.                  * @param string $value The value to escape
  384.                  *
  385.                  * @return string the escaped value
  386.                  */
  387.                 function ($value) {
  388.                     if ('UTF-8' != $this->getCharset()) {
  389.                         $value iconv($this->getCharset(), 'UTF-8'$value);
  390.                     }
  391.                     $callback = function ($matches) {
  392.                         $char $matches[0];
  393.                         // \xHH
  394.                         if (!isset($char[1])) {
  395.                             return '\\x'.substr('00'.bin2hex($char), -2);
  396.                         }
  397.                         // \uHHHH
  398.                         $char iconv('UTF-8''UTF-16BE'$char);
  399.                         return '\\u'.substr('0000'.bin2hex($char), -4);
  400.                     };
  401.                     if (null === $value preg_replace_callback('#[^\p{L}\p{N} ]#u'$callback$value)) {
  402.                         throw new \InvalidArgumentException('The string to escape is not a valid UTF-8 string.');
  403.                     }
  404.                     if ('UTF-8' != $this->getCharset()) {
  405.                         $value iconv('UTF-8'$this->getCharset(), $value);
  406.                     }
  407.                     return $value;
  408.                 },
  409.         ];
  410.         self::$escaperCache = [];
  411.     }
  412.     /**
  413.      * Gets the loader associated with this engine.
  414.      *
  415.      * @return LoaderInterface A LoaderInterface instance
  416.      */
  417.     public function getLoader()
  418.     {
  419.         return $this->loader;
  420.     }
  421.     /**
  422.      * Loads the given template.
  423.      *
  424.      * @param string|TemplateReferenceInterface $name A template name or a TemplateReferenceInterface instance
  425.      *
  426.      * @return Storage A Storage instance
  427.      *
  428.      * @throws \InvalidArgumentException if the template cannot be found
  429.      */
  430.     protected function load($name)
  431.     {
  432.         $template $this->parser->parse($name);
  433.         $key $template->getLogicalName();
  434.         if (isset($this->cache[$key])) {
  435.             return $this->cache[$key];
  436.         }
  437.         $storage $this->loader->load($template);
  438.         if (false === $storage) {
  439.             throw new \InvalidArgumentException(sprintf('The template "%s" does not exist.'$template));
  440.         }
  441.         return $this->cache[$key] = $storage;
  442.     }
  443. }