2010-01-04 14:26:20 +00:00
< ? php
2010-08-20 22:09:55 +01:00
namespace Symfony\Component\Templating ;
2010-01-04 14:26:20 +00:00
2010-08-20 22:09:55 +01:00
use Symfony\Component\Templating\Loader\LoaderInterface ;
use Symfony\Component\Templating\Renderer\RendererInterface ;
use Symfony\Component\Templating\Helper\HelperInterface ;
2010-01-04 14:26:20 +00:00
/*
2010-04-07 01:51:29 +01:00
* This file is part of the Symfony package .
2010-01-04 14:26:20 +00:00
*
* ( c ) Fabien Potencier < fabien . potencier @ symfony - project . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
/**
* Engine is the main class of the templating component .
*
2010-10-17 12:45:15 +01:00
* @ author Fabien Potencier < fabien . potencier @ symfony - project . com >
2010-01-04 14:26:20 +00:00
*/
2010-08-19 14:52:47 +01:00
class Engine implements \ArrayAccess
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
protected $loader ;
protected $renderers ;
protected $current ;
protected $helpers ;
protected $parents ;
protected $stack ;
protected $charset ;
protected $cache ;
2010-11-23 09:22:48 +00:00
protected $escapers ;
2010-12-20 12:05:28 +00:00
protected $globals ;
2010-05-06 12:25:53 +01:00
/**
* Constructor .
*
2011-01-07 14:28:12 +00:00
* @ param LoaderInterface $loader A loader instance
* @ param array $helpers An array of helper instances
2010-05-06 12:25:53 +01:00
*/
2011-01-06 19:35:08 +00:00
public function __construct ( LoaderInterface $loader , array $helpers = array ())
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
$this -> loader = $loader ;
$this -> helpers = array ();
$this -> parents = array ();
$this -> stack = array ();
$this -> charset = 'UTF-8' ;
$this -> cache = array ();
2011-01-06 10:20:48 +00:00
$this -> globals = array ();
2011-01-07 14:28:12 +00:00
$this -> renderers = array ();
2010-05-06 12:25:53 +01:00
$this -> addHelpers ( $helpers );
2011-01-06 19:35:08 +00:00
$this -> initializeEscapers ();
foreach ( $this -> escapers as $context => $escaper ) {
$this -> setEscaper ( $context , $escaper );
}
}
public function setRenderers ( array $renderers = array ())
{
2011-01-07 14:28:12 +00:00
$this -> renderers = array ();
foreach ( $renderers as $name => $renderer ) {
$this -> setRenderer ( $name , $renderer );
2010-05-06 12:25:53 +01:00
}
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Renders a template .
*
* The template name is composed of segments separated by a colon ( : ) .
* By default , this engine knows how to parse templates with one or two segments :
*
* * index : The template logical name is index and the renderer is php
* * index : twig : The template logical name is index and the renderer is twig
*
* @ param string $name A template name
* @ param array $parameters An array of parameters to pass to the template
*
* @ return string The evaluated template as a string
*
* @ throws \InvalidArgumentException if the renderer does not exist or if the template does not exist
* @ throws \RuntimeException if the template cannot be rendered
*/
public function render ( $name , array $parameters = array ())
2010-01-04 14:26:20 +00:00
{
2011-01-13 10:16:45 +00:00
$template = $this -> load ( $name );
2010-05-06 12:25:53 +01:00
// renderer
2011-01-13 10:16:45 +00:00
$renderer = $template -> getRenderer ();
2010-05-06 12:25:53 +01:00
2011-01-13 10:16:45 +00:00
if ( ! isset ( $this -> renderers [ $renderer ])) {
2010-05-06 12:25:53 +01:00
throw new \InvalidArgumentException ( sprintf ( 'The renderer "%s" is not registered.' , $renderer ));
}
2010-10-21 07:57:23 +01:00
$this -> current = $name ;
$this -> parents [ $name ] = null ;
2010-12-20 12:05:28 +00:00
// Attach the global variables
$parameters = array_replace ( $this -> getGlobals (), $parameters );
2010-05-06 12:25:53 +01:00
// render
2010-05-07 15:09:11 +01:00
if ( false === $content = $this -> renderers [ $renderer ] -> evaluate ( $template , $parameters )) {
2010-05-06 12:25:53 +01:00
throw new \RuntimeException ( sprintf ( 'The template "%s" cannot be rendered (renderer: %s).' , $name , $renderer ));
}
// decorator
2010-05-07 15:09:11 +01:00
if ( $this -> parents [ $name ]) {
2010-05-06 12:25:53 +01:00
$slots = $this -> get ( 'slots' );
$this -> stack [] = $slots -> get ( '_content' );
$slots -> set ( '_content' , $content );
2011-01-13 10:16:45 +00:00
// a decorator must use the same renderer as its children
$parent = $this -> load ( $this -> parents [ $name ]);
if ( $renderer !== $parentRenderer = ( $parent -> getRenderer () ? $parent -> getRenderer () : $renderer )) {
throw new \LogicException ( sprintf ( 'Template "%s" extends "%s" but a "%s" template cannot extend a "%s" one.' , $name , $this -> parents [ $name ], $renderer , $parentRenderer ));
}
2010-05-06 12:25:53 +01:00
$content = $this -> render ( $this -> parents [ $name ], $parameters );
$slots -> set ( '_content' , array_pop ( $this -> stack ));
}
return $content ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
2010-08-29 15:09:02 +01:00
/**
* Returns true if the template exists .
*
* @ param string $name A template name
*
* @ return Boolean true if the template exists , false otherwise
*/
public function exists ( $name )
{
2011-01-13 12:18:48 +00:00
try {
$this -> load ( $name );
} catch ( \InvalidArgumentException $e ) {
return false ;
}
return true ;
2010-11-21 08:33:03 +00:00
}
2010-08-29 15:09:02 +01:00
2010-11-21 08:33:03 +00:00
/**
2011-01-13 12:18:56 +00:00
* Loads the given template .
2010-11-21 08:33:03 +00:00
*
* @ param string $name A template name
*
2011-01-13 12:18:56 +00:00
* @ return Storage A Storage instance
*
* @ throws \InvalidArgumentException if the template cannot be found
2010-11-21 08:33:03 +00:00
*/
public function load ( $name )
{
if ( isset ( $this -> cache [ $name ])) {
2011-01-13 10:16:45 +00:00
return $this -> cache [ $name ];
}
2010-08-29 15:09:02 +01:00
2011-01-13 10:16:45 +00:00
list ( $tpl , $options ) = $this -> splitTemplateName ( $name );
2010-08-29 15:09:02 +01:00
2011-01-13 10:16:45 +00:00
// load
$template = $this -> loader -> load ( $tpl , $options );
2010-11-21 08:33:03 +00:00
2011-01-13 10:16:45 +00:00
if ( false === $template ) {
throw new \InvalidArgumentException ( sprintf ( 'The template "%s" does not exist (renderer: %s).' , $name , $options [ 'renderer' ]));
2010-11-21 08:33:03 +00:00
}
2010-08-29 15:09:02 +01:00
2011-01-13 10:16:45 +00:00
$this -> cache [ $name ] = $template ;
2010-11-21 08:33:03 +00:00
return $template ;
2010-08-29 15:09:02 +01:00
}
2010-05-06 12:25:53 +01:00
/**
* Gets a helper value .
*
* @ param string $name The helper name
*
* @ return mixed The helper value
*
* @ throws \InvalidArgumentException if the helper is not defined
*/
2010-08-19 14:52:47 +01:00
public function offsetGet ( $name )
2010-05-06 12:25:53 +01:00
{
return $this -> $name = $this -> get ( $name );
2010-01-04 14:26:20 +00:00
}
2010-05-20 12:50:14 +01:00
/**
2010-05-25 17:16:08 +01:00
* Returns true if the helper is defined .
2010-05-20 12:50:14 +01:00
*
* @ param string $name The helper name
*
* @ return Boolean true if the helper is defined , false otherwise
*/
2010-08-19 14:52:47 +01:00
public function offsetExists ( $name )
2010-05-20 12:50:14 +01:00
{
return isset ( $this -> helpers [ $name ]);
}
2010-08-19 14:52:47 +01:00
/**
* Sets a helper .
*
* @ param HelperInterface $value The helper instance
* @ param string $alias An alias
*/
public function offsetSet ( $name , $value )
{
$this -> set ( $name , $value );
}
/**
* Removes a helper .
*
* @ param string $name The helper name
*/
public function offsetUnset ( $name )
{
throw new \LogicException ( sprintf ( 'You can\'t unset a helper (%s).' , $name ));
}
2010-05-06 12:25:53 +01:00
/**
* @ param Helper [] $helpers An array of helper
*/
public function addHelpers ( array $helpers = array ())
{
2010-05-07 15:09:11 +01:00
foreach ( $helpers as $alias => $helper ) {
2010-05-06 12:25:53 +01:00
$this -> set ( $helper , is_int ( $alias ) ? null : $alias );
}
}
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
/**
* Sets a helper .
*
* @ param HelperInterface $value The helper instance
* @ param string $alias An alias
*/
public function set ( HelperInterface $helper , $alias = null )
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
$this -> helpers [ $helper -> getName ()] = $helper ;
2010-05-07 15:09:11 +01:00
if ( null !== $alias ) {
2010-05-06 12:25:53 +01:00
$this -> helpers [ $alias ] = $helper ;
}
$helper -> setCharset ( $this -> charset );
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Returns true if the helper if defined .
*
* @ param string $name The helper name
*
* @ return Boolean true if the helper is defined , false otherwise
*/
public function has ( $name )
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
return isset ( $this -> helpers [ $name ]);
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Gets a helper value .
*
* @ param string $name The helper name
*
* @ return HelperInterface The helper instance
*
* @ throws \InvalidArgumentException if the helper is not defined
*/
public function get ( $name )
2010-01-04 14:26:20 +00:00
{
2010-05-07 15:09:11 +01:00
if ( ! isset ( $this -> helpers [ $name ])) {
2010-05-06 12:25:53 +01:00
throw new \InvalidArgumentException ( sprintf ( 'The helper "%s" is not defined.' , $name ));
}
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
return $this -> helpers [ $name ];
}
2010-01-04 14:26:20 +00:00
2010-05-06 12:25:53 +01:00
/**
* Decorates the current template with another one .
*
* @ param string $template The decorator logical name
*/
public function extend ( $template )
{
$this -> parents [ $this -> current ] = $template ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Escapes a string by using the current charset .
*
2010-11-23 09:22:48 +00:00
* @ param mixed $value A variable to escape
2010-05-06 12:25:53 +01:00
*
2010-11-23 09:22:48 +00:00
* @ return string The escaped value
2010-05-06 12:25:53 +01:00
*/
2010-11-23 09:22:48 +00:00
public function escape ( $value , $context = 'html' )
2010-01-04 14:26:20 +00:00
{
2010-11-23 09:22:48 +00:00
return call_user_func ( $this -> getEscaper ( $context ), $value );
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Sets the charset to use .
*
* @ param string $charset The charset
*/
public function setCharset ( $charset )
2010-01-04 14:26:20 +00:00
{
2010-05-06 12:25:53 +01:00
$this -> charset = $charset ;
2010-01-04 14:26:20 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Gets the current charset .
*
* @ return string The current charset
*/
public function getCharset ()
2010-02-05 14:54:34 +00:00
{
2010-05-06 12:25:53 +01:00
return $this -> charset ;
2010-02-05 14:54:34 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Gets the loader associated with this engine .
*
* @ return LoaderInterface A LoaderInterface instance
*/
public function getLoader ()
2010-01-28 06:56:34 +00:00
{
2010-05-06 12:25:53 +01:00
return $this -> loader ;
2010-01-28 06:56:34 +00:00
}
2010-05-06 12:25:53 +01:00
/**
* Sets a template renderer .
*
* @ param string $name The renderer name
* @ param RendererInterface $renderer A RendererInterface instance
*/
public function setRenderer ( $name , RendererInterface $renderer )
2010-01-28 06:56:34 +00:00
{
2010-05-06 12:25:53 +01:00
$this -> renderers [ $name ] = $renderer ;
$renderer -> setEngine ( $this );
2010-01-28 06:56:34 +00:00
}
2011-01-06 13:45:12 +00:00
/**
* Converts a short template notation to a template name and an array of options .
*
* @ param string $name A short template template
* @ param array $defaults An array of default options
*
* @ return array An array composed of the template name and an array of options
*/
2010-05-20 16:23:33 +01:00
public function splitTemplateName ( $name )
2010-05-06 12:25:53 +01:00
{
2010-05-07 15:09:11 +01:00
if ( false !== $pos = strpos ( $name , ':' )) {
2010-05-06 12:25:53 +01:00
$renderer = substr ( $name , $pos + 1 );
$name = substr ( $name , 0 , $pos );
2010-05-07 15:09:11 +01:00
} else {
2010-05-06 12:25:53 +01:00
$renderer = 'php' ;
}
return array ( $name , array ( 'renderer' => $renderer ));
}
2010-11-23 09:22:48 +00:00
/**
* Adds an escaper for the given context .
*
* @ param string $name The escaper context ( html , js , ... )
* @ param mixed $escaper A PHP callable
*/
public function setEscaper ( $context , $escaper )
{
$this -> escapers [ $context ] = $escaper ;
}
/**
* Gets an escaper for a given context .
*
* @ param string $name The context name
*
* @ return mixed $escaper A PHP callable
*/
public function getEscaper ( $context )
{
if ( ! isset ( $this -> escapers [ $context ])) {
throw new \InvalidArgumentException ( sprintf ( 'No registered escaper for context "%s".' , $context ));
}
return $this -> escapers [ $context ];
}
2010-12-20 12:05:28 +00:00
/**
* @ param string $name
* @ param mixed $value
*/
public function addGlobal ( $name , $value )
{
$this -> globals [ $name ] = $value ;
}
/**
2011-01-06 10:20:48 +00:00
* Returns the assigned globals .
2010-12-20 12:05:28 +00:00
*
* @ return array
*/
public function getGlobals ()
{
return $this -> globals ;
}
2010-11-23 09:22:48 +00:00
/**
* Initializes the built - in escapers .
*
* Each function specifies a way for applying a transformation to a string
* passed to it . The purpose is for the string to be " escaped " so it is
* suitable for the format it is being displayed in .
*
* For example , the string : " It's required that you enter a username & password. \n "
* If this were to be displayed as HTML it would be sensible to turn the
* ampersand into '&' and the apostrophe into '&aps;' . However if it were
* going to be used as a string in JavaScript to be displayed in an alert box
* it would be right to leave the string as - is , but c - escape the apostrophe and
* the new line .
*
* For each function there is a define to avoid problems with strings being
* incorrectly specified .
*/
protected function initializeEscapers ()
{
$that = $this ;
$this -> escapers = array (
'html' =>
/**
* Runs the PHP function htmlspecialchars on the value passed .
*
* @ param string $value the value to escape
*
* @ return string the escaped value
*/
function ( $value ) use ( $that )
{
// Numbers and boolean values get turned into strings which can cause problems
// with type comparisons (e.g. === or is_int() etc).
return is_string ( $value ) ? htmlspecialchars ( $value , ENT_QUOTES , $that -> getCharset (), false ) : $value ;
},
'js' =>
/**
* A function that escape all non - alphanumeric characters
* into their \xHH or \uHHHH representations
*
* @ param string $value the value to escape
* @ return string the escaped value
*/
function ( $value ) use ( $that )
{
if ( 'UTF-8' != $that -> getCharset ()) {
$string = $that -> convertEncoding ( $string , 'UTF-8' , $that -> getCharset ());
}
$callback = function ( $matches ) use ( $that )
{
$char = $matches [ 0 ];
// \xHH
if ( ! isset ( $char [ 1 ])) {
return '\\x' . substr ( '00' . bin2hex ( $char ), - 2 );
}
// \uHHHH
$char = $that -> convertEncoding ( $char , 'UTF-16BE' , 'UTF-8' );
return '\\u' . substr ( '0000' . bin2hex ( $char ), - 4 );
};
if ( null === $string = preg_replace_callback ( '#[^\p{L}\p{N} ]#u' , $callback , $string )) {
throw new \InvalidArgumentException ( 'The string to escape is not a valid UTF-8 string.' );
}
if ( 'UTF-8' != $that -> getCharset ()) {
$string = $that -> convertEncoding ( $string , $that -> getCharset (), 'UTF-8' );
}
return $string ;
},
);
}
public function convertEncoding ( $string , $to , $from )
{
if ( function_exists ( 'iconv' )) {
return iconv ( $from , $to , $string );
} elseif ( function_exists ( 'mb_convert_encoding' )) {
return mb_convert_encoding ( $string , $to , $from );
} else {
throw new \RuntimeException ( 'No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).' );
}
}
2010-01-04 14:26:20 +00:00
}