refactored template name parser to occur independently of the loaders

This commit is contained in:
Fabien Potencier 2011-01-26 14:53:12 +01:00
parent 956857119b
commit db2f2b1315
27 changed files with 253 additions and 187 deletions

View File

@ -6,7 +6,7 @@
<parameters>
<parameter key="templating.engine.delegating.class">Symfony\Bundle\FrameworkBundle\Templating\DelegatingEngine</parameter>
<parameter key="templating.name_parser.class">Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateNameParser</parameter>
<parameter key="templating.name_parser.class">Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser</parameter>
<parameter key="templating.cache_warmer.template_paths.class">Symfony\Bundle\FrameworkBundle\Templating\CacheWarmer\TemplatePathsCacheWarmer</parameter>
<parameter key="templating.locator.class">Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator</parameter>
<parameter key="templating.locator.cached.class">Symfony\Bundle\FrameworkBundle\Templating\Loader\CachedTemplateLocator</parameter>
@ -30,7 +30,6 @@
<service id="templating.locator" class="%templating.locator.class%" public="false">
<argument type="service" id="kernel" />
<argument type="service" id="templating.name_parser" />
<argument>%kernel.root_dir%</argument>
</service>

View File

@ -20,6 +20,7 @@
<services>
<service id="templating.engine.php" class="%templating.engine.php.class%" public="false">
<argument type="service" id="templating.name_parser" />
<argument type="service" id="service_container" />
<argument type="service" id="templating.loader" />
<call method="setCharset"><argument>%kernel.charset%</argument></call>

View File

@ -70,34 +70,44 @@ class TemplatePathsCacheWarmer extends CacheWarmer
$finder = new Finder();
foreach ($finder->files()->followLinks()->in($dir) as $file) {
list($category, $template) = $this->parseTemplateName($file, $prefix.'/');
$name = sprintf('%s:%s:%s', $bundle->getName(), $category, $template);
$resource = '@'.$bundle->getName().$prefix.'/'.$category.'/'.$template;
if (false !== $template = $this->parseTemplateName($file, $prefix.'/', $bundle->getName())) {
$resource = '@'.$template['bundle'].'/Resources/views/'.$template['controller'].'/'.$template['name'].'.'.$template['format'].'.'.$template['engine'];
$templates[$name] = $this->kernel->locateResource($resource, $this->rootDir);
$templates[md5(serialize($template))] = $this->kernel->locateResource($resource, $this->rootDir);
}
}
}
if (is_dir($this->rootDir)) {
$finder = new Finder();
foreach ($finder->files()->followLinks()->in($this->rootDir) as $file) {
list($category, $template) = $this->parseTemplateName($file, strtr($this->rootDir, '\\', '/').'/');
$templates[sprintf(':%s:%s', $category, $template)] = (string) $file;
if (false !== $template = $this->parseTemplateName($file, strtr($this->rootDir, '\\', '/').'/')) {
$templates[md5(serialize($template))] = (string) $file;
}
}
}
return $templates;
}
protected function parseTemplateName($file, $prefix)
protected function parseTemplateName($file, $prefix, $bundle = '')
{
$path = strtr($file->getPathname(), '\\', '/');
list(, $tmp) = explode($prefix, $path, 2);
$parts = explode('/', strtr($tmp, '\\', '/'));
$template = array_pop($parts);
return array(implode('/', $parts), $template);
$elements = explode('.', array_pop($parts));
if (3 !== count($elements)) {
return false;
}
return array(
'bundle' => $bundle,
'controller' => implode('/', $parts),
'name' => $elements[0],
'format' => $elements[1],
'engine' => $elements[2],
);
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Loader;
/**
* CachedTemplateLocator locates templates in the cache.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
@ -31,12 +32,21 @@ class CachedTemplateLocator implements TemplateLocatorInterface
$this->templates = require $cache;
}
public function locate($name)
/**
* Locates a template on the filesystem.
*
* @param array $template The template name as an array
*
* @return string An absolute file name
*/
public function locate($template)
{
if (!isset($this->templates[$name])) {
throw new \InvalidArgumentException(sprintf('Unable to find template "%s".', $name));
$key = md5(serialize($template));
if (!isset($this->templates[$key])) {
throw new \InvalidArgumentException(sprintf('Unable to find template "%s".', var_export($template, true)));
}
return $this->templates[$name];
return $this->templates[$key];
}
}

View File

@ -36,7 +36,7 @@ class FilesystemLoader implements LoaderInterface
/**
* Loads a template.
*
* @param string $template The logical template name
* @param array $template The template name as an array
*
* @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise
*/
@ -52,7 +52,7 @@ class FilesystemLoader implements LoaderInterface
/**
* Returns true if the template is still fresh.
*
* @param string $template The template name
* @param array $template The template name as an array
* @param timestamp $time The last modification time of the cached template
*/
public function isFresh($template, $time)

View File

@ -11,63 +11,61 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Loader;
use Symfony\Component\Templating\Loader\TemplateNameParserInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* TemplateLocator locates templates in bundles.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class TemplateLocator implements TemplateLocatorInterface
{
protected $kernel;
protected $parser;
protected $path;
protected $cache;
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param string $path A global fallback path
* @param KernelInterface $kernel A KernelInterface instance
* @param string $path A global fallback path
*/
public function __construct(KernelInterface $kernel, TemplateNameParserInterface $parser, $path)
public function __construct(KernelInterface $kernel, $path)
{
$this->kernel = $kernel;
$this->path = $path;
$this->parser = $parser;
$this->cache = array();
}
public function locate($name)
/**
* Locates a template on the filesystem.
*
* @param array $template The template name as an array
*
* @return string An absolute file name
*/
public function locate($template)
{
// normalize name
$name = str_replace(':/' , ':', preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')));
$key = md5(serialize($template));
if (isset($this->cache[$name])) {
return $this->cache[$name];
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
if (false !== strpos($name, '..')) {
throw new \RuntimeException(sprintf('Template name "%s" contains invalid characters.', $name));
}
$parameters = $this->parser->parse($name);
$resource = $parameters['bundle'].'/Resources/views/'.$parameters['controller'].'/'.$parameters['name'].'.'.$parameters['format'].'.'.$parameters['renderer'];
if (!$parameters['bundle']) {
if (is_file($file = $this->path.'/views/'.$parameters['controller'].'/'.$parameters['name'].'.'.$parameters['format'].'.'.$parameters['renderer'])) {
return $this->cache[$name] = $file;
if (!$template['bundle']) {
if (is_file($file = $this->path.'/views/'.$template['controller'].'/'.$template['name'].'.'.$template['format'].'.'.$template['engine'])) {
return $this->cache[$key] = $file;
}
throw new \InvalidArgumentException(sprintf('Unable to find template "%s" in "%s".', $name, $this->path));
throw new \InvalidArgumentException(sprintf('Unable to find template "%s" in "%s".', var_export($template, true), $this->path));
}
$resource = $template['bundle'].'/Resources/views/'.$template['controller'].'/'.$template['name'].'.'.$template['format'].'.'.$template['engine'];
try {
return $this->kernel->locateResource('@'.$resource, $this->path);
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('Unable to find template "%s".', $name, $this->path), 0, $e);
throw new \InvalidArgumentException(sprintf('Unable to find template "%s".', var_export($template, true), $this->path), 0, $e);
}
}
}

View File

@ -12,10 +12,18 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Loader;
/**
* Interfaces for classes that locates templates on disk
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface TemplateLocatorInterface
{
function locate($name);
/**
* Locates a template on the filesystem.
*
* @param array $template The template name as an array
*
* @return string An absolute file name
*/
function locate($template);
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Templating;
use Symfony\Component\Templating\PhpEngine as BasePhpEngine;
use Symfony\Component\Templating\Loader\LoaderInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
@ -28,14 +29,15 @@ class PhpEngine extends BasePhpEngine implements EngineInterface
/**
* Constructor.
*
* @param ContainerInterface $container The DI container
* @param LoaderInterface $loader A loader instance
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param ContainerInterface $container The DI container
* @param LoaderInterface $loader A loader instance
*/
public function __construct(ContainerInterface $container, LoaderInterface $loader)
public function __construct(TemplateNameParserInterface $parser, ContainerInterface $container, LoaderInterface $loader)
{
$this->container = $container;
parent::__construct($loader);
parent::__construct($parser, $loader);
}
/**

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Templating\Loader;
namespace Symfony\Bundle\FrameworkBundle\Templating;
use Symfony\Component\Templating\Loader\TemplateNameParser as BaseTemplateNameParser;
use Symfony\Component\Templating\TemplateNameParser as BaseTemplateNameParser;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* TemplateNameParser parsers template name from the short notation
* "bundle:section:template.renderer.format" to an array of
* "bundle:section:template.engine.format" to an array of
* template parameters.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
@ -40,14 +40,25 @@ class TemplateNameParser extends BaseTemplateNameParser
*/
public function parse($name)
{
if (is_array($name)) {
return $name;
}
// normalize name
$name = str_replace(':/' , ':', preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')));
if (false !== strpos($name, '..')) {
throw new \RuntimeException(sprintf('Template name "%s" contains invalid characters.', $name));
}
$parts = explode(':', $name);
if (3 !== count($parts)) {
throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.renderer.format").', $name));
throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.engine.format").', $name));
}
$elements = explode('.', $parts[2]);
if (3 !== count($elements)) {
throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.renderer.format").', $name));
throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.engine.format").', $name));
}
$parameters = array(
@ -55,7 +66,7 @@ class TemplateNameParser extends BaseTemplateNameParser
'controller' => $parts[1],
'name' => $elements[0],
'format' => $elements[1],
'renderer' => $elements[2],
'engine' => $elements[2],
);
if ($parameters['bundle']) {

View File

@ -9,10 +9,10 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Loader;
namespace Symfony\Bundle\FrameworkBundle\Tests\Templating;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Tests\Kernel;
class TemplateNameParserTest extends TestCase
@ -32,13 +32,13 @@ class TemplateNameParserTest extends TestCase
public function getParseTests()
{
return array(
array('FooBundle:Post:index.html.php', array('name' => 'index', 'bundle' => 'FooBundle', 'controller' => 'Post', 'renderer' => 'php', 'format' => 'html')),
array('FooBundle:Post:index.html.twig', array('name' => 'index', 'bundle' => 'FooBundle', 'controller' => 'Post', 'renderer' => 'twig', 'format' => 'html')),
array('FooBundle:Post:index.xml.php', array('name' => 'index', 'bundle' => 'FooBundle', 'controller' => 'Post', 'renderer' => 'php', 'format' => 'xml')),
array('SensioFooBundle:Post:index.html.php', array('name' => 'index', 'bundle' => 'SensioFooBundle', 'controller' => 'Post', 'renderer' => 'php', 'format' => 'html')),
array('SensioCmsFooBundle:Post:index.html.php', array('name' => 'index', 'bundle' => 'SensioCmsFooBundle', 'controller' => 'Post', 'renderer' => 'php', 'format' => 'html')),
array(':Post:index.html.php',array('name' => 'index', 'bundle' => '', 'controller' => 'Post', 'renderer' => 'php', 'format' => 'html')),
array('::index.html.php', array('name' => 'index', 'bundle' => '', 'controller' => '', 'renderer' => 'php', 'format' => 'html')),
array('FooBundle:Post:index.html.php', array('name' => 'index', 'bundle' => 'FooBundle', 'controller' => 'Post', 'engine' => 'php', 'format' => 'html')),
array('FooBundle:Post:index.html.twig', array('name' => 'index', 'bundle' => 'FooBundle', 'controller' => 'Post', 'engine' => 'twig', 'format' => 'html')),
array('FooBundle:Post:index.xml.php', array('name' => 'index', 'bundle' => 'FooBundle', 'controller' => 'Post', 'engine' => 'php', 'format' => 'xml')),
array('SensioFooBundle:Post:index.html.php', array('name' => 'index', 'bundle' => 'SensioFooBundle', 'controller' => 'Post', 'engine' => 'php', 'format' => 'html')),
array('SensioCmsFooBundle:Post:index.html.php', array('name' => 'index', 'bundle' => 'SensioCmsFooBundle', 'controller' => 'Post', 'engine' => 'php', 'format' => 'html')),
array(':Post:index.html.php',array('name' => 'index', 'bundle' => '', 'controller' => 'Post', 'engine' => 'php', 'format' => 'html')),
array('::index.html.php', array('name' => 'index', 'bundle' => '', 'controller' => '', 'engine' => 'php', 'format' => 'html')),
);
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\TwigBundle\Loader;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocatorInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
/**
* FilesystemLoader extends the default Twig filesystem loader
@ -21,14 +22,20 @@ use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocatorInterface;
*/
class FilesystemLoader implements \Twig_LoaderInterface
{
protected $locator;
protected $parser;
protected $cache;
/**
* Constructor.
*
* @param TemplateLocator $locator A TemplateLocator instance
*/
public function __construct(TemplateLocatorInterface $locator)
public function __construct(TemplateLocatorInterface $locator, TemplateNameParserInterface $parser)
{
$this->locator = $locator;
$this->parser = $parser;
$this->cache = array();
}
/**
@ -68,10 +75,19 @@ class FilesystemLoader implements \Twig_LoaderInterface
protected function findTemplate($name)
{
if (!is_array($name)) {
$name = $this->parser->parse($name);
}
$key = md5(serialize($name));
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
if (false === $file = $this->locator->locate($name)) {
throw new \Twig_Error_Loader(sprintf('Unable to find template "%s".', $name));
}
return $file;
return $this->cache[$key] = $file;
}
}

View File

@ -32,11 +32,13 @@
<service id="twig.loader" class="%twig.loader.class%">
<argument type="service" id="templating.locator" />
<argument type="service" id="templating.name_parser" />
</service>
<service id="templating.engine.twig" class="%templating.engine.twig.class%" public="false">
<argument type="service" id="service_container" />
<argument type="service" id="twig" />
<argument type="service" id="service_container" />
<argument type="service" id="templating.name_parser" />
<argument type="service" id="twig.globals" />
</service>

View File

@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Bundle\TwigBundle\GlobalVariables;
class TwigEngineTest extends TestCase
@ -24,7 +25,7 @@ class TwigEngineTest extends TestCase
{
$environment = $this->getTwigEnvironment();
$container = $this->getContainer();
$engine = new TwigEngine($container, $environment, $app = new GlobalVariables($container));
$engine = new TwigEngine($environment, $container, new TemplateNameParser(), $app = new GlobalVariables($container));
$template = $this->getMock('\Twig_TemplateInterface');
@ -43,7 +44,7 @@ class TwigEngineTest extends TestCase
{
$environment = $this->getTwigEnvironment();
$container = new Container();
$engine = new TwigEngine($container, $environment, new GlobalVariables($container));
$engine = new TwigEngine($environment, $container, new TemplateNameParser(), new GlobalVariables($container));
$template = $this->getMock('\Twig_TemplateInterface');
@ -88,4 +89,4 @@ class TwigEngineTest extends TestCase
->setMethods(array('loadTemplate'))
->getMock();
}
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\TwigBundle;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -24,18 +25,21 @@ class TwigEngine implements EngineInterface
{
protected $environment;
protected $container;
protected $parser;
/**
* Constructor.
*
* @param ContainerInterface $container The DI container
* @param \Twig_Environment $environment A \Twig_Environment instance
* @param GlobalVariables $globals A GlobalVariables instance
* @param \Twig_Environment $environment A \Twig_Environment instance
* @param ContainerInterface $container The DI container
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param GlobalVariables $globals A GlobalVariables instance
*/
public function __construct(ContainerInterface $container, \Twig_Environment $environment, GlobalVariables $globals)
public function __construct(\Twig_Environment $environment, ContainerInterface $container, TemplateNameParserInterface $parser, GlobalVariables $globals)
{
$this->container = $container;
$this->environment = $environment;
$this->container = $container;
$this->parser = $parser;
$environment->addGlobal('app', $globals);
}
@ -43,8 +47,8 @@ class TwigEngine implements EngineInterface
/**
* Renders a template.
*
* @param string $name A template name
* @param array $parameters An array of parameters to pass to the template
* @param mixed $name A template name
* @param array $parameters An array of parameters to pass to the template
*
* @return string The evaluated template as a string
*
@ -59,7 +63,7 @@ class TwigEngine implements EngineInterface
/**
* Returns true if the template exists.
*
* @param string $name A template name
* @param mixed $name A template name
*
* @return Boolean true if the template exists, false otherwise
*/
@ -77,7 +81,7 @@ class TwigEngine implements EngineInterface
/**
* Loads the given template.
*
* @param string $name A template name
* @param mixed $name A template name
*
* @return \Twig_TemplateInterface A \Twig_TemplateInterface instance
*
@ -85,7 +89,7 @@ class TwigEngine implements EngineInterface
*/
public function load($name)
{
return $this->environment->loadTemplate($name);
return $this->environment->loadTemplate($this->parser->parse($name));
}
/**
@ -97,7 +101,9 @@ class TwigEngine implements EngineInterface
*/
public function supports($name)
{
return false !== strpos($name, '.twig');
$template = $this->parser->parse($name);
return 'twig' === $template['engine'];
}
/**

View File

@ -14,6 +14,17 @@ namespace Symfony\Component\Templating;
/**
* EngineInterface is the interface each engine must implement.
*
* All methods relies on a template name. A template name is a
* "logical" name for the template (an array), and as such it does not
* refers to a path on the filesystem (in fact, the template can be
* stored anywhere, like in a database).
*
* The methods should accept any name and if it is not an array, it should
* then use a TemplateNameParserInterface to convert the name to an array.
*
* Each template loader use the logical template name to look for
* the template.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface EngineInterface
@ -21,8 +32,8 @@ interface EngineInterface
/**
* Renders a template.
*
* @param string $name A template name
* @param array $parameters An array of parameters to pass to the template
* @param mixed $name A template name
* @param array $parameters An array of parameters to pass to the template
*
* @return string The evaluated template as a string
*

View File

@ -36,8 +36,6 @@ class CacheLoader extends Loader
*/
public function __construct(Loader $loader, $dir)
{
parent::__construct($loader->getTemplateNameParser());
$this->loader = $loader;
$this->dir = $dir;
}
@ -45,28 +43,26 @@ class CacheLoader extends Loader
/**
* Loads a template.
*
* @param string $template The logical template name
* @param array $template The template name as an array
*
* @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise
*/
public function load($template)
{
$parameters = $this->nameParser->parse($template);
$tmp = md5(serialize($parameters)).'.tpl';
$tmp = md5(serialize($template)).'.tpl';
$dir = $this->dir.DIRECTORY_SEPARATOR.substr($tmp, 0, 2);
$file = substr($tmp, 2);
$path = $dir.DIRECTORY_SEPARATOR.$file;
if (file_exists($path)) {
if (null !== $this->debugger) {
$this->debugger->log(sprintf('Fetching template "%s" from cache', $parameters['name']));
$this->debugger->log(sprintf('Fetching template "%s" from cache', $template['name']));
}
return new FileStorage($path);
}
if (false === $storage = $this->loader->load($parameters['name'], $parameters)) {
if (false === $storage = $this->loader->load($template)) {
return false;
}
@ -79,7 +75,7 @@ class CacheLoader extends Loader
file_put_contents($path, $content);
if (null !== $this->debugger) {
$this->debugger->log(sprintf('Storing template "%s" in cache', $parameters['name']));
$this->debugger->log(sprintf('Storing template "%s" in cache', $template['name']));
}
return new FileStorage($path);
@ -88,7 +84,7 @@ class CacheLoader extends Loader
/**
* Returns true if the template is still fresh.
*
* @param string $template The template name
* @param array $template The template name as an array
* @param timestamp $time The last modification time of the cached template
*/
public function isFresh($template, $time)

View File

@ -48,7 +48,7 @@ class ChainLoader extends Loader
/**
* Loads a template.
*
* @param string $template The logical template name
* @param array $template The template name as an array
*
* @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise
*/
@ -66,7 +66,7 @@ class ChainLoader extends Loader
/**
* Returns true if the template is still fresh.
*
* @param string $template The template name
* @param array $template The template name as an array
* @param timestamp $time The last modification time of the cached template
*/
public function isFresh($template, $time)

View File

@ -26,13 +26,10 @@ class FilesystemLoader extends Loader
/**
* Constructor.
*
* @param TemplateNameParserInterface $nameParser A TemplateNameParserInterface instance
* @param array $templatePathPatterns An array of path patterns to look for templates
* @param array $templatePathPatterns An array of path patterns to look for templates
*/
public function __construct(TemplateNameParserInterface $nameParser, $templatePathPatterns)
public function __construct($templatePathPatterns)
{
parent::__construct($nameParser);
if (!is_array($templatePathPatterns)) {
$templatePathPatterns = array($templatePathPatterns);
}
@ -43,20 +40,18 @@ class FilesystemLoader extends Loader
/**
* Loads a template.
*
* @param string $template The logical template name
* @param array $template The template name as an array
*
* @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise
*/
public function load($template)
{
$parameters = $this->nameParser->parse($template);
if (self::isAbsolutePath($parameters['name']) && file_exists($parameters['name'])) {
return new FileStorage($parameters['name']);
if (self::isAbsolutePath($template['name']) && file_exists($template['name'])) {
return new FileStorage($template['name']);
}
$replacements = array();
foreach ($parameters as $key => $value) {
foreach ($template as $key => $value) {
$replacements['%'.$key.'%'] = $value;
}
@ -87,7 +82,7 @@ class FilesystemLoader extends Loader
/**
* Returns true if the template is still fresh.
*
* @param string $template The template name
* @param array $template The template name as an array
* @param timestamp $time The last modification time of the cached template
*/
public function isFresh($template, $time)

View File

@ -21,27 +21,6 @@ use Symfony\Component\Templating\DebuggerInterface;
abstract class Loader implements LoaderInterface
{
protected $debugger;
protected $nameParser;
/**
* Constructor.
*
* @param TemplateNameParserInterface $nameParser A TemplateNameParserInterface instance
*/
public function __construct(TemplateNameParserInterface $nameParser)
{
$this->nameParser = $nameParser;
}
/**
* Gets the template name parser.
*
* @return TemplateNameParserInterface A TemplateNameParserInterface instance
*/
public function getTemplateNameParser()
{
return $this->nameParser;
}
/**
* Sets the debugger to use for this loader.

View File

@ -21,7 +21,7 @@ interface LoaderInterface
/**
* Loads a template.
*
* @param string $template The logical template name
* @param array $template The template name as an array
*
* @return Storage|Boolean false if the template cannot be loaded, a Storage instance otherwise
*/
@ -30,7 +30,7 @@ interface LoaderInterface
/**
* Returns true if the template is still fresh.
*
* @param string $template The template name
* @param array $template The template name as an array
* @param timestamp $time The last modification time of the cached template
*/
function isFresh($template, $time);

View File

@ -33,15 +33,18 @@ class PhpEngine implements EngineInterface, \ArrayAccess
protected $cache;
protected $escapers;
protected $globals;
protected $parser;
/**
* Constructor.
*
* @param LoaderInterface $loader A loader instance
* @param array $helpers An array of helper instances
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param LoaderInterface $loader A loader instance
* @param array $helpers An array of helper instances
*/
public function __construct(LoaderInterface $loader, array $helpers = array())
public function __construct(TemplateNameParserInterface $parser, LoaderInterface $loader, array $helpers = array())
{
$this->parser = $parser;
$this->loader = $loader;
$this->parents = array();
$this->stack = array();
@ -60,8 +63,8 @@ class PhpEngine implements EngineInterface, \ArrayAccess
/**
* Renders a template.
*
* @param string $name A template name
* @param array $parameters An array of parameters to pass to the template
* @param mixed $name A template name
* @param array $parameters An array of parameters to pass to the template
*
* @return string The evaluated template as a string
*
@ -100,7 +103,7 @@ class PhpEngine implements EngineInterface, \ArrayAccess
/**
* Returns true if the template exists.
*
* @param string $name A template name
* @param mixed $name A template name
*
* @return Boolean true if the template exists, false otherwise
*/
@ -118,7 +121,7 @@ class PhpEngine implements EngineInterface, \ArrayAccess
/**
* Loads the given template.
*
* @param string $name A template name
* @param mixed $name A template name
*
* @return Storage A Storage instance
*
@ -126,32 +129,35 @@ class PhpEngine implements EngineInterface, \ArrayAccess
*/
public function load($name)
{
if (isset($this->cache[$name])) {
return $this->cache[$name];
$template = $this->parser->parse($name);
$key = md5(serialize($template));
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
// load
$template = $this->loader->load($name);
$template = $this->loader->load($template);
if (false === $template) {
throw new \InvalidArgumentException(sprintf('The template "%s" does not exist.', $name));
}
$this->cache[$name] = $template;
return $template;
return $this->cache[$key] = $template;
}
/**
* Returns true if this class is able to render the given template.
*
* @param string $name A template name
* @param mixed $name A template name
*
* @return Boolean True if this class supports the given resource, false otherwise
*/
public function supports($name)
{
return false !== strpos($name, '.php');
$template = $this->parser->parse($name);
return 'php' === $template['engine'];
}
/**

View File

@ -9,11 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Templating\Loader;
namespace Symfony\Component\Templating;
/**
* TemplateNameParser is the default implementation of TemplateNameParserInterface.
*
* This implementation takes everything as the template name
* and the extension for the engine.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class TemplateNameParser implements TemplateNameParserInterface
@ -21,14 +24,21 @@ class TemplateNameParser implements TemplateNameParserInterface
/**
* Parses a template to an array of parameters.
*
* The only mandatory parameter is the template name (name).
*
* @param string $name A template name
*
* @return array An array of parameters
*/
public function parse($name)
{
return array('name' => $name);
if (is_array($name)) {
return $name;
}
$engine = null;
if (false !== $pos = strrpos($name, '.')) {
$engine = substr($name, $pos + 1);
}
return array('name' => $name, 'engine' => $engine);
}
}

View File

@ -9,10 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Templating\Loader;
namespace Symfony\Component\Templating;
/**
* TemplateNameParserInterface parses template name to a template name and an array of options.
* TemplateNameParserInterface parses template names to a
* "normalized" array of template parameters.
*
* The template name array must always have at least a "name"
* and an "engine" key.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
@ -21,11 +25,9 @@ interface TemplateNameParserInterface
/**
* Parses a template to an array of parameters.
*
* The only mandatory parameter is the template name (name).
*
* @param string $name A template name
*
* @return array An array of parameters
* @return array An array of template parameters
*/
function parse($name);
}

View File

@ -16,7 +16,7 @@ require_once __DIR__.'/../Fixtures/ProjectTemplateDebugger.php';
use Symfony\Component\Templating\Loader\Loader;
use Symfony\Component\Templating\Loader\CacheLoader;
use Symfony\Component\Templating\Storage\StringStorage;
use Symfony\Component\Templating\Loader\TemplateNameParser;
use Symfony\Component\Templating\TemplateNameParser;
class CacheLoaderTest extends \PHPUnit_Framework_TestCase
{

View File

@ -16,7 +16,6 @@ require_once __DIR__.'/../Fixtures/ProjectTemplateDebugger.php';
use Symfony\Component\Templating\Loader\ChainLoader;
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\Storage\FileStorage;
use Symfony\Component\Templating\Loader\TemplateNameParser;
class ChainLoaderTest extends \PHPUnit_Framework_TestCase
{
@ -26,8 +25,8 @@ class ChainLoaderTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$fixturesPath = realpath(__DIR__.'/../Fixtures/');
$this->loader1 = new FilesystemLoader(new TemplateNameParser(), $fixturesPath.'/null/%name%');
$this->loader2 = new FilesystemLoader(new TemplateNameParser(), $fixturesPath.'/templates/%name%');
$this->loader1 = new FilesystemLoader($fixturesPath.'/null/%name%');
$this->loader2 = new FilesystemLoader($fixturesPath.'/templates/%name%');
}
public function testConstructor()
@ -46,9 +45,9 @@ class ChainLoaderTest extends \PHPUnit_Framework_TestCase
public function testLoad()
{
$loader = new ProjectTemplateLoader1(array($this->loader1, $this->loader2));
$this->assertFalse($loader->load('bar'), '->load() returns false if the template is not found');
$this->assertFalse($loader->load('foo'), '->load() returns false if the template does not exists for the given renderer');
$this->assertInstanceOf('Symfony\Component\Templating\Storage\FileStorage', $loader->load('foo.php'), '->load() returns a FileStorage if the template exists');
$this->assertFalse($loader->load(array('name' => 'bar', 'engine' => 'php')), '->load() returns false if the template is not found');
$this->assertFalse($loader->load(array('name' => 'foo', 'engine' => 'php')), '->load() returns false if the template does not exists for the given renderer');
$this->assertInstanceOf('Symfony\Component\Templating\Storage\FileStorage', $loader->load(array('name' => 'foo.php', 'engine' => 'php')), '->load() returns a FileStorage if the template exists');
}
}

View File

@ -15,7 +15,6 @@ require_once __DIR__.'/../Fixtures/ProjectTemplateDebugger.php';
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\Storage\FileStorage;
use Symfony\Component\Templating\Loader\TemplateNameParser;
class FilesystemLoaderTest extends \PHPUnit_Framework_TestCase
{
@ -28,11 +27,11 @@ class FilesystemLoaderTest extends \PHPUnit_Framework_TestCase
public function testConstructor()
{
$pathPattern = self::$fixturesPath.'/templates/%name%.%renderer%';
$pathPattern = self::$fixturesPath.'/templates/%name%.%engine%';
$path = self::$fixturesPath.'/templates';
$loader = new ProjectTemplateLoader2(new TemplateNameParser(), $pathPattern);
$loader = new ProjectTemplateLoader2($pathPattern);
$this->assertEquals(array($pathPattern), $loader->getTemplatePathPatterns(), '__construct() takes a path as its second argument');
$loader = new ProjectTemplateLoader2(new TemplateNameParser(), array($pathPattern));
$loader = new ProjectTemplateLoader2(array($pathPattern));
$this->assertEquals(array($pathPattern), $loader->getTemplatePathPatterns(), '__construct() takes an array of paths as its second argument');
}
@ -48,25 +47,25 @@ class FilesystemLoaderTest extends \PHPUnit_Framework_TestCase
{
$pathPattern = self::$fixturesPath.'/templates/%name%';
$path = self::$fixturesPath.'/templates';
$loader = new ProjectTemplateLoader2(new TemplateNameParser(), $pathPattern);
$storage = $loader->load($path.'/foo.php');
$loader = new ProjectTemplateLoader2($pathPattern);
$storage = $loader->load(array('name' => $path.'/foo.php', 'engine' => 'php'));
$this->assertInstanceOf('Symfony\Component\Templating\Storage\FileStorage', $storage, '->load() returns a FileStorage if you pass an absolute path');
$this->assertEquals($path.'/foo.php', (string) $storage, '->load() returns a FileStorage pointing to the passed absolute path');
$this->assertFalse($loader->load('bar'), '->load() returns false if the template is not found');
$this->assertFalse($loader->load(array('name' => 'bar', 'engine' => 'php')), '->load() returns false if the template is not found');
$storage = $loader->load('foo.php');
$storage = $loader->load(array('name' => 'foo.php', 'engine' => 'php'));
$this->assertInstanceOf('Symfony\Component\Templating\Storage\FileStorage', $storage, '->load() returns a FileStorage if you pass a relative template that exists');
$this->assertEquals($path.'/foo.php', (string) $storage, '->load() returns a FileStorage pointing to the absolute path of the template');
$loader = new ProjectTemplateLoader2(new TemplateNameParser(), $pathPattern);
$loader = new ProjectTemplateLoader2($pathPattern);
$loader->setDebugger($debugger = new \ProjectTemplateDebugger());
$this->assertFalse($loader->load('foo.xml'), '->load() returns false if the template does not exists for the given renderer');
$this->assertFalse($loader->load(array('name' => 'foo.xml', 'engine' => 'php')), '->load() returns false if the template does not exists for the given engine');
$this->assertTrue($debugger->hasMessage('Failed loading template'), '->load() logs a "Failed loading template" message if the template is not found');
$loader = new ProjectTemplateLoader2(new TemplateNameParser(), array(self::$fixturesPath.'/null/%name%', $pathPattern));
$loader = new ProjectTemplateLoader2(array(self::$fixturesPath.'/null/%name%', $pathPattern));
$loader->setDebugger($debugger = new \ProjectTemplateDebugger());
$loader->load('foo.php');
$loader->load(array('name' => 'foo.php', 'engine' => 'php'));
$this->assertTrue($debugger->hasMessage('Loaded template file'), '->load() logs a "Loaded template file" message if the template is found');
}
}

View File

@ -18,7 +18,7 @@ use Symfony\Component\Templating\Loader\Loader;
use Symfony\Component\Templating\Storage\Storage;
use Symfony\Component\Templating\Storage\StringStorage;
use Symfony\Component\Templating\Helper\SlotsHelper;
use Symfony\Component\Templating\Loader\TemplateNameParser;
use Symfony\Component\Templating\TemplateNameParser;
class PhpEngineTest extends \PHPUnit_Framework_TestCase
{
@ -26,18 +26,18 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$this->loader = new ProjectTemplateLoader(new TemplateNameParser());
$this->loader = new ProjectTemplateLoader();
}
public function testConstructor()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$this->assertEquals($this->loader, $engine->getLoader(), '__construct() takes a loader instance as its second first argument');
}
public function testOffsetGet()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$engine->set($helper = new \SimpleHelper('bar'), 'foo');
$this->assertEquals($helper, $engine['foo'], '->offsetGet() returns the value of a helper');
@ -52,7 +52,7 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function testGetSetHas()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$foo = new \SimpleHelper('foo');
$engine->set($foo);
$this->assertEquals($foo, $engine->get('foo'), '->set() sets a helper');
@ -74,7 +74,7 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function testExtendRender()
{
$engine = new ProjectTemplateEngine($this->loader, array(), array(new SlotsHelper()));
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader, array(), array(new SlotsHelper()));
try {
$engine->render('name');
$this->fail('->render() throws an InvalidArgumentException if the template does not exist');
@ -83,13 +83,13 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('The template "name" does not exist.', $e->getMessage(), '->render() throws an InvalidArgumentException if the template does not exist');
}
$engine = new ProjectTemplateEngine($this->loader, array(new SlotsHelper()));
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader, array(new SlotsHelper()));
$engine->set(new \SimpleHelper('bar'));
$this->loader->setTemplate('foo.php', '<?php $view->extend("layout.php"); echo $view[\'foo\'].$foo ?>');
$this->loader->setTemplate('layout.php', '-<?php echo $view[\'slots\']->get("_content") ?>-');
$this->assertEquals('-barfoo-', $engine->render('foo.php', array('foo' => 'foo')), '->render() uses the decorator to decorate the template');
$engine = new ProjectTemplateEngine($this->loader, array(new SlotsHelper()));
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader, array(new SlotsHelper()));
$engine->set(new \SimpleHelper('bar'));
$this->loader->setTemplate('bar.php', 'bar');
$this->loader->setTemplate('foo.php', '<?php $view->extend("layout.php"); echo $foo ?>');
@ -99,7 +99,7 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function testEscape()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$this->assertEquals('&lt;br /&gt;', $engine->escape('<br />'), '->escape() escapes strings');
$foo = new \stdClass();
$this->assertEquals($foo, $engine->escape($foo), '->escape() does nothing on non strings');
@ -107,7 +107,7 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function testGetSetCharset()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$this->assertEquals('UTF-8', $engine->getCharset(), '->getCharset() returns UTF-8 by default');
$engine->setCharset('ISO-8859-1');
$this->assertEquals('ISO-8859-1', $engine->getCharset(), '->setCharset() changes the default charset to use');
@ -115,7 +115,7 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function testGlobalVariables()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$engine->addGlobal('global_variable', 'lorem ipsum');
$this->assertEquals(array(
@ -125,7 +125,7 @@ class PhpEngineTest extends \PHPUnit_Framework_TestCase
public function testGlobalsGetPassedToTemplate()
{
$engine = new ProjectTemplateEngine($this->loader);
$engine = new ProjectTemplateEngine(new TemplateNameParser(), $this->loader);
$engine->addGlobal('global', 'global variable');
$this->loader->setTemplate('global.php', '<?php echo $global; ?>');
@ -150,13 +150,13 @@ class ProjectTemplateLoader extends Loader
public function setTemplate($name, $template)
{
$this->templates[$name] = $template;
$this->templates[$this->getKey(array('name' => $name, 'engine' => 'php'))] = $template;
}
public function load($template)
public function load($name)
{
if (isset($this->templates[$template])) {
return new StringStorage($this->templates[$template]);
if (isset($this->templates[$this->getKey($name)])) {
return new StringStorage($this->templates[$this->getKey($name)]);
}
return false;
@ -166,4 +166,9 @@ class ProjectTemplateLoader extends Loader
{
return false;
}
protected function getKey($template)
{
return md5(serialize($template));
}
}