improved the filter system to allow object methods to be used as filters
authorfabien <fabien@93ef8e89-cb99-4229-a87c-7fa0fa45744b>
Mon, 14 Dec 2009 15:29:19 +0000 (15:29 +0000)
committerfabien <fabien@93ef8e89-cb99-4229-a87c-7fa0fa45744b>
Mon, 14 Dec 2009 15:29:19 +0000 (15:29 +0000)
git-svn-id: http://svn.twig-project.org/trunk@180 93ef8e89-cb99-4229-a87c-7fa0fa45744b

CHANGELOG
doc/04-Extending-Twig.markdown
lib/Twig/Extension/Core.php
lib/Twig/Extension/Escaper.php
lib/Twig/Filter.php [new file with mode: 0644]
lib/Twig/Filter/Function.php [new file with mode: 0644]
lib/Twig/Filter/Method.php [new file with mode: 0644]
lib/Twig/Node/Expression/Filter.php

index a9cd814..5334216 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,18 @@
 * 0.9.5-DEV
 
+If you have defined custom filters, you MUST upgrade them for this release. To
+upgrade, replace "array" with "new Twig_Filter_Function", and replace the
+environment constant by the "needs_environment" option:
+
+  // before
+  'even'   => array('twig_is_even_filter', false),
+  'escape' => array('twig_escape_filter', true),
+
+  // after
+  'even'   => new Twig_Filter_Function('twig_is_even_filter'),
+  'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
+
+ * improved the filter system to allow object methods to be used as filters
  * changed the Array and String loaders to actually make use of the cache mechanism
  * included the default filter function definitions in the extension class files directly (Core, Escaper)
  * added the .. operator (as a syntactic sugar for the range filter when the step is 1)
index a3da39f..5c91c28 100644 (file)
@@ -57,15 +57,25 @@ Instead of you implementing the whole interface, your extension class can
 inherit from the `Twig_Extension` class, which provides empty implementations
 of all the above methods to keep your extension clean.
 
+The `getName()` method must always be implemented to return a unique
+identifier for the extension.
+
 >**TIP**
 >The bundled extensions are great examples of how extensions work.
 
+Registering a custom extension is like registering any other core extension:
+
+    [php]
+    $twig->addExtension(new Project_Twig_Extension());
+
 Defining new Filters
 --------------------
 
 The most common element you will want to add to Twig is filters. A filter is
-just a regular PHP function that takes the left side of the filter as first
-argument and the arguments passed to the filter as extra arguments.
+just a regular PHP function or method that takes the left side of the filter
+as first argument and the arguments passed to the filter as extra arguments.
+
+### Function Filters
 
 Let's create a filter, named `rot13`, which returns the
 [rot13](http://www.php.net/manual/en/function.str-rot13.php) transformation of
@@ -76,7 +86,17 @@ a string:
 
     {# should displays Gjvt #}
 
-Here is the simplest extension class you can create to add this filter:
+A filter is defined as a sub-class of the `Twig_Filter` class. The
+`Twig_Filter_Function` class can be used to define a filter implemented as a
+function:
+
+    $filter = new Twig_Filter_Function('str_rot13');
+
+The first argument is the name of the function to call, here `str_rot13`, a
+native PHP function.
+
+Registering the filter in an extension means implementing the `getFilters()`
+method:
 
     [php]
     class Project_Twig_Extension extends Twig_Extension
@@ -84,7 +104,7 @@ Here is the simplest extension class you can create to add this filter:
       public function getFilters()
       {
         return array(
-          'rot13' => array('str_rot13', false),
+          'rot13' => new Twig_Filter_Function('str_rot13'),
         );
       }
 
@@ -94,29 +114,17 @@ Here is the simplest extension class you can create to add this filter:
       }
     }
 
-Registering the new extension is like registering core extensions:
+The `Twig_Filter` classes take flags as their last argument. For instance, if
+you want access to the current environment instance in your filter, set the
+`needs_environment` option to `true`:
 
     [php]
-    $twig->addExtension(new Project_Twig_Extension());
+    $filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true));
 
-Filters can also be passed the current environment. You might have noticed
-that a filter is defined by a function name and a Boolean. If you change the
-Boolean to `true`, Twig will pass the current environment as the first
-argument to the filter call:
+Twig will then pass the current environment as the first argument to the
+filter call:
 
     [php]
-    class Project_Twig_Extension extends Twig_Extension
-    {
-      public function getFilters()
-      {
-        return array(
-          'rot13' => array('twig_compute_rot13', true),
-        );
-      }
-
-      // ...
-    }
-
     function twig_compute_rot13(Twig_Environment $env, $string)
     {
       // get the current charset for instance
@@ -142,6 +150,62 @@ function call:
       return $prefix.str_rot13($string);
     }
 
+### Class Method Filters
+
+The `Twig_Filter_Function` class can also be used to register static method as
+filters:
+
+    [php]
+    class Project_Twig_Extension extends Twig_Extension
+    {
+      public function getFilters()
+      {
+        return array(
+          'rot13' => new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'),
+        );
+      }
+
+      static public function rot13Filter($string)
+      {
+        return str_rot13($string);
+      }
+
+      public function getName()
+      {
+        return 'project';
+      }
+    }
+
+### Object Method Filters
+
+You can also register methods as filters by using the `Twig_Filter_Method`
+class:
+
+    [php]
+    class Project_Twig_Extension extends Twig_Extension
+    {
+      public function getFilters()
+      {
+        return array(
+          'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
+        );
+      }
+
+      public function rot13Filter($string)
+      {
+        return str_rot13($string);
+      }
+
+      public function getName()
+      {
+        return 'project';
+      }
+    }
+
+Using methods for filters is a great way to package your filter without
+polluting the global namespace. This also gives the developer more flexibility
+at the cost of a small overhead.
+
 Overriding default Filters
 --------------------------
 
@@ -158,15 +222,15 @@ it and override the filter(s) by overriding the `getFilters()` method:
         return array_merge(
           parent::getFilters(),
           array(
-            'date' => array('my_date_format_filter', false)
+            'date' => Twig_Filter_Method($this, 'dateFilter')
           )
         );
       }
-    }
 
-    function my_date_format_filter($timestamp, $format = 'F j, Y H:i')
-    {
-      return '...'.twig_date_format_filter($timestamp, $format);
+      public function dateFilter($timestamp, $format = 'F j, Y H:i')
+      {
+        return '...'.twig_date_format_filter($timestamp, $format);
+      }
     }
 
 Here, we override the `date` filter with a custom one. Using this new core
index 34672be..5ab0138 100644 (file)
@@ -61,45 +61,45 @@ class Twig_Extension_Core extends Twig_Extension
   {
     $filters = array(
       // formatting filters
-      'date'   => array('twig_date_format_filter', false),
-      'format' => array('sprintf', false),
+      'date'   => new Twig_Filter_Function('twig_date_format_filter'),
+      'format' => new Twig_Filter_Function('sprintf'),
 
       // numbers
-      'even' => array('twig_is_even_filter', false),
-      'odd'  => array('twig_is_odd_filter', false),
+      'even' => new Twig_Filter_Function('twig_is_even_filter'),
+      'odd'  => new Twig_Filter_Function('twig_is_odd_filter'),
 
       // encoding
-      'urlencode' => array('twig_urlencode_filter', false),
+      'urlencode' => new Twig_Filter_Function('twig_urlencode_filter'),
 
       // string filters
-      'title'      => array('twig_title_string_filter', true),
-      'capitalize' => array('twig_capitalize_string_filter', true),
-      'upper'      => array('strtoupper', false),
-      'lower'      => array('strtolower', false),
-      'striptags'  => array('strip_tags', false),
+      'title'      => new Twig_Filter_Function('twig_title_string_filter', array('needs_environment' => true)),
+      'capitalize' => new Twig_Filter_Function('twig_capitalize_string_filter', array('needs_environment' => true)),
+      'upper'      => new Twig_Filter_Function('strtoupper'),
+      'lower'      => new Twig_Filter_Function('strtolower'),
+      'striptags'  => new Twig_Filter_Function('strip_tags'),
 
       // array helpers
-      'join'    => array('twig_join_filter', false),
-      'reverse' => array('twig_reverse_filter', false),
-      'length'  => array('twig_length_filter', false),
-      'sort'    => array('twig_sort_filter', false),
-      'in'      => array('twig_in_filter', false),
-      'range'   => array('twig_range_filter', false),
+      'join'    => new Twig_Filter_Function('twig_join_filter'),
+      'reverse' => new Twig_Filter_Function('twig_reverse_filter'),
+      'length'  => new Twig_Filter_Function('twig_length_filter'),
+      'sort'    => new Twig_Filter_Function('twig_sort_filter'),
+      'in'      => new Twig_Filter_Function('twig_in_filter'),
+      'range'   => new Twig_Filter_Function('twig_range_filter'),
 
       // iteration and runtime
-      'default' => array('twig_default_filter', false),
-      'keys'    => array('twig_get_array_keys_filter', false),
-      'items'   => array('twig_get_array_items_filter', false),
+      'default' => new Twig_Filter_Function('twig_default_filter'),
+      'keys'    => new Twig_Filter_Function('twig_get_array_keys_filter'),
+      'items'   => new Twig_Filter_Function('twig_get_array_items_filter'),
 
       // escaping
-      'escape' => array('twig_escape_filter', true),
-      'e'      => array('twig_escape_filter', true),
+      'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
+      'e'      => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
     );
 
     if (function_exists('mb_get_info'))
     {
-      $filters['upper'] = array('twig_upper_filter', true);
-      $filters['lower'] = array('twig_lower_filter', true);
+      $filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true));
+      $filters['lower'] = new Twig_Filter_Function('twig_lower_filter', array('needs_environment' => true));
     }
 
     return $filters;
index cefe3ac..681989d 100644 (file)
@@ -54,7 +54,7 @@ class Twig_Extension_Escaper extends Twig_Extension
   public function getFilters()
   {
     return array(
-      'safe' => array('twig_safe_filter', false),
+      'safe' => new Twig_Filter_Function('twig_safe_filter'),
     );
   }
 
diff --git a/lib/Twig/Filter.php b/lib/Twig/Filter.php
new file mode 100644 (file)
index 0000000..f4249a2
--- /dev/null
@@ -0,0 +1,36 @@
+<?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.
+ */
+
+/**
+ * Represents a template filter.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
+abstract class Twig_Filter
+{
+  protected $options;
+
+  public function __construct(array $options = array())
+  {
+    $this->options = array_merge(array(
+      'needs_environment' => false,
+    ), $options);
+  }
+
+  abstract public function compile();
+
+  public function needsEnvironment()
+  {
+    return $this->options['needs_environment'];
+  }
+}
diff --git a/lib/Twig/Filter/Function.php b/lib/Twig/Filter/Function.php
new file mode 100644 (file)
index 0000000..da8e886
--- /dev/null
@@ -0,0 +1,34 @@
+<?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.
+ */
+
+/**
+ * Represents a function template filter.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
+class Twig_Filter_Function extends Twig_Filter
+{
+  protected $function;
+
+  public function __construct($function, array $options = array())
+  {
+    parent::__construct($options);
+
+    $this->function = $function;
+  }
+
+  public function compile()
+  {
+    return $this->function;
+  }
+}
diff --git a/lib/Twig/Filter/Method.php b/lib/Twig/Filter/Method.php
new file mode 100644 (file)
index 0000000..755eac1
--- /dev/null
@@ -0,0 +1,35 @@
+<?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.
+ */
+
+/**
+ * Represents a method template filter.
+ *
+ * @package    twig
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id$
+ */
+class Twig_Filter_Method extends Twig_Filter
+{
+  protected $extension, $method;
+
+  public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
+  {
+    parent::__construct($options);
+
+    $this->extension = $extension;
+    $this->method = $method;
+  }
+
+  public function compile()
+  {
+    return sprintf('$this->getEnvironment()->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
+  }
+}
index a89cca0..f796ac9 100644 (file)
@@ -71,7 +71,7 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression implements Twig_N
       }
       else
       {
-        $compiler->raw($filterMap[$name][0].($filterMap[$name][1] ? '($this->getEnvironment(), ' : '('));
+        $compiler->raw($filterMap[$name]->compile().($filterMap[$name]->needsEnvironment() ? '($this->getEnvironment(), ' : '('));
       }
       $postponed[] = $attrs;
     }