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