Handle functions like filters

Functions are not global variables anymore. They are resolved at
compile time, and Twig_Function objects are instanciated only when
compiling.
This commit is contained in:
Arnaud Le Blanc
2010-12-24 17:21:10 +01:00
committed by Fabien Potencier
parent b247452ec5
commit c225a5bed9
14 changed files with 218 additions and 72 deletions
+38
View File
@@ -27,6 +27,7 @@ class Twig_Environment
protected $visitors;
protected $filters;
protected $tests;
protected $functions;
protected $globals;
protected $runtimeInitialized;
protected $loadedTemplates;
@@ -455,6 +456,43 @@ class Twig_Environment
return $this->tests;
}
public function addFunction($name, Twig_Function $function)
{
if (null === $this->functions) {
$this->loadFunctions();
}
$this->functions[$name] = $function;
}
/**
* Get a function by name
*
* Subclasses may override getFunction($name) and load functions differently;
* so no list of functions is available.
*
* @param string $name function name
* @return Twig_Function|null A Twig_Function instance or null if the function does not exists
*/
public function getFunction($name)
{
if (null === $this->functions) {
$this->loadFunctions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
return null;
}
protected function loadFunctions() {
$this->functions = array();
foreach ($this->getExtensions() as $extension) {
$this->functions = array_merge($this->functions, $extension->getFunctions());
}
}
public function addGlobal($name, $value)
{
if (null === $this->globals) {
+10
View File
@@ -60,6 +60,16 @@ abstract class Twig_Extension implements Twig_ExtensionInterface
{
return array();
}
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of functions
*/
public function getFunctions()
{
return array();
}
/**
* Returns a list of operators to add to the existing list.
+4 -4
View File
@@ -87,12 +87,12 @@ class Twig_Extension_Core extends Twig_Extension
*
* @return array An array of global functions
*/
public function getGlobals()
public function getFunctions()
{
return array(
'fn_range' => new Twig_Function($this, 'getRange'),
'fn_constant' => new Twig_Function($this, 'getConstant'),
'fn_cycle' => new Twig_Function($this, 'getCycle'),
'range' => new Twig_Function_Method($this, 'getRange'),
'constant' => new Twig_Function_Method($this, 'getConstant'),
'cycle' => new Twig_Function_Method($this, 'getCycle'),
);
}
+7
View File
@@ -54,6 +54,13 @@ interface Twig_ExtensionInterface
*/
public function getTests();
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of functions
*/
public function getFunctions();
/**
* Returns a list of operators to add to the existing list.
*
+19 -11
View File
@@ -10,29 +10,37 @@
*/
/**
* Defines a new Twig function.
* Represents a template function.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_Function extends Exception
abstract class Twig_Function implements Twig_FunctionInterface
{
protected $object;
protected $method;
protected $options;
public function __construct($object, $method)
public function __construct(array $options = array())
{
$this->object = $object;
$this->method = $method;
$this->options = array_merge(array(
'needs_environment' => false,
), $options);
}
public function getObject()
public function needsEnvironment()
{
return $this->object;
return $this->options['needs_environment'];
}
public function getMethod()
public function getSafe(Twig_Node $functionArgs)
{
return $this->method;
if (isset($this->options['is_safe'])) {
return $this->options['is_safe'];
}
if (isset($this->options['is_safe_callback'])) {
return call_user_func($this->options['is_safe_callback'], $functionArgs);
}
return array();
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2010 Arnaud Le Blanc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a function template function.
*
* @package twig
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
*/
class Twig_Function_Function extends Twig_Function
{
protected $function;
public function __construct($function, array $options = array())
{
parent::__construct($options);
$this->function = $function;
}
public function compile()
{
return $this->function;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2009 Fabien Potencier
* (c) 2010 Arnaud Le Blanc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a method template function.
*
* @package twig
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
*/
class Twig_Function_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->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2010 Fabien Potencier
* (c) 2010 Arnaud Le Blanc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a template function.
*
* @package twig
* @author Arnaud Le Blanc <arnaud.lb@gmail.com>
*/
interface Twig_FunctionInterface
{
public function compile();
public function needsEnvironment();
public function getSafe(Twig_Node $filterArgs);
}
+16 -15
View File
@@ -17,22 +17,23 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression
public function compile($compiler)
{
// functions must be prefixed with fn_
$this->getNode('name')->setAttribute('name', 'fn_'.$this->getNode('name')->getAttribute('name'));
$compiler
->raw('$this->callFunction($context, ')
->subcompile($this->getNode('name'))
->raw(', array(')
;
foreach ($this->getNode('arguments') as $node) {
$compiler
->subcompile($node)
->raw(', ')
;
$function = $compiler->getEnvironment()->getFunction($this->getNode('name')->getAttribute('name'));
if (!$function) {
throw new Twig_Error_Syntax(sprintf('The function "%s" does not exist', $this->getNode('name')->getAttribute('name')), $this->getLine());
}
$compiler->raw('))');
$compiler->raw($function->compile().($function->needsEnvironment() ? '($this->env, ' : '('));
$first = true;
foreach ($this->getNode('arguments') as $node) {
if (!$first) {
$compiler->raw(', ');
} else {
$first = false;
}
$compiler->subcompile($node);
}
$compiler->raw(')');
}
}
@@ -1,13 +0,0 @@
--TEST--
Twig supports calling variables
--TEMPLATE--
{{ lower('FOO') }}
{{ lower1('FOO') }}
--DATA--
return array(
'foo' => new Foo(),
'fn_lower' => new Twig_Function('foo', 'strToLower'),
'fn_lower1' => new Twig_Function(new Foo(), 'strToLower'))
--EXPECT--
foo
foo
-25
View File
@@ -1,25 +0,0 @@
--TEST--
global variables
--TEMPLATE--
{{ foo() }}
{{ bar() }}
{% include "included.twig" %}
{% from "included.twig" import foobar %}
{{ foobar() }}
--TEMPLATE(included.twig)--
{% macro foobar() %}
{{ foo() }}
{% endmacro %}
{{ foo() }}
--DATA--
$twig->addGlobal('fn_foo', new Twig_Function(new Foo(), 'getFoo'));
$twig->addGlobal('fn_bar', new Twig_Function('barObj', 'getFoo'));
return array('barObj' => new Foo());
--EXPECT--
foo
foo
foo
foo
+14
View File
@@ -0,0 +1,14 @@
--TEST--
global variables
--TEMPLATE--
{% include "included.twig" %}
{% from "included.twig" import foobar %}
{{ foobar() }}
--TEMPLATE(included.twig)--
{% macro foobar() %}
called foobar
{% endmacro %}
--DATA--
return array();
--EXPECT--
called foobar
@@ -10,7 +10,7 @@
--DATA--
return array('foo' => 'bar')
--EXPECT--
fn_range,fn_constant,fn_cycle,foo,_parent,
fn_range,fn_constant,fn_cycle,_parent,
fn_range,fn_constant,fn_cycle,foo,foo1,_parent,
fn_range,fn_constant,fn_cycle,foo1,_parent,
foo,_parent,
_parent,
foo,foo1,_parent,
foo1,_parent,
+13
View File
@@ -135,6 +135,14 @@ class TestExtension extends Twig_Extension
);
}
public function getFunctions()
{
return array(
'safe_br' => new Twig_Function_Method($this, 'br', array('is_safe' => array('html'))),
'unsafe_br' => new Twig_Function_Method($this, 'br'),
);
}
/**
* nl2br which also escapes, for testing escaper filters
*/
@@ -158,6 +166,11 @@ class TestExtension extends Twig_Extension
return strtoupper($value);
}
public function br()
{
return '<br />';
}
public function getName()
{
return 'test';