* 1.5.0
+ * added support for dynamically named filters and functions
* added a dump function to help debugging templates
* added a nl2br filter
* added a random function
$filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
+Dynamic Filters
+~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.5
+ Dynamic filters support was added in Twig 1.5.
+
+A filter name containing the special ``*`` character is a dynamic filter as
+the ``*`` can be any string::
+
+ $twig->addFilter('*_path', new Twig_Filter_Function('twig_path'));
+
+ function twig_path($name, $arguments)
+ {
+ // ...
+ }
+
+The following filters will be matched by the above defined dynamic filter:
+
+* ``product_path``
+* ``category_path``
+
+A dynamic filter can define more than one dynamic parts::
+
+ $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
+
+ function twig_path($name, $suffix, $arguments)
+ {
+ // ...
+ }
+
+The filter will receive all dynamic part values before the normal filters
+arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the
+following PHP call: ``twig_path('a', 'b', 'foo')``.
+
Functions
---------
Functions also support ``needs_environment`` and ``is_safe`` parameters.
+Dynamic Functions
+~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.5
+ Dynamic functions support was added in Twig 1.5.
+
+A function name containing the special ``*`` character is a dynamic function
+as the ``*`` can be any string::
+
+ $twig->addFunction('*_path', new Twig_Function_Function('twig_path'));
+
+ function twig_path($name, $arguments)
+ {
+ // ...
+ }
+
+The following functions will be matched by the above defined dynamic function:
+
+* ``product_path``
+* ``category_path``
+
+A dynamic function can define more than one dynamic parts::
+
+ $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
+
+ function twig_path($name, $suffix, $arguments)
+ {
+ // ...
+ }
+
+The function will receive all dynamic part values before the normal functions
+arguments. For instance, a call to ``a_path_b('foo')`` will result in the
+following PHP call: ``twig_path('a', 'b', 'foo')``.
+
Tags
----
return $this->filters[$name];
}
+ foreach ($this->filters as $pattern => $filter) {
+ $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
+
+ if ($count) {
+ if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
+ array_shift($matches);
+ $filter->setArguments($matches);
+
+ return $filter;
+ }
+ }
+ }
+
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = call_user_func($callback, $name)) {
return $filter;
return $this->functions[$name];
}
+ foreach ($this->functions as $pattern => $function) {
+ $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
+
+ if ($count) {
+ if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
+ array_shift($matches);
+ $function->setArguments($matches);
+
+ return $function;
+ }
+ }
+ }
+
foreach ($this->functionCallbacks as $callback) {
if (false !== $function = call_user_func($callback, $name)) {
return $function;
abstract class Twig_Filter implements Twig_FilterInterface
{
protected $options;
+ protected $arguments = array();
public function __construct(array $options = array())
{
), $options);
}
+ public function setArguments($arguments)
+ {
+ $this->arguments = $arguments;
+ }
+
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
public function needsEnvironment()
{
return $this->options['needs_environment'];
function getSafe(Twig_Node $filterArgs);
function getPreEscape();
+
+ function setArguments($arguments);
+
+ function getArguments();
}
abstract class Twig_Function implements Twig_FunctionInterface
{
protected $options;
+ protected $arguments = array();
public function __construct(array $options = array())
{
), $options);
}
+ public function setArguments($arguments)
+ {
+ $this->arguments = $arguments;
+ }
+
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
public function needsEnvironment()
{
return $this->options['needs_environment'];
function needsContext();
function getSafe(Twig_Node $filterArgs);
+
+ function setArguments($arguments);
+
+ function getArguments();
}
->raw($filter->compile().'(')
->raw($filter->needsEnvironment() ? '$this->env, ' : '')
->raw($filter->needsContext() ? '$context, ' : '')
- ->subcompile($this->getNode('node'))
;
+ foreach ($filter->getArguments() as $argument) {
+ $compiler
+ ->string($argument)
+ ->raw(', ')
+ ;
+ }
+
+ $compiler->subcompile($this->getNode('node'));
+
foreach ($this->getNode('arguments') as $node) {
$compiler
->raw(', ')
throw new Twig_Error_Syntax($message, $this->getLine());
}
- $compiler
- ->raw($function->compile().'(')
- ->raw($function->needsEnvironment() ? '$this->env' : '')
- ;
+ $compiler->raw($function->compile().'(');
+
+ $first = true;
+
+ if ($function->needsEnvironment()) {
+ $compiler->raw('$this->env');
+ $first = false;
+ }
if ($function->needsContext()) {
- $compiler->raw($function->needsEnvironment() ? ', $context' : '$context');
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $compiler->raw('$context');
+ $first = false;
+ }
+
+ foreach ($function->getArguments() as $argument) {
+ if (!$first) {
+ $compiler->raw(', ');
+ }
+ $compiler->string($argument);
+ $first = false;
}
- $first = true;
foreach ($this->getNode('arguments') as $node) {
if (!$first) {
$compiler->raw(', ');
- } else {
- if ($function->needsEnvironment() || $function->needsContext()) {
- $compiler->raw(', ');
- }
- $first = false;
}
$compiler->subcompile($node);
+ $first = false;
}
$compiler->raw(')');
--- /dev/null
+--TEST--
+dynamic filter
+--TEMPLATE--
+{{ 'bar'|foo_path }}
+{{ 'bar'|a_foo_b_bar }}
+--DATA--
+return array()
+--EXPECT--
+foo/bar
+a/b/bar
--- /dev/null
+--TEST--
+dynamic function
+--TEMPLATE--
+{{ foo_path('bar') }}
+{{ a_foo_b_bar('bar') }}
+--DATA--
+return array()
+--EXPECT--
+foo/bar
+a/b/bar
public function getFilters()
{
return array(
- '☃' => new Twig_Filter_Method($this, '☃Filter'),
+ '☃' => new Twig_Filter_Method($this, '☃Filter'),
'escape_and_nl2br' => new Twig_Filter_Method($this, 'escape_and_nl2br', array('needs_environment' => true, 'is_safe' => array('html'))),
- 'nl2br' => new Twig_Filter_Method($this, 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
+ 'nl2br' => new Twig_Filter_Method($this, 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
'escape_something' => new Twig_Filter_Method($this, 'escape_something', array('is_safe' => array('something'))),
+ '*_path' => new Twig_Filter_Method($this, 'dynamic_path'),
+ '*_foo_*_bar' => new Twig_Filter_Method($this, 'dynamic_foo'),
);
}
public function getFunctions()
{
return array(
- '☃' => new Twig_Function_Method($this, '☃Function'),
- 'safe_br' => new Twig_Function_Method($this, 'br', array('is_safe' => array('html'))),
- 'unsafe_br' => new Twig_Function_Method($this, 'br'),
+ '☃' => new Twig_Function_Method($this, '☃Function'),
+ 'safe_br' => new Twig_Function_Method($this, 'br', array('is_safe' => array('html'))),
+ 'unsafe_br' => new Twig_Function_Method($this, 'br'),
+ '*_path' => new Twig_Function_Method($this, 'dynamic_path'),
+ '*_foo_*_bar' => new Twig_Function_Method($this, 'dynamic_foo'),
);
}
return str_replace("\n", "$sep\n", $value);
}
+ public function dynamic_path($element, $item)
+ {
+ return $element.'/'.$item;
+ }
+
+ public function dynamic_foo($foo, $bar, $item)
+ {
+ return $foo.'/'.$bar.'/'.$item;
+ }
+
public function escape_something($value)
{
return strtoupper($value);