From 215ed4864d598339faa6beeee965455130d109cc Mon Sep 17 00:00:00 2001 From: fabien Date: Mon, 14 Dec 2009 15:29:19 +0000 Subject: [PATCH] improved the filter system to allow object methods to be used as filters git-svn-id: http://svn.twig-project.org/trunk@180 93ef8e89-cb99-4229-a87c-7fa0fa45744b --- CHANGELOG | 13 ++++ doc/04-Extending-Twig.markdown | 118 +++++++++++++++++++++++++++-------- lib/Twig/Extension/Core.php | 46 +++++++------- lib/Twig/Extension/Escaper.php | 2 +- lib/Twig/Filter.php | 36 +++++++++++ lib/Twig/Filter/Function.php | 34 ++++++++++ lib/Twig/Filter/Method.php | 35 ++++++++++ lib/Twig/Node/Expression/Filter.php | 2 +- 8 files changed, 234 insertions(+), 52 deletions(-) create mode 100644 lib/Twig/Filter.php create mode 100644 lib/Twig/Filter/Function.php create mode 100644 lib/Twig/Filter/Method.php diff --git a/CHANGELOG b/CHANGELOG index a9cd814..5334216 100644 --- 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) diff --git a/doc/04-Extending-Twig.markdown b/doc/04-Extending-Twig.markdown index a3da39f..5c91c28 100644 --- a/doc/04-Extending-Twig.markdown +++ b/doc/04-Extending-Twig.markdown @@ -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 diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index 34672be..5ab0138 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -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; diff --git a/lib/Twig/Extension/Escaper.php b/lib/Twig/Extension/Escaper.php index cefe3ac..681989d 100644 --- a/lib/Twig/Extension/Escaper.php +++ b/lib/Twig/Extension/Escaper.php @@ -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 index 0000000..f4249a2 --- /dev/null +++ b/lib/Twig/Filter.php @@ -0,0 +1,36 @@ + + * @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 index 0000000..da8e886 --- /dev/null +++ b/lib/Twig/Filter/Function.php @@ -0,0 +1,34 @@ + + * @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 index 0000000..755eac1 --- /dev/null +++ b/lib/Twig/Filter/Method.php @@ -0,0 +1,35 @@ + + * @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); + } +} diff --git a/lib/Twig/Node/Expression/Filter.php b/lib/Twig/Node/Expression/Filter.php index a89cca0..f796ac9 100644 --- a/lib/Twig/Node/Expression/Filter.php +++ b/lib/Twig/Node/Expression/Filter.php @@ -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; } -- 1.7.2.5