added callbacks for undefined functions and filters
authorFabien Potencier <fabien.potencier@gmail.com>
Sat, 8 Jan 2011 14:14:59 +0000 (15:14 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Sat, 8 Jan 2011 14:16:33 +0000 (15:16 +0100)
doc/recipes.rst
lib/Twig/Environment.php

index a615507..3387a35 100644 (file)
@@ -208,3 +208,36 @@ The output will be similar to:
 In the inner loop, the ``loop.parent`` variable is used to access the outer
 context. So, the index of the current ``topic`` defined in the outer for loop
 is accessible via the ``loop.parent.loop.index`` variable.
+
+Define undefined Functions and Filters on the Fly
+-------------------------------------------------
+
+When a function (or a filter) is not defined, Twig defaults to throw a
+``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
+valid PHP callable) which should return a function (or a filter).
+
+For filters, register callbacks with ``registerUndefinedFunctionCallback()``.
+For functions, use ``registerUndefinedFunctionCallback()``::
+
+    // auto-register all native PHP functions as Twig functions
+    // don't try this at home as it's not secure at all!
+    $twig->registerUndefinedFunctionCallback(function ($name) {
+        if (function_exists($name)) {
+            return new Twig_Function_Function($name);
+        }
+
+        return false;
+    });
+
+If the callable is not able to return a valid function (or filter), it must
+return ``false``.
+
+If you register more than one callback, Twig will call them in turn until one
+does not return ``false``.
+
+.. tip::
+
+    As the resolution of functions and filters is done during compilation,
+    there is no overhead when registering these callbacks.
+
+.. _callback:: http://www.php.net/manual/en/function.is-callable.php
index cfe92a1..ac52398 100644 (file)
@@ -41,6 +41,8 @@ class Twig_Environment
     protected $unaryOperators;
     protected $binaryOperators;
     protected $templateClassPrefix = '__TwigTemplate_';
+    protected $functionCallbacks;
+    protected $filterCallbacks;
 
     /**
      * Constructor.
@@ -104,6 +106,8 @@ class Twig_Environment
         $this->strictVariables    = (bool) $options['strict_variables'];
         $this->runtimeInitialized = false;
         $this->setCache($options['cache']);
+        $this->functionCallbacks = array();
+        $this->filterCallbacks = array();
     }
 
     /**
@@ -673,9 +677,20 @@ class Twig_Environment
             return $this->filters[$name];
         }
 
+        foreach ($this->filterCallbacks as $callback) {
+            if (false !== $filter = call_user_func($callback, $name)) {
+                return $filter;
+            }
+        }
+
         return false;
     }
 
+    public function registerUndefinedFilterCallback($callable)
+    {
+        $this->filterCallbacks[] = $callable;
+    }
+
     /**
      * Gets the registered Filters.
      *
@@ -756,9 +771,20 @@ class Twig_Environment
             return $this->functions[$name];
         }
 
+        foreach ($this->functionCallbacks as $callback) {
+            if (false !== $function = call_user_func($callback, $name)) {
+                return $function;
+            }
+        }
+
         return false;
     }
 
+    public function registerUndefinedFunctionCallback($callable)
+    {
+        $this->functionCallbacks[] = $callable;
+    }
+
     protected function loadFunctions() {
         $this->functions = array();
         foreach ($this->getExtensions() as $extension) {