Issue #1964156 by Cottser, geoffreyr, joelpittet: Contrib cannot define additional Twig extensions.

8.0.x
Alex Pott 2013-07-02 13:45:03 +01:00
parent b48f8fda69
commit 436fe57760
11 changed files with 326 additions and 0 deletions

View File

@ -22,6 +22,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass; use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass; use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass; use Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
@ -74,6 +75,8 @@ class CoreServiceProvider implements ServiceProviderInterface {
$container->addCompilerPass(new ModifyServiceDefinitionsPass()); $container->addCompilerPass(new ModifyServiceDefinitionsPass());
// Add the compiler pass that will process tagged authentication services. // Add the compiler pass that will process tagged authentication services.
$container->addCompilerPass(new RegisterAuthenticationPass()); $container->addCompilerPass(new RegisterAuthenticationPass());
// Register Twig extensions.
$container->addCompilerPass(new RegisterTwigExtensionsPass());
} }
/** /**

View File

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Register additional Twig extensions to the Twig service container.
*/
class RegisterTwigExtensionsPass implements CompilerPassInterface {
/**
* Adds services tagged 'twig.extension' to the Twig service container.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('twig')) {
return;
}
$definition = $container->getDefinition('twig');
foreach ($container->findTaggedServiceIds('twig.extension') as $id => $attributes) {
// We must assume that the class value has been correcly filled,
// even if the service is created by a factory.
$class = $container->getDefinition($id)->getClass();
$refClass = new \ReflectionClass($class);
$interface = 'Twig_ExtensionInterface';
if (!$refClass->implementsInterface($interface)) {
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
$definition->addMethodCall('addExtension', array(new Reference($id)));
}
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\TwigExtensionTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests Twig extensions.
*/
class TwigExtensionTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test', 'twig_extension_test');
public static function getInfo() {
return array(
'name' => 'Twig Extension',
'description' => 'Tests Twig extensions.',
'group' => 'Theme',
);
}
function setUp() {
parent::setUp();
theme_enable(array('test_theme'));
}
/**
* Tests that the provided Twig extension loads the service appropriately.
*/
function testTwigExtensionLoaded() {
$twigService = \Drupal::service('twig');
$ext = $twigService->getExtension('twig_extension_test.test_extension');
$this->assertEqual(get_class($ext), 'Drupal\twig_extension_test\TwigExtension\TestExtension', 'TestExtension loaded successfully.');
}
/**
* Tests that the Twig extension's filter produces expected output.
*/
function testTwigExtensionFilter() {
config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('twig-extension-test/filter');
$this->assertText('Every plant is not a mineral.', 'Success: String filtered.');
}
/**
* Tests that the Twig extension's function produces expected output.
*/
function testTwigExtensionFunction() {
config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('twig-extension-test/function');
$this->assertText('THE QUICK BROWN BOX JUMPS OVER THE LAZY DOG 123.', 'Success: Text converted to uppercase.');
$this->assertText('the quick brown box jumps over the lazy dog 123.', 'Success: Text converted to lowercase.');
$this->assertNoText('The Quick Brown Fox Jumps Over The Lazy Dog 123.', 'Success: No text left behind.');
}
}

View File

@ -0,0 +1,106 @@
<?php
/**
* @file
* Contains \Drupal\twig_extension_test\TwigExtension\TestExtension.
*/
namespace Drupal\twig_extension_test\TwigExtension;
use Drupal\Core\Template\TwigExtension;
use Drupal\Core\Template\TwigReferenceFunction;
/**
* A test Twig extension that adds a custom function and a custom filter.
*/
class TestExtension extends TwigExtension {
/**
* Generates a list of all Twig functions that this extension defines.
*
* @return array
* A key/value array that defines custom Twig functions. The key denotes the
* function name used in the tag, e.g.:
* @code
* {{ testfunc() }}
* @endcode
*
* The value is a standard PHP callback that defines what the function does.
*/
public function getFunctions() {
return array(
'testfunc' => new \Twig_Function_Function(array('Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFunction')),
);
}
/**
* Generates a list of all Twig filters that this extension defines.
*
* @return array
* A key/value array that defines custom Twig filters. The key denotes the
* filter name used in the tag, e.g.:
* @code
* {{ foo|testfilter }}
* @endcode
*
* The value is a standard PHP callback that defines what the filter does.
*/
public function getFilters() {
return array(
'testfilter' => new \Twig_Filter_Function(array('Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFilter')),
);
}
/**
* Gets a unique identifier for this Twig extension.
*
* @return string
* A unique identifier for this Twig extension.
*/
public function getName() {
return 'twig_extension_test.test_extension';
}
/**
* Outputs either an uppercase or lowercase test phrase.
*
* The function generates either an uppercase or lowercase version of the
* phrase "The quick brown fox jumps over the lazy dog 123.", depending on
* whether or not the $upperCase parameter evaluates to TRUE. If $upperCase
* evaluates to TRUE, the result will be uppercase, and if it evaluates to
* FALSE, the result will be lowercase.
*
* @param bool $upperCase
* (optional) Whether the result is uppercase (true) or lowercase (false).
*
* @return string
* The generated string.
*
* @see \Drupal\system\Tests\Theme\TwigExtensionTest::testTwigExtensionFunction()
*/
public static function testFunction($upperCase = FALSE) {
$string = "The quick brown box jumps over the lazy dog 123.";
if ($upperCase == TRUE) {
return strtoupper($string);
}
else {
return strtolower($string);
}
}
/**
* Replaces all instances of "animal" in a string with "plant".
*
* @param string $string
* The string to be filtered.
*
* @return string
* The filtered string.
*
* @see \Drupal\system\Tests\Theme\TwigExtensionTest::testTwigExtensionFilter()
*/
public static function testFilter($string) {
return str_replace(array('animal'), array('plant'), $string);
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\twig_extension_test\TwigExtensionTestController.
*/
namespace Drupal\twig_extension_test;
use Drupal\Core\Controller\ControllerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Controller routines for Twig extension test routes.
*/
class TwigExtensionTestController implements ControllerInterface {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static();
}
/**
* Menu callback for testing Twig filters in a Twig template.
*/
public function testFilterRender() {
return array(
'#theme' => 'twig_extension_test_filter',
'#message' => 'Every animal is not a mineral.',
);
}
/**
* Menu callback for testing Twig functions in a Twig template.
*/
public function testFunctionRender() {
return array('#theme' => 'twig_extension_test_function');
}
}

View File

@ -0,0 +1,3 @@
<div class="testfilter">
{{ message|testfilter }}
</div>

View File

@ -0,0 +1,7 @@
<div class="testfunction">
{{ testfunc(1) }}
</div>
<div class="testfunction">
{{ testfunc(0) }}
</div>

View File

@ -0,0 +1,7 @@
name: 'Twig Extension Test'
type: module
description: 'Support module for testing Twig extensions.'
package: Testing
version: VERSION
core: 8.x
hidden: true

View File

@ -0,0 +1,23 @@
<?php
/**
* @file
* Helper module for Twig extension tests.
*/
/**
* Implements hook_theme().
*/
function twig_extension_test_theme($existing, $type, $theme, $path) {
return array(
'twig_extension_test_filter' => array(
'variables' => array('message' => NULL),
'template' => 'twig_extension_test.filter',
),
'twig_extension_test_function' => array(
'render element' => 'element',
'template' => 'twig_extension_test.function',
),
);
}

View File

@ -0,0 +1,12 @@
twig_extension_test.filter:
pattern: '/twig-extension-test/filter'
defaults:
_content: '\Drupal\twig_extension_test\TwigExtensionTestController::testFilterRender'
requirements:
_permission: 'access content'
twig_extension_test.function:
pattern: '/twig-extension-test/function'
defaults:
_content: '\Drupal\twig_extension_test\TwigExtensionTestController::testFunctionRender'
requirements:
_permission: 'access content'

View File

@ -0,0 +1,5 @@
services:
twig_extension_test.twig.test_extension:
class: Drupal\twig_extension_test\TwigExtension\TestExtension
tags:
- { name: twig.extension }