refactored loaders (closes #38)
authorfabien <fabien@93ef8e89-cb99-4229-a87c-7fa0fa45744b>
Mon, 16 Nov 2009 15:01:52 +0000 (15:01 +0000)
committerfabien <fabien@93ef8e89-cb99-4229-a87c-7fa0fa45744b>
Mon, 16 Nov 2009 15:01:52 +0000 (15:01 +0000)
git-svn-id: http://svn.twig-project.org/trunk@142 93ef8e89-cb99-4229-a87c-7fa0fa45744b

19 files changed:
CHANGELOG
doc/03-Twig-for-Developers.markdown
lib/Twig/Environment.php
lib/Twig/Loader.php [deleted file]
lib/Twig/Loader/Array.php
lib/Twig/Loader/Filesystem.php
lib/Twig/Loader/String.php
lib/Twig/LoaderInterface.php
lib/Twig/Node/Import.php
lib/Twig/Node/Module.php
test/fixtures/tags/include/basic.test
test/fixtures/tags/include/expression.test
test/fixtures/tags/inheritance/basic.test
test/fixtures/tags/inheritance/parent.test
test/fixtures/tags/macro/basic.test
test/fixtures/tags/macro/external.test
test/lib/Twig_Loader_Var.php [deleted file]
test/unit/Twig/Extension/SandboxTest.php [moved from test/unit/Twig/Extension/Sandbox.php with 96% similarity]
test/unit/integrationTest.php

index 01d5883..6811b4e 100644 (file)
--- 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)
 
index 9457eae..be94e34 100644 (file)
@@ -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
 ----------------
index 8b9bf17..ef7392e 100644 (file)
@@ -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 (file)
index 2e0f3fe..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-/*
- * This file is part of Twig.
- *
- * (c) 2009 Fabien Potencier
- * (c) 2009 Armin Ronacher
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * Base loader class for all builtin loaders.
- *
- * @package    twig
- * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
- * @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);
-}
index 265f3d0..2d3fbe7 100644 (file)
@@ -16,7 +16,7 @@
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @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;
   }
 }
index 839421b..3df61dd 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @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)));
index c135fe4..1c3eb1b 100644 (file)
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  * @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;
   }
 }
index 4682f08..fee97a0 100644 (file)
 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);
 }
index d5ef05c..8978839 100644 (file)
@@ -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(")
index 80a3df6..55443e0 100644 (file)
@@ -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")
       ;
index 1f52614..8fe1a6c 100644 (file)
@@ -2,7 +2,7 @@
 "include" tag
 --TEMPLATE--
 FOO
-{% include "%prefix%foo.twig" %}
+{% include "foo.twig" %}
 
 BAR
 --TEMPLATE(foo.twig)--
index df40e2a..eaeeb11 100644 (file)
@@ -2,7 +2,7 @@
 "include" tag allows expressions for the template to include
 --TEMPLATE--
 FOO
-{% include "%prefix%" ~ foo %}
+{% include foo %}
 
 BAR
 --TEMPLATE(foo.twig)--
index a9df3df..0778a4b 100644 (file)
@@ -1,7 +1,7 @@
 --TEST--
 "extends" tag
 --TEMPLATE--
-{% extends "%prefix%foo.twig" %}
+{% extends "foo.twig" %}
 
 {% block content %}
 FOO
index b2e5daf..b4a1daf 100644 (file)
@@ -1,7 +1,7 @@
 --TEST--
 "extends" tag
 --TEMPLATE--
-{% extends "%prefix%foo.twig" %}
+{% extends "foo.twig" %}
 
 {% block content %}{% parent %}FOO{% parent %}{% endblock %}
 --TEMPLATE(foo.twig)--
index be2a20b..2bbae8f 100644 (file)
@@ -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) }}
index d364170..5cd3dae 100644 (file)
@@ -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 (file)
index c2a5b9c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-/*
- * This file is part of Twig.
- *
- * (c) 2009 Fabien Potencier
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * Loads a template from variables.
- *
- * @package    twig
- * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
- * @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);
-  }
-}
similarity index 96%
rename from test/unit/Twig/Extension/Sandbox.php
rename to test/unit/Twig/Extension/SandboxTest.php
index c929edd..0cc6dd3 100644 (file)
@@ -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 Object
 {
   public $bar = 'bar';
@@ -131,11 +129,7 @@ catch (Twig_Sandbox_SecurityError $e)
 
 function get_environment($sandboxed, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array())
 {
-  static $prefix = 0;
-
-  ++$prefix;
-
-  $loader = new Twig_Loader_Var($templates, $prefix);
+  $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));
index 43785b4..4a63bb0 100644 (file)
@@ -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)