optimized the filesystem loader
authorFabien Potencier <fabien.potencier@gmail.com>
Sun, 4 Aug 2013 06:27:13 +0000 (08:27 +0200)
committerFabien Potencier <fabien.potencier@gmail.com>
Sun, 4 Aug 2013 06:48:38 +0000 (08:48 +0200)
* added a cache for templates that do not exist
* sped up Filesystem::exists() by avoiding the creation of exceptions

CHANGELOG
lib/Twig/Loader/Filesystem.php

index 44a483f..d4b5f5f 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 * 1.14.0 (2013-XX-XX)
 
+ * improved the performance of the filesystem loader
  * fixed the filesystem loader cache when a template name exists in several namespaces
  * added support for calling macros defined in a template without importing them first
  * added support for named arguments for macros
index 5ce13dd..aea1c6c 100644 (file)
@@ -21,6 +21,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
 
     protected $paths;
     protected $cache;
+    protected $errorCache;
 
     /**
      * Constructor.
@@ -87,7 +88,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
     public function addPath($path, $namespace = self::MAIN_NAMESPACE)
     {
         // invalidate the cache
-        $this->cache = array();
+        $this->cache = $this->errorCache = array();
 
         if (!is_dir($path)) {
             throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
@@ -107,7 +108,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
     public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
     {
         // invalidate the cache
-        $this->cache = array();
+        $this->cache = $this->errorCache = array();
 
         if (!is_dir($path)) {
             throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
@@ -143,18 +144,19 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
      */
     public function exists($name)
     {
-        $name = (string) $name;
+        $name = $this->normalizeName($name);
+
         if (isset($this->cache[$name])) {
             return true;
         }
 
-        try {
-            $this->findTemplate($name);
-
-            return true;
-        } catch (Twig_Error_Loader $exception) {
+        if (isset($this->errorCache[$name])) {
             return false;
         }
+
+        $this->doFindTemplate($name);
+
+        return isset($this->cache[$name]);
     }
 
     /**
@@ -167,22 +169,36 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
 
     protected function findTemplate($name)
     {
-        $name = (string) $name;
+        $name = $this->normalizeName($name);
+
+        if (isset($this->cache[$name])) {
+            return $this->cache[$name];
+        }
+
+        if (isset($this->errorCache[$name])) {
+            throw new Twig_Error_Loader($this->errorCache[$name]);
+        }
 
-        // normalize name
-        $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
+        $this->doFindTemplate($name);
 
         if (isset($this->cache[$name])) {
             return $this->cache[$name];
         }
 
+        throw new Twig_Error_Loader($this->errorCache[$name]);
+    }
+
+    protected function doFindTemplate($name, $exists = false)
+    {
         $this->validateName($name);
 
         $namespace = self::MAIN_NAMESPACE;
         $shortname = $name;
         if (isset($name[0]) && '@' == $name[0]) {
             if (false === $pos = strpos($name, '/')) {
-                throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
+                $this->errorCache[$name] = sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name);
+
+                return;
             }
 
             $namespace = substr($name, 1, $pos - 1);
@@ -190,16 +206,25 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
         }
 
         if (!isset($this->paths[$namespace])) {
-            throw new Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace));
+            $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace);
+
+            return;
         }
 
         foreach ($this->paths[$namespace] as $path) {
             if (is_file($path.'/'.$shortname)) {
-                return $this->cache[$name] = $path.'/'.$shortname;
+                $this->cache[$name] = $path.'/'.$shortname;
+
+                return;
             }
         }
 
-        throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])));
+        $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]));
+    }
+
+    protected function normalizeName($name)
+    {
+        return preg_replace('#/{2,}#', '/', strtr((string) $name, '\\', '/'));
     }
 
     protected function validateName($name)