mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 19:06:40 +00:00
allowed filters/functions/tests implementation to use a different class than the extension they belong to
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
* 1.26.0 (2016-XX-XX)
|
||||
|
||||
* allowed filters/functions/tests implementation to use a different class than the extension they belong to
|
||||
* deprecated Twig_ExtensionInterface::getName()
|
||||
|
||||
* 1.25.0 (2016-09-21)
|
||||
|
||||
+103
-6
@@ -136,7 +136,13 @@ Creating a filter is as simple as associating a name with a PHP callable::
|
||||
// or a simple PHP function
|
||||
$filter = new Twig_SimpleFilter('rot13', 'str_rot13');
|
||||
|
||||
// or a class static method
|
||||
$filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
|
||||
$filter = new Twig_SimpleFilter('rot13', 'SomeClass::rot13Filter');
|
||||
|
||||
// or a class method
|
||||
$filter = new Twig_SimpleFilter('rot13', array($this, 'rot13Filter'));
|
||||
// the one below needs a runtime implementation (see below for more information)
|
||||
$filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
|
||||
|
||||
The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
|
||||
@@ -525,10 +531,6 @@ reusable class like adding support for internationalization. An extension can
|
||||
define tags, filters, tests, operators, global variables, functions, and node
|
||||
visitors.
|
||||
|
||||
Creating an extension also makes for a better separation of code that is
|
||||
executed at compilation time and code needed at runtime. As such, it makes
|
||||
your code faster.
|
||||
|
||||
Most of the time, it is useful to create a single extension for your project,
|
||||
to host all the specific tags and filters you want to add to Twig.
|
||||
|
||||
@@ -646,7 +648,7 @@ main ``Environment`` object::
|
||||
|
||||
.. tip::
|
||||
|
||||
The bundled extensions are great examples of how extensions work.
|
||||
The Twig core extensions are great examples of how extensions work.
|
||||
|
||||
Globals
|
||||
~~~~~~~
|
||||
@@ -765,6 +767,101 @@ The ``getTests()`` method lets you add new test functions::
|
||||
// ...
|
||||
}
|
||||
|
||||
Definition vs Runtime
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Twig filters, functions, and tests runtime implementations can be defined as
|
||||
any valid PHP callable:
|
||||
|
||||
* **functions/static methods**: Simple to implement and fast (used by all Twig
|
||||
core extensions); but it is hard for the runtime to depend on external
|
||||
objects;
|
||||
|
||||
* **closures**: Simple to implement;
|
||||
|
||||
* **object methods**: More flexible and required if your runtime code depends
|
||||
on external objects.
|
||||
|
||||
The simplest way to use methods is to define them on the extension itself::
|
||||
|
||||
class Project_Twig_Extension extends Twig_Extension
|
||||
{
|
||||
private $rot13Provider;
|
||||
|
||||
public function __construct($rot13Provider)
|
||||
{
|
||||
$this->rot13Provider = $rot13Provider;
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
{
|
||||
return array(
|
||||
new Twig_SimpleFunction('rot13', array($this, 'rot13')),
|
||||
);
|
||||
}
|
||||
|
||||
public function rot13($value)
|
||||
{
|
||||
return $rot13Provider->rot13($value);
|
||||
}
|
||||
}
|
||||
|
||||
This is very convenient but not recommended as it makes template compilation
|
||||
depend on runtime dependencies even if they are not needed (think for instance
|
||||
as a dependency that connects to a database engine).
|
||||
|
||||
As of Twig 1.26, you can easily decouple the extension definitions from their
|
||||
runtime implementations by registering a ``Twig_RuntimeLoaderInterface``
|
||||
instance on the environment that knows how to instantiate such runtime classes
|
||||
(runtime classes must be autoload-able)::
|
||||
|
||||
class RuntimeLoader implements Twig_RuntimeLoaderInterface
|
||||
{
|
||||
public function load($class)
|
||||
{
|
||||
// implement the logic to create an instance of $class
|
||||
// and inject its dependencies
|
||||
// most of the time, it means using your dependency injection container
|
||||
if ('Project_Twig_RuntimeExtension' === $class) {
|
||||
return new $class(new Rot13Provider());
|
||||
} else {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$twig->addRuntimeLoader(new RuntimeLoader());
|
||||
|
||||
It is now possible to move the runtime logic to a new
|
||||
``Project_Twig_RuntimeExtension`` class and use it directly in the extension::
|
||||
|
||||
class Project_Twig_RuntimeExtension extends Twig_Extension
|
||||
{
|
||||
private $rot13Provider;
|
||||
|
||||
public function __construct($rot13Provider)
|
||||
{
|
||||
$this->rot13Provider = $rot13Provider;
|
||||
}
|
||||
|
||||
public function rot13($value)
|
||||
{
|
||||
return $rot13Provider->rot13($value);
|
||||
}
|
||||
}
|
||||
|
||||
class Project_Twig_Extension extends Twig_Extension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return array(
|
||||
new Twig_SimpleFunction('rot13', array('Project_Twig_RuntimeExtension', 'rot13')),
|
||||
// or
|
||||
new Twig_SimpleFunction('rot13', 'Project_Twig_RuntimeExtension::rot13'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Overloading
|
||||
-----------
|
||||
|
||||
@@ -792,7 +889,7 @@ possible** (order matters)::
|
||||
|
||||
Here, we have overloaded the built-in ``date`` filter with a custom one.
|
||||
|
||||
If you do the same on the Twig_Environment itself, beware that it takes
|
||||
If you do the same on the ``Twig_Environment`` itself, beware that it takes
|
||||
precedence over any other registered extensions::
|
||||
|
||||
$twig = new Twig_Environment($loader);
|
||||
|
||||
@@ -49,7 +49,8 @@ class Twig_Environment
|
||||
private $bcWriteCacheFile = false;
|
||||
private $bcGetCacheFilename = false;
|
||||
private $lastModifiedExtension = 0;
|
||||
private $legacyExtensionNames = array();
|
||||
private $runtimeLoaders = array();
|
||||
private $runtimes = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -786,6 +787,14 @@ class Twig_Environment
|
||||
return isset($this->extensions[ltrim($class, '\\')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a runtime loader.
|
||||
*/
|
||||
public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader)
|
||||
{
|
||||
$this->runtimeLoaders[] = $loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an extension by class name.
|
||||
*
|
||||
@@ -809,6 +818,28 @@ class Twig_Environment
|
||||
return $this->extensions[$class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the runtime implementation of a Twig element (filter/function/test).
|
||||
*
|
||||
* @param string $class A runtime class name
|
||||
*
|
||||
* @return object The runtime implementation
|
||||
*/
|
||||
public function getRuntime($class)
|
||||
{
|
||||
if (isset($this->runtimes[$class])) {
|
||||
return $this->runtimes[$class];
|
||||
}
|
||||
|
||||
foreach ($this->runtimeLoaders as $loader) {
|
||||
if (null !== $runtime = $loader->load($class)) {
|
||||
return $this->runtimes[$class] = $runtime;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Exception(sprintf('Unable to load the "%s" runtime.', $class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an extension.
|
||||
*
|
||||
|
||||
@@ -10,18 +10,29 @@
|
||||
*/
|
||||
abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
|
||||
{
|
||||
private $reflector;
|
||||
|
||||
protected function compileCallable(Twig_Compiler $compiler)
|
||||
{
|
||||
$closingParenthesis = false;
|
||||
if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) {
|
||||
if (is_string($callable)) {
|
||||
if (is_string($callable) && false === strpos($callable, '::')) {
|
||||
$compiler->raw($callable);
|
||||
} elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) {
|
||||
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', get_class($callable[0]), $callable[1]));
|
||||
} else {
|
||||
$type = ucfirst($this->getAttribute('type'));
|
||||
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name')));
|
||||
$closingParenthesis = true;
|
||||
list($r, $callable) = $this->reflectCallable($callable);
|
||||
if ($r instanceof ReflectionMethod && is_string($callable[0])) {
|
||||
if ($r->isStatic()) {
|
||||
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
|
||||
} else {
|
||||
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
|
||||
}
|
||||
} elseif ($r instanceof ReflectionMethod && $callable[0] instanceof Twig_ExtensionInterface) {
|
||||
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', get_class($callable[0]), $callable[1]));
|
||||
} else {
|
||||
$type = ucfirst($this->getAttribute('type'));
|
||||
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name')));
|
||||
$closingParenthesis = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$compiler->raw($this->getAttribute('thing')->compile());
|
||||
@@ -121,7 +132,6 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
|
||||
throw new LogicException($message);
|
||||
}
|
||||
|
||||
// manage named arguments
|
||||
$callableParameters = $this->getCallableParameters($callable, $isVariadic);
|
||||
$arguments = array();
|
||||
$names = array();
|
||||
@@ -208,16 +218,7 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
|
||||
|
||||
private function getCallableParameters($callable, $isVariadic)
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
$r = new ReflectionMethod($callable[0], $callable[1]);
|
||||
} elseif (is_object($callable) && !$callable instanceof Closure) {
|
||||
$r = new ReflectionObject($callable);
|
||||
$r = $r->getMethod('__invoke');
|
||||
} elseif (is_string($callable) && false !== strpos($callable, '::')) {
|
||||
$r = new ReflectionMethod($callable);
|
||||
} else {
|
||||
$r = new ReflectionFunction($callable);
|
||||
}
|
||||
list($r, $_) = $this->reflectCallable($callable);
|
||||
|
||||
$parameters = $r->getParameters();
|
||||
if ($this->hasNode('node')) {
|
||||
@@ -250,4 +251,26 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
private function reflectCallable($callable)
|
||||
{
|
||||
if (null !== $this->reflector) {
|
||||
return $this->reflector;
|
||||
}
|
||||
|
||||
if (is_array($callable)) {
|
||||
$r = new ReflectionMethod($callable[0], $callable[1]);
|
||||
} elseif (is_object($callable) && !$callable instanceof Closure) {
|
||||
$r = new ReflectionObject($callable);
|
||||
$r = $r->getMethod('__invoke');
|
||||
$callable = array($callable, '__invoke');
|
||||
} elseif (is_string($callable) && false !== $pos = strpos($callable, '::')) {
|
||||
$r = new ReflectionMethod($callable);
|
||||
$callable = array(substr($callable, 0, $pos), substr($callable, $pos + 2));
|
||||
} else {
|
||||
$r = new ReflectionFunction($callable);
|
||||
}
|
||||
|
||||
return $this->reflector = array($r, $callable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates runtime implementations for Twig elements (filters/functions/tests).
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface Twig_RuntimeLoaderInterface
|
||||
{
|
||||
/**
|
||||
* Creates the runtime implementation of a Twig element (filter/function/test).
|
||||
*
|
||||
* @param string $class A runtime class
|
||||
*
|
||||
* @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class
|
||||
*/
|
||||
public function load($class);
|
||||
}
|
||||
@@ -396,6 +396,32 @@ EOF
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
public function testAddRuntimeLoader()
|
||||
{
|
||||
$runtimeLoader = $this->getMockBuilder('Twig_RuntimeLoaderInterface')->getMock();
|
||||
$runtimeLoader->expects($this->any())->method('load')->will($this->returnValue(new Twig_Tests_EnvironmentTest_Runtime()));
|
||||
|
||||
$loader = new Twig_Loader_Array(array(
|
||||
'func_array' => '{{ from_runtime_array("foo") }}',
|
||||
'func_array_default' => '{{ from_runtime_array() }}',
|
||||
'func_array_named_args' => '{{ from_runtime_array(name="foo") }}',
|
||||
'func_string' => '{{ from_runtime_string("foo") }}',
|
||||
'func_string_default' => '{{ from_runtime_string() }}',
|
||||
'func_string_named_args' => '{{ from_runtime_string(name="foo") }}',
|
||||
));
|
||||
|
||||
$twig = new Twig_Environment($loader);
|
||||
$twig->addExtension(new Twig_Tests_EnvironmentTest_ExtensionWithoutRuntime());
|
||||
$twig->addRuntimeLoader($runtimeLoader);
|
||||
|
||||
$this->assertEquals('foo', $twig->render('func_array'));
|
||||
$this->assertEquals('bar', $twig->render('func_array_default'));
|
||||
$this->assertEquals('foo', $twig->render('func_array_named_args'));
|
||||
$this->assertEquals('foo', $twig->render('func_string'));
|
||||
$this->assertEquals('bar', $twig->render('func_string_default'));
|
||||
$this->assertEquals('foo', $twig->render('func_string_named_args'));
|
||||
}
|
||||
|
||||
protected function getMockLoader($templateName, $templateContent)
|
||||
{
|
||||
$loader = $this->getMockBuilder('Twig_LoaderInterface')->getMock();
|
||||
@@ -526,3 +552,27 @@ class Twig_Tests_EnvironmentTest_ExtensionWithoutDeprecationInitRuntime extends
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class Twig_Tests_EnvironmentTest_ExtensionWithoutRuntime extends Twig_Extension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return array(
|
||||
new Twig_SimpleFunction('from_runtime_array', array('Twig_Tests_EnvironmentTest_Runtime', 'fromRuntime')),
|
||||
new Twig_SimpleFunction('from_runtime_string', 'Twig_Tests_EnvironmentTest_Runtime::fromRuntime'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'from_runtime';
|
||||
}
|
||||
}
|
||||
|
||||
class Twig_Tests_EnvironmentTest_Runtime
|
||||
{
|
||||
public function fromRuntime($name = 'bar')
|
||||
{
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user