From d3ee1165feff9d8f5ebf3d68fd2c3b1386a53309 Mon Sep 17 00:00:00 2001 From: fabien Date: Mon, 16 Nov 2009 15:01:52 +0000 Subject: [PATCH] refactored loaders (closes #38) git-svn-id: http://svn.twig-project.org/trunk@142 93ef8e89-cb99-4229-a87c-7fa0fa45744b --- CHANGELOG | 5 + doc/03-Twig-for-Developers.markdown | 65 +++++++------ lib/Twig/Environment.php | 65 ++++++++++--- lib/Twig/Loader.php | 120 ----------------------- lib/Twig/Loader/Array.php | 39 +++++++- lib/Twig/Loader/Filesystem.php | 49 ++++++++-- lib/Twig/Loader/String.php | 33 +++++- lib/Twig/LoaderInterface.php | 24 ++++-- lib/Twig/Node/Import.php | 2 +- lib/Twig/Node/Module.php | 2 +- test/fixtures/tags/include/basic.test | 2 +- test/fixtures/tags/include/expression.test | 2 +- test/fixtures/tags/inheritance/basic.test | 2 +- test/fixtures/tags/inheritance/parent.test | 2 +- test/fixtures/tags/macro/basic.test | 2 +- test/fixtures/tags/macro/external.test | 2 +- test/lib/Twig_Loader_Var.php | 43 -------- test/unit/Twig/Extension/Sandbox.php | 144 ---------------------------- test/unit/Twig/Extension/SandboxTest.php | 138 ++++++++++++++++++++++++++ test/unit/integrationTest.php | 11 +-- 20 files changed, 365 insertions(+), 387 deletions(-) delete mode 100644 lib/Twig/Loader.php delete mode 100644 test/lib/Twig_Loader_Var.php delete mode 100644 test/unit/Twig/Extension/Sandbox.php create mode 100644 test/unit/Twig/Extension/SandboxTest.php diff --git a/CHANGELOG b/CHANGELOG index 01d5883..6811b4e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ * 0.9.4-DEV +If you have custom loaders, you MUST upgrade them for this release: The +Twig_Loader base class has been removed, and the Twig_LoaderInterface has also +been changed (see the source code for more information or the documentation). + + * refactored loaders * 0.9.3 (2009-11-11) diff --git a/doc/03-Twig-for-Developers.markdown b/doc/03-Twig-for-Developers.markdown index 9457eae..be94e34 100644 --- a/doc/03-Twig-for-Developers.markdown +++ b/doc/03-Twig-for-Developers.markdown @@ -111,6 +111,10 @@ The following options are available: Loaders ------- +>**CAUTION** +>This section describes the loaders as implemented in Twig version 0.9.4 and +>above. + Loaders are responsible for loading templates from a resource such as the file system. @@ -137,7 +141,7 @@ Here a list of the built-in loaders Twig provides: you pass it the source code directly. [php] - $loader = new Twig_Loader_String($cacheDir); + $loader = new Twig_Loader_String(); * `Twig_Loader_Array`: Loads a template from a PHP array. It's passed an array of strings bound to template names. This loader is useful for unit @@ -154,52 +158,55 @@ All loaders implement the `Twig_LoaderInterface`: interface Twig_LoaderInterface { /** - * Loads a template by name. + * Gets the source code of a template, given its name. * - * @param string $name The template name + * @param string $name string The name of the template to load * - * @return string The class name of the compiled template + * @return string The template source code */ - public function load($name); + public function getSource($name); /** - * Sets the Environment related to this loader. + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load * - * @param Twig_Environment $env A Twig_Environment instance + * @return string The cache key */ - public function setEnvironment(Twig_Environment $env); + public function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time); } -But if you want to create your own loader, you'd better inherit from the -`Twig_Loader` class, which already provides a lot of useful features. In this -case, you just need to implement the `getSource()` method. As an example, here -is how the built-in `Twig_Loader_String` reads: +As an example, here is how the built-in `Twig_Loader_String` reads: [php] - class Twig_Loader_String extends Twig_Loader + class Twig_Loader_String implements Twig_LoaderInterface { - /** - * Gets the source code of a template, given its name. - * - * @param string $name string The name of the template to load - * - * @return array An array consisting of the source code as the first element, - * and the last modification time as the second one - * or false if it's not relevant - */ public function getSource($name) { - return array($name, false); + return $name; } - } -The `getSource()` method must return an array of two values: + public function getCacheKey($name) + { + return $name; + } - * The first one is the template source code; + public function isFresh($name, $time) + { + return false; + } + } - * The second one is the last modification time of the template (used by the - auto-reload feature), or `false` if the loader does not support - auto-reloading. +The `isFresh()` method must return `true` if the current cached template is +still fresh, given the last modification time, or `false` otherwise. Using Extensions ---------------- diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index 8b9bf17..ef7392e 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -126,7 +126,7 @@ class Twig_Environment public function getCacheFilename($name) { - return $this->getCache() ? $this->getCache().'/twig_'.md5($name).'.php' : false; + return $this->getCache() ? $this->getCache().'/'.$this->getTemplateClass($name).'.php' : false; } public function getTrimBlocks() @@ -148,26 +148,61 @@ class Twig_Environment */ public function getTemplateClass($name) { - return '__TwigTemplate_'.md5($name); + return '__TwigTemplate_'.md5($this->loader->getCacheKey($name)); } + /** + * Loads a template by name. + * + * @param string $name The template name + * + * @return Twig_TemplateInterface A template instance representing the given template name + */ public function loadTemplate($name) { - if (!$this->runtimeInitialized) - { - $this->initRuntime(); + $cls = $this->getTemplateClass($name); - $this->runtimeInitialized = true; + if (isset($this->loadedTemplates[$cls])) + { + return $this->loadedTemplates[$cls]; } - if (isset($this->loadedTemplates[$name])) + if (!class_exists($cls, false)) { - return $this->loadedTemplates[$name]; + if (false === $cache = $this->getCacheFilename($name)) + { + eval('?>'.$this->compileSource($this->loader->getSource($name), $name)); + } + else + { + if (!file_exists($cache) || ($this->isAutoReload() && !$this->loader->isFresh($name, filemtime($cache)))) + { + $content = $this->compileSource($this->loader->getSource($name), $name); + + if (false === file_put_contents($cache, $content, LOCK_EX)) + { + eval('?>'.$content); + } + else + { + require_once $cache; + } + } + else + { + require_once $cache; + } + } } - $cls = $this->getLoader()->load($name, $this); + if (!$this->runtimeInitialized) + { + $this->initRuntime(); - return $this->loadedTemplates[$name] = new $cls($this); + $this->runtimeInitialized = true; + } + + return $this->loadedTemplates[$cls] = new $cls($this); } public function clearTemplateCache() @@ -191,9 +226,9 @@ class Twig_Environment $lexer->setEnvironment($this); } - public function tokenize($source, $name = null) + public function tokenize($source, $name) { - return $this->getLexer()->tokenize($source, null === $name ? $source : $name); + return $this->getLexer()->tokenize($source, $name); } public function getParser() @@ -238,10 +273,14 @@ class Twig_Environment return $this->getCompiler()->compile($node)->getSource(); } + public function compileSource($source, $name) + { + return $this->compile($this->parse($this->tokenize($source, $name))); + } + public function setLoader(Twig_LoaderInterface $loader) { $this->loader = $loader; - $loader->setEnvironment($this); } public function getLoader() diff --git a/lib/Twig/Loader.php b/lib/Twig/Loader.php deleted file mode 100644 index 2e0f3fe..0000000 --- a/lib/Twig/Loader.php +++ /dev/null @@ -1,120 +0,0 @@ - - * @version SVN: $Id$ - */ -abstract class Twig_Loader implements Twig_LoaderInterface -{ - protected $env; - - /** - * Loads a template by name. - * - * @param string $name The template name - * - * @return string The class name of the compiled template - */ - public function load($name) - { - $cls = $this->env->getTemplateClass($name); - - if (class_exists($cls, false)) - { - return $cls; - } - - if (false === $cache = $this->env->getCacheFilename($name)) - { - list($source, ) = $this->getSource($name); - $this->evalString($source, $name); - - return $cls; - } - - if (!file_exists($cache)) - { - list($source, $mtime) = $this->getSource($name); - if (false === $mtime) - { - $this->evalString($source, $name); - - return $cls; - } - - $this->save($this->compile($source, $name), $cache); - } - elseif ($this->env->isAutoReload()) - { - list($source, $mtime) = $this->getSource($name); - if (filemtime($cache) < $mtime) - { - $this->save($this->compile($source, $name), $cache); - } - } - - require_once $cache; - - return $cls; - } - - /** - * Saves a PHP string in the cache. - * - * If the cache file cannot be written, then the PHP string is evaluated. - * - * @param string $content The PHP string - * @param string $cache The absolute path of the cache - */ - protected function save($content, $cache) - { - if (false === file_put_contents($cache, $content, LOCK_EX)) - { - eval('?>'.$content); - } - } - - /** - * Sets the Environment related to this loader. - * - * @param Twig_Environment $env A Twig_Environment instance - */ - public function setEnvironment(Twig_Environment $env) - { - $this->env = $env; - } - - protected function compile($source, $name) - { - return $this->env->compile($this->env->parse($this->env->tokenize($source, $name))); - } - - protected function evalString($source, $name) - { - eval('?>'.$this->compile($source, $name)); - } - - /** - * Gets the source code of a template, given its name. - * - * @param string $name string The name of the template to load - * - * @return array An array consisting of the source code as the first element, - * and the last modification time as the second one - * or false if it's not relevant - */ - abstract protected function getSource($name); -} diff --git a/lib/Twig/Loader/Array.php b/lib/Twig/Loader/Array.php index 265f3d0..2d3fbe7 100644 --- a/lib/Twig/Loader/Array.php +++ b/lib/Twig/Loader/Array.php @@ -16,7 +16,7 @@ * @author Fabien Potencier * @version SVN: $Id$ */ -class Twig_Loader_Array extends Twig_Loader +class Twig_Loader_Array implements Twig_LoaderInterface { protected $templates; @@ -36,6 +36,13 @@ class Twig_Loader_Array extends Twig_Loader } } + /** + * Gets the source code of a template, given its name. + * + * @param string $name string The name of the template to load + * + * @return string The template source code + */ public function getSource($name) { if (!isset($this->templates[$name])) @@ -43,6 +50,34 @@ class Twig_Loader_Array extends Twig_Loader throw new LogicException(sprintf('Template "%s" is not defined.', $name)); } - return array($this->templates[$name], false); + return $this->templates[$name]; + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + if (!isset($this->templates[$name])) + { + throw new LogicException(sprintf('Template "%s" is not defined.', $name)); + } + + return $this->templates[$name]; + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + return false; } } diff --git a/lib/Twig/Loader/Filesystem.php b/lib/Twig/Loader/Filesystem.php index 839421b..3df61dd 100644 --- a/lib/Twig/Loader/Filesystem.php +++ b/lib/Twig/Loader/Filesystem.php @@ -16,9 +16,10 @@ * @author Fabien Potencier * @version SVN: $Id$ */ -class Twig_Loader_Filesystem extends Twig_Loader +class Twig_Loader_Filesystem implements Twig_LoaderInterface { protected $paths; + protected $cache; /** * Constructor. @@ -47,6 +48,9 @@ class Twig_Loader_Filesystem extends Twig_Loader */ public function setPaths($paths) { + // invalidate the cache + $this->cache = array(); + if (!is_array($paths)) { $paths = array($paths); @@ -69,17 +73,46 @@ class Twig_Loader_Filesystem extends Twig_Loader * * @param string $name string The name of the template to load * - * @return array An array consisting of the source code as the first element, - * and the last modification time as the second one - * or false if it's not relevant + * @return string The template source code */ public function getSource($name) { - foreach ($this->paths as $path) + return file_get_contents($this->findTemplate($name)); + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + return $this->findTemplate($name); + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + return $time < filemtime($this->findTemplate($name)); + } + + protected function findTemplate($name) + { + if (isset($this->cache[$name])) { - $file = realpath($path.DIRECTORY_SEPARATOR.$name); + return $this->cache[$name]; + } - if (false === $file) + foreach ($this->paths as $path) + { + if (false === $file = realpath($path.DIRECTORY_SEPARATOR.$name)) { continue; } @@ -90,7 +123,7 @@ class Twig_Loader_Filesystem extends Twig_Loader throw new RuntimeException('Looks like you try to load a template outside configured directories.'); } - return array(file_get_contents($file), filemtime($file)); + return $this->cache[$name] = $file; } throw new RuntimeException(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths))); diff --git a/lib/Twig/Loader/String.php b/lib/Twig/Loader/String.php index c135fe4..1c3eb1b 100644 --- a/lib/Twig/Loader/String.php +++ b/lib/Twig/Loader/String.php @@ -16,19 +16,40 @@ * @author Fabien Potencier * @version SVN: $Id$ */ -class Twig_Loader_String extends Twig_Loader +class Twig_Loader_String implements Twig_LoaderInterface { /** * Gets the source code of a template, given its name. * * @param string $name string The name of the template to load * - * @return array An array consisting of the source code as the first element, - * and the last modification time as the second one - * or false if it's not relevant + * @return string The template source code */ - public function getSource($source) + public function getSource($name) { - return array($source, false); + return $name; + } + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name) + { + return $name; + } + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template + */ + public function isFresh($name, $time) + { + return false; } } diff --git a/lib/Twig/LoaderInterface.php b/lib/Twig/LoaderInterface.php index 4682f08..fee97a0 100644 --- a/lib/Twig/LoaderInterface.php +++ b/lib/Twig/LoaderInterface.php @@ -19,18 +19,28 @@ interface Twig_LoaderInterface { /** - * Loads a template by name. + * Gets the source code of a template, given its name. * - * @param string $name The template name + * @param string $name string The name of the template to load * - * @return string The class name of the compiled template + * @return string The template source code */ - public function load($name); + public function getSource($name); /** - * Sets the Environment related to this loader. + * Gets the cache key to use for the cache for a given template name. * - * @param Twig_Environment $env A Twig_Environment instance + * @param string $name string The name of the template to load + * + * @return string The cache key + */ + public function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param timestamp $time The last modification time of the cached template */ - public function setEnvironment(Twig_Environment $env); + public function isFresh($name, $time); } diff --git a/lib/Twig/Node/Import.php b/lib/Twig/Node/Import.php index d5ef05c..8978839 100644 --- a/lib/Twig/Node/Import.php +++ b/lib/Twig/Node/Import.php @@ -37,7 +37,7 @@ class Twig_Node_Import extends Twig_Node { $compiler ->addDebugInfo($this) - ->write('$this->env->getLoader()->load(') + ->write('$this->env->loadTemplate(') ->string($this->macro) ->raw(");\n\n") ->write("if (!class_exists(") diff --git a/lib/Twig/Node/Module.php b/lib/Twig/Node/Module.php index 80a3df6..55443e0 100644 --- a/lib/Twig/Node/Module.php +++ b/lib/Twig/Node/Module.php @@ -99,7 +99,7 @@ class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface if (!is_null($this->extends)) { $compiler - ->write('$this->env->loadTemplate(') + ->write('$this->loadTemplate(') ->repr($this->extends) ->raw(");\n\n") ; diff --git a/test/fixtures/tags/include/basic.test b/test/fixtures/tags/include/basic.test index 1f52614..8fe1a6c 100644 --- a/test/fixtures/tags/include/basic.test +++ b/test/fixtures/tags/include/basic.test @@ -2,7 +2,7 @@ "include" tag --TEMPLATE-- FOO -{% include "%prefix%foo.twig" %} +{% include "foo.twig" %} BAR --TEMPLATE(foo.twig)-- diff --git a/test/fixtures/tags/include/expression.test b/test/fixtures/tags/include/expression.test index df40e2a..eaeeb11 100644 --- a/test/fixtures/tags/include/expression.test +++ b/test/fixtures/tags/include/expression.test @@ -2,7 +2,7 @@ "include" tag allows expressions for the template to include --TEMPLATE-- FOO -{% include "%prefix%" ~ foo %} +{% include foo %} BAR --TEMPLATE(foo.twig)-- diff --git a/test/fixtures/tags/inheritance/basic.test b/test/fixtures/tags/inheritance/basic.test index a9df3df..0778a4b 100644 --- a/test/fixtures/tags/inheritance/basic.test +++ b/test/fixtures/tags/inheritance/basic.test @@ -1,7 +1,7 @@ --TEST-- "extends" tag --TEMPLATE-- -{% extends "%prefix%foo.twig" %} +{% extends "foo.twig" %} {% block content %} FOO diff --git a/test/fixtures/tags/inheritance/parent.test b/test/fixtures/tags/inheritance/parent.test index b2e5daf..b4a1daf 100644 --- a/test/fixtures/tags/inheritance/parent.test +++ b/test/fixtures/tags/inheritance/parent.test @@ -1,7 +1,7 @@ --TEST-- "extends" tag --TEMPLATE-- -{% extends "%prefix%foo.twig" %} +{% extends "foo.twig" %} {% block content %}{% parent %}FOO{% parent %}{% endblock %} --TEMPLATE(foo.twig)-- diff --git a/test/fixtures/tags/macro/basic.test b/test/fixtures/tags/macro/basic.test index be2a20b..2bbae8f 100644 --- a/test/fixtures/tags/macro/basic.test +++ b/test/fixtures/tags/macro/basic.test @@ -1,7 +1,7 @@ --TEST-- "macro" tag --TEMPLATE-- -{% import '%prefix%index.twig' as forms %} +{% import 'index.twig' as forms %} {{ forms.input('username') }} {{ forms.input('password', null, 'password', 1) }} diff --git a/test/fixtures/tags/macro/external.test b/test/fixtures/tags/macro/external.test index d364170..5cd3dae 100644 --- a/test/fixtures/tags/macro/external.test +++ b/test/fixtures/tags/macro/external.test @@ -1,7 +1,7 @@ --TEST-- "macro" tag --TEMPLATE-- -{% import '%prefix%forms.twig' as forms %} +{% import 'forms.twig' as forms %} {{ forms.input('username') }} {{ forms.input('password', null, 'password', 1) }} diff --git a/test/lib/Twig_Loader_Var.php b/test/lib/Twig_Loader_Var.php deleted file mode 100644 index c2a5b9c..0000000 --- a/test/lib/Twig_Loader_Var.php +++ /dev/null @@ -1,43 +0,0 @@ - - * @version SVN: $Id$ - */ -class Twig_Loader_Var extends Twig_Loader -{ - protected $templates; - protected $prefix; - - public function __construct(array $templates, $prefix) - { - $this->prefix = $prefix; - $this->templates = array(); - foreach ($templates as $name => $template) - { - $this->templates[$this->prefix.'_'.$name] = $template; - } - } - - public function getSource($name) - { - if (!isset($this->templates[$this->prefix.'_'.$name])) - { - throw new LogicException(sprintf('Template "%s" is not defined.', $name)); - } - - return array(str_replace('%prefix%', $this->prefix.'_', $this->templates[$this->prefix.'_'.$name]), false); - } -} diff --git a/test/unit/Twig/Extension/Sandbox.php b/test/unit/Twig/Extension/Sandbox.php deleted file mode 100644 index c929edd..0000000 --- a/test/unit/Twig/Extension/Sandbox.php +++ /dev/null @@ -1,144 +0,0 @@ - 'Fabien', - 'obj' => new Object(), -); -$templates = array( - '1_basic1' => '{{ obj.foo }}', - '1_basic2' => '{{ name|upper }}', - '1_basic3' => '{% if name %}foo{% endif %}', - '1_basic4' => '{{ obj.bar }}', - '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', -); - -$t = new LimeTest(11); - -$t->diag('Sandbox globally set'); -$twig = get_environment(false, $templates); -$t->is($twig->loadTemplate('1_basic')->render($params), 'FOO', 'Sandbox does nothing if it is disabled globally'); - -$twig = get_environment(true, $templates); -try -{ - $twig->loadTemplate('1_basic1')->render($params); - $t->fail('Sandbox throws a SecurityError exception if an unallowed method is called'); -} -catch (Twig_Sandbox_SecurityError $e) -{ - $t->pass('Sandbox throws a SecurityError exception if an unallowed method is called'); -} - -$twig = get_environment(true, $templates); -try -{ - $twig->loadTemplate('1_basic2')->render($params); - $t->fail('Sandbox throws a SecurityError exception if an unallowed filter is called'); -} -catch (Twig_Sandbox_SecurityError $e) -{ - $t->pass('Sandbox throws a SecurityError exception if an unallowed filter is called'); -} - -$twig = get_environment(true, $templates); -try -{ - $twig->loadTemplate('1_basic3')->render($params); - $t->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template'); -} -catch (Twig_Sandbox_SecurityError $e) -{ - $t->pass('Sandbox throws a SecurityError exception if an unallowed tag is used in the template'); -} - -$twig = get_environment(true, $templates); -try -{ - $twig->loadTemplate('1_basic4')->render($params); - $t->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template'); -} -catch (Twig_Sandbox_SecurityError $e) -{ - $t->pass('Sandbox throws a SecurityError exception if an unallowed property is called in the template'); -} - -$twig = get_environment(true, $templates, array(), array(), array('Object' => 'foo')); -$t->is($twig->loadTemplate('1_basic1')->render($params), 'foo', 'Sandbox allow some methods'); - -$twig = get_environment(true, $templates, array(), array('upper')); -$t->is($twig->loadTemplate('1_basic2')->render($params), 'FABIEN', 'Sandbox allow some filters'); - -$twig = get_environment(true, $templates, array('if')); -$t->is($twig->loadTemplate('1_basic3')->render($params), 'foo', 'Sandbox allow some tags'); - -$twig = get_environment(true, $templates, array(), array(), array(), array('Object' => 'bar')); -$t->is($twig->loadTemplate('1_basic4')->render($params), 'bar', 'Sandbox allow some properties'); - -$t->diag('Sandbox locally set for an include'); - -$templates = array( - '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}', - '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', -); - -$twig = get_environment(false, $templates); -$t->is($twig->loadTemplate('2_basic')->render($params), 'fooFOOfoo', 'Sandbox does nothing if disabled globally and sandboxed not used for the include'); - -$templates = array( - '3_basic' => '{{ obj.foo }}{% include "3_included" sandboxed %}{{ obj.foo }}', - '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', -); - -$twig = get_environment(false, $templates); -$twig = get_environment(true, $templates); -try -{ - $twig->loadTemplate('3_basic')->render($params); - $t->fail('Sandbox throws a SecurityError exception when the included file is sandboxed'); -} -catch (Twig_Sandbox_SecurityError $e) -{ - $t->pass('Sandbox throws a SecurityError exception when the included file is sandboxed'); -} - - -function get_environment($sandboxed, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array()) -{ - static $prefix = 0; - - ++$prefix; - - $loader = new Twig_Loader_Var($templates, $prefix); - $twig = new Twig_Environment($loader, array('trim_blocks' => true, 'debug' => true)); - $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties); - $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed)); - - return $twig; -} diff --git a/test/unit/Twig/Extension/SandboxTest.php b/test/unit/Twig/Extension/SandboxTest.php new file mode 100644 index 0000000..0cc6dd3 --- /dev/null +++ b/test/unit/Twig/Extension/SandboxTest.php @@ -0,0 +1,138 @@ + 'Fabien', + 'obj' => new Object(), +); +$templates = array( + '1_basic1' => '{{ obj.foo }}', + '1_basic2' => '{{ name|upper }}', + '1_basic3' => '{% if name %}foo{% endif %}', + '1_basic4' => '{{ obj.bar }}', + '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', +); + +$t = new LimeTest(11); + +$t->diag('Sandbox globally set'); +$twig = get_environment(false, $templates); +$t->is($twig->loadTemplate('1_basic')->render($params), 'FOO', 'Sandbox does nothing if it is disabled globally'); + +$twig = get_environment(true, $templates); +try +{ + $twig->loadTemplate('1_basic1')->render($params); + $t->fail('Sandbox throws a SecurityError exception if an unallowed method is called'); +} +catch (Twig_Sandbox_SecurityError $e) +{ + $t->pass('Sandbox throws a SecurityError exception if an unallowed method is called'); +} + +$twig = get_environment(true, $templates); +try +{ + $twig->loadTemplate('1_basic2')->render($params); + $t->fail('Sandbox throws a SecurityError exception if an unallowed filter is called'); +} +catch (Twig_Sandbox_SecurityError $e) +{ + $t->pass('Sandbox throws a SecurityError exception if an unallowed filter is called'); +} + +$twig = get_environment(true, $templates); +try +{ + $twig->loadTemplate('1_basic3')->render($params); + $t->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template'); +} +catch (Twig_Sandbox_SecurityError $e) +{ + $t->pass('Sandbox throws a SecurityError exception if an unallowed tag is used in the template'); +} + +$twig = get_environment(true, $templates); +try +{ + $twig->loadTemplate('1_basic4')->render($params); + $t->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template'); +} +catch (Twig_Sandbox_SecurityError $e) +{ + $t->pass('Sandbox throws a SecurityError exception if an unallowed property is called in the template'); +} + +$twig = get_environment(true, $templates, array(), array(), array('Object' => 'foo')); +$t->is($twig->loadTemplate('1_basic1')->render($params), 'foo', 'Sandbox allow some methods'); + +$twig = get_environment(true, $templates, array(), array('upper')); +$t->is($twig->loadTemplate('1_basic2')->render($params), 'FABIEN', 'Sandbox allow some filters'); + +$twig = get_environment(true, $templates, array('if')); +$t->is($twig->loadTemplate('1_basic3')->render($params), 'foo', 'Sandbox allow some tags'); + +$twig = get_environment(true, $templates, array(), array(), array(), array('Object' => 'bar')); +$t->is($twig->loadTemplate('1_basic4')->render($params), 'bar', 'Sandbox allow some properties'); + +$t->diag('Sandbox locally set for an include'); + +$templates = array( + '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}', + '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', +); + +$twig = get_environment(false, $templates); +$t->is($twig->loadTemplate('2_basic')->render($params), 'fooFOOfoo', 'Sandbox does nothing if disabled globally and sandboxed not used for the include'); + +$templates = array( + '3_basic' => '{{ obj.foo }}{% include "3_included" sandboxed %}{{ obj.foo }}', + '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', +); + +$twig = get_environment(false, $templates); +$twig = get_environment(true, $templates); +try +{ + $twig->loadTemplate('3_basic')->render($params); + $t->fail('Sandbox throws a SecurityError exception when the included file is sandboxed'); +} +catch (Twig_Sandbox_SecurityError $e) +{ + $t->pass('Sandbox throws a SecurityError exception when the included file is sandboxed'); +} + + +function get_environment($sandboxed, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array()) +{ + $loader = new Twig_Loader_Array($templates); + $twig = new Twig_Environment($loader, array('trim_blocks' => true, 'debug' => true)); + $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties); + $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed)); + + return $twig; +} diff --git a/test/unit/integrationTest.php b/test/unit/integrationTest.php index 43785b4..4a63bb0 100644 --- a/test/unit/integrationTest.php +++ b/test/unit/integrationTest.php @@ -15,8 +15,6 @@ LimeAutoloader::register(); require_once dirname(__FILE__).'/../../lib/Twig/Autoloader.php'; Twig_Autoloader::register(); -require_once dirname(__FILE__).'/../lib/Twig_Loader_Var.php'; - class Foo { public function bar($param1 = null, $param2 = null) @@ -52,20 +50,19 @@ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesD throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); } - $prefix = rand(1, 999999999); $message = $match[1]; $templates = array(); preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $match[2], $matches, PREG_SET_ORDER); foreach ($matches as $match) { - $templates[$prefix.'_'.($match[1] ? $match[1] : 'index.twig')] = $match[2]; + $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2]; } - $loader = new Twig_Loader_Var($templates, $prefix); - $twig = new Twig_Environment($loader, array('trim_blocks' => true)); + $loader = new Twig_Loader_Array($templates); + $twig = new Twig_Environment($loader, array('trim_blocks' => true, 'cache' => false)); $twig->addExtension(new Twig_Extension_Escaper()); - $template = $twig->loadTemplate($prefix.'_index.twig'); + $template = $twig->loadTemplate('index.twig'); preg_match_all('/--DATA--(.*?)--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $matches, PREG_SET_ORDER); foreach ($matches as $match) -- 1.7.2.5