Merge remote branch 'opensky/TwigExtension-configuration'

* opensky/TwigExtension-configuration:
  [TwigBundle] Refactored TwigExtension class and implemented configuration tree
This commit is contained in:
Fabien Potencier 2011-02-19 15:35:09 +01:00
commit 4833acf301
8 changed files with 347 additions and 144 deletions

View File

@ -0,0 +1,122 @@
<?php
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
/**
* TwigExtension configuration structure.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class Configuration
{
/**
* Generates the configuration tree.
*
* @return \Symfony\Component\Config\Definition\NodeInterface
*/
public function getConfigTree()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('twig', 'array');
$rootNode
->scalarNode('cache_warmer')->end()
;
$this->addExtensionsSection($rootNode);
$this->addFormSection($rootNode);
$this->addGlobalsSection($rootNode);
$this->addTwigOptions($rootNode);
return $treeBuilder->buildTree();
}
private function addExtensionsSection(NodeBuilder $rootNode)
{
$rootNode
->fixXmlConfig('extension')
->arrayNode('extensions')
->prototype('scalar')
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && isset($v['id']); })
->then(function($v){ return $v['id']; })
->end()
->end()
->end()
;
}
private function addFormSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('form')
->addDefaultsIfNotSet()
->fixXmlConfig('resource')
->arrayNode('resources')
->addDefaultsIfNotSet()
->defaultValue(array('TwigBundle::form.html.twig'))
->validate()
->always()
->then(function($v){
return array_merge(array('TwigBundle::form.html.twig'), $v);
})
->end()
->prototype('scalar')->end()
->end()
->end()
;
}
private function addGlobalsSection(NodeBuilder $rootNode)
{
$rootNode
->fixXmlConfig('global')
->arrayNode('globals')
->useAttributeAsKey('key')
->prototype('array')
->beforeNormalization()
->ifTrue(function($v){ return is_scalar($v); })
->then(function($v){
return ('@' === substr($v, 0, 1))
? array('id' => substr($v, 1), 'type' => 'service')
: array('value' => $v);
})
->end()
->scalarNode('id')->end()
->scalarNode('type')
->validate()
->ifNotInArray(array('service'))
->thenInvalid('The %s type is not supported')
->end()
->end()
->scalarNode('value')->end()
->end()
->end()
;
}
private function addTwigOptions(NodeBuilder $rootNode)
{
$rootNode
->scalarNode('autoescape')->end()
->scalarNode('base_template_class')->end()
->scalarNode('cache')
->addDefaultsIfNotSet()
->defaultValue('%kernel.cache_dir%/twig')
->end()
->scalarNode('charset')
->addDefaultsIfNotSet()
->defaultValue('%kernel.charset%')
->end()
->scalarNode('debug')
->addDefaultsIfNotSet()
->defaultValue('%kernel.debug%')
->end()
->scalarNode('strict_variables')->end()
->scalarNode('auto_reload')->end()
;
}
}

View File

@ -11,24 +11,69 @@
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* TwigExtension.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class TwigExtension extends Extension
{
/**
* Responds to the twig configuration parameter.
*
* @param array $configs
* @param ContainerBuilder $container
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('twig.xml');
$processor = new Processor();
$configuration = new Configuration();
$config = $processor->process($configuration->getConfigTree(), $configs);
$container->setParameter('twig.form.resources', $config['form']['resources']);
if (!empty($config['globals'])) {
$def = $container->getDefinition('twig');
foreach ($config['globals'] as $key => $global) {
if (isset($global['type']) && 'service' === $global['type']) {
$def->addMethodCall('addGlobal', array($key, new Reference($global['id'])));
} else {
$def->addMethodCall('addGlobal', array($key, $global['value']));
}
}
}
if (!empty($config['extensions'])) {
foreach ($config['extensions'] as $id) {
$container->getDefinition($id)->addTag('twig.extension');
}
}
if (!empty($config['cache_warmer'])) {
$container->getDefinition('templating.cache_warmer.templates_cache')->addTag('kernel.cache_warmer');
}
unset(
$config['form'],
$config['globals'],
$config['extensions'],
$config['cache_warmer']
);
$container->setParameter('twig.options', $config);
$this->addClassesToCompile(array(
'Twig_Environment',
'Twig_ExtensionInterface',
@ -41,83 +86,6 @@ class TwigExtension extends Extension
'Twig_TemplateInterface',
'Twig_Template',
));
foreach ($configs as $config) {
$this->doConfigLoad($config, $container);
}
}
/**
* Loads the Twig configuration.
*
* @param array $config An array of configuration settings
* @param ContainerBuilder $container A ContainerBuilder instance
*/
protected function doConfigLoad(array $config, ContainerBuilder $container)
{
// form resources
foreach (array('resources', 'resource') as $key) {
if (isset($config['form'][$key])) {
$resources = (array) $config['form'][$key];
$container->setParameter('twig.form.resources', array_merge($container->getParameter('twig.form.resources'), $resources));
unset($config['form'][$key]);
}
}
// globals
$def = $container->getDefinition('twig');
$globals = $this->normalizeConfig($config, 'global');
if (isset($globals[0])) {
foreach ($globals as $global) {
if (isset($global['type']) && 'service' === $global['type']) {
$def->addMethodCall('addGlobal', array($global['key'], new Reference($global['id'])));
} elseif (isset($global['value'])) {
$def->addMethodCall('addGlobal', array($global['key'], $global['value']));
} else {
throw new \InvalidArgumentException(sprintf('Unable to understand global configuration (%s).', var_export($global, true)));
}
}
} else {
foreach ($globals as $key => $value) {
if (is_string($value) && '@' === substr($value, 0, 1)) {
$def->addMethodCall('addGlobal', array($key, new Reference(substr($value, 1))));
} else {
$def->addMethodCall('addGlobal', array($key, $value));
}
}
}
unset($config['globals'], $config['global']);
// extensions
$extensions = $this->normalizeConfig($config, 'extension');
if (isset($extensions[0]) && is_array($extensions[0])) {
foreach ($extensions as $extension) {
$container->getDefinition($extension['id'])->addTag('twig.extension');
}
} else {
foreach ($extensions as $id) {
$container->getDefinition($id)->addTag('twig.extension');
}
}
unset($config['extensions'], $config['extension']);
// convert - to _
foreach ($config as $key => $value) {
if (false !== strpos($key, '-')) {
unset($config[$key]);
$config[str_replace('-', '_', $key)] = $value;
}
}
if (isset($config['cache-warmer'])) {
$config['cache_warmer'] = $config['cache-warmer'];
}
if (isset($config['cache_warmer']) && $config['cache_warmer']) {
$container->getDefinition('templating.cache_warmer.templates_cache')->addTag('kernel.cache_warmer');
}
$container->setParameter('twig.options', array_replace($container->getParameter('twig.options'), $config));
}
/**

View File

@ -14,14 +14,14 @@
<xsd:element name="extension" type="extension" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="charset" type="xsd:string" />
<xsd:attribute name="debug" type="xsd:string" />
<xsd:attribute name="cache" type="xsd:string" />
<xsd:attribute name="strict-variables" type="xsd:string" />
<xsd:attribute name="auto-reload" type="xsd:string" />
<xsd:attribute name="auto-reload" type="xsd:boolean" />
<xsd:attribute name="autoescape" type="xsd:boolean" />
<xsd:attribute name="base-template-class" type="xsd:string" />
<xsd:attribute name="autoescape" type="xsd:string" />
<xsd:attribute name="cache" type="xsd:string" />
<xsd:attribute name="cache-warmer" type="cache_warmer" />
<xsd:attribute name="charset" type="xsd:string" />
<xsd:attribute name="debug" type="xsd:boolean" />
<xsd:attribute name="strict-variables" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="form">
@ -32,7 +32,7 @@
<xsd:complexType name="global" mixed="true">
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="type" type="global_type" />
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
@ -47,4 +47,10 @@
<xsd:enumeration value="full" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="global_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="service" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@ -6,16 +6,8 @@
<parameters>
<parameter key="twig.class">Twig_Environment</parameter>
<parameter key="twig.options" type="collection">
<parameter key="charset">%kernel.charset%</parameter>
<parameter key="debug">%kernel.debug%</parameter>
<parameter key="cache">%kernel.cache_dir%/twig</parameter>
</parameter>
<parameter key="twig.loader.class">Symfony\Bundle\TwigBundle\Loader\FilesystemLoader</parameter>
<parameter key="twig.globals.class">Symfony\Bundle\TwigBundle\GlobalVariables</parameter>
<parameter key="twig.form.resources" type="collection">
<parameter>TwigBundle::form.html.twig</parameter>
</parameter>
<parameter key="templating.engine.twig.class">Symfony\Bundle\TwigBundle\TwigEngine</parameter>
<parameter key="templating.cache_warmer.templates_cache.class">Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer</parameter>
</parameters>

View File

@ -0,0 +1,25 @@
<?php
$container->loadFromExtension('twig', array(
'form' => array(
'resources' => array(
'MyBundle::form.html.twig',
)
),
'extensions' => array(
'twig.extension.debug',
'twig.extension.text',
),
'globals' => array(
'foo' => '@bar',
'pi' => 3.14,
),
'auto_reload' => true,
'autoescape' => true,
'base_template_class' => 'stdClass',
'cache' => '/tmp',
'cache_warmer' => true,
'charset' => 'ISO-8859-1',
'debug' => true,
'strict_variables' => true,
));

View File

@ -0,0 +1,18 @@
<?xml version="1.0" ?>
<container xmlns="http://www.symfony-project.org/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://www.symfony-project.org/schema/dic/twig"
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd
http://www.symfony-project.org/schema/dic/twig http://www.symfony-project.org/schema/dic/twig/twig-1.0.xsd">
<twig:config auto-reload="true" autoescape="true" base-template-class="stdClass" cache="/tmp" cache-warmer="true" charset="ISO-8859-1" debug="true" strict-variables="true">
<twig:form>
<twig:resource>MyBundle::form.html.twig</twig:resource>
</twig:form>
<twig:global key="foo" id="bar" type="service" />
<twig:global key="pi">3.14</twig:global>
<twig:extension id="twig.extension.debug" />
<twig:extension id="twig.extension.text" />
</twig:config>
</container>

View File

@ -0,0 +1,18 @@
twig:
form:
resources:
- MyBundle::form.html.twig
extensions:
- twig.extension.debug
- twig.extension.text
globals:
foo: @bar
pi: 3.14
auto_reload: true
autoescape: true
base_template_class: stdClass
cache: /tmp
cache_warmer: true
charset: ISO-8859-1
debug: true
strict_variables: true

View File

@ -11,72 +11,126 @@
namespace Symfony\Bundle\TwigBundle\Tests\DependencyInjection;
use Symfony\Bundle\TwigBundle\Tests\TestCase;
use Symfony\Bundle\TwigBundle\DependencyInjection\TwigExtension;
use Symfony\Bundle\TwigBundle\Tests\TestCase;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
class TwigExtensionTest extends TestCase
{
public function testLoad()
/**
* @dataProvider getFormats
*/
public function testLoadEmptyConfiguration($format)
{
$container = new ContainerBuilder();
$loader = new TwigExtension();
$container = $this->createContainer();
$container->registerExtension(new TwigExtension());
$container->loadFromExtension('twig', array());
$this->compileContainer($container);
$loader->load(array(array()), $container);
$this->assertEquals('Twig_Environment', $container->getParameter('twig.class'), '->load() loads the twig.xml file if not already loaded');
$this->assertEquals('Twig_Environment', $container->getParameter('twig.class'), '->load() loads the twig.xml file');
$this->assertFalse($container->getDefinition('templating.cache_warmer.templates_cache')->hasTag('kernel.cache_warmer'), '->load() does not enable cache warming by default');
$this->assertContains('TwigBundle::form.html.twig', $container->getParameter('twig.form.resources'), '->load() includes default template for form resources');
$loader->load(array(array('charset' => 'ISO-8859-1')), $container);
// Twig options
$options = $container->getParameter('twig.options');
$this->assertEquals('ISO-8859-1', $options['charset'], '->load() overrides existing configuration options');
$this->assertEquals('%kernel.debug%', $options['debug'], '->load() merges the new values with the old ones');
$this->assertEquals(__DIR__.'/twig', $options['cache'], '->load() sets default value for cache option');
$this->assertEquals('UTF-8', $options['charset'], '->load() sets default value for charset option');
$this->assertEquals(false, $options['debug'], '->load() sets default value for debug option');
}
public function testConfigGlobals()
/**
* @dataProvider getFormats
*/
public function testLoadFullConfiguration($format)
{
// XML
$container = new ContainerBuilder();
$loader = new TwigExtension();
$loader->load(array(array('global' => array(
array('key' => 'foo', 'type' => 'service', 'id' => 'bar'),
array('key' => 'pi', 'value' => 3.14),
))), $container);
$config = $container->getDefinition('twig')->getMethodCalls();
$this->assertEquals('foo', $config[0][1][0]);
$this->assertEquals(new Reference('bar'), $config[0][1][1]);
$this->assertEquals('pi', $config[1][1][0]);
$this->assertEquals(3.14, $config[1][1][1]);
$container = $this->createContainer();
$container->registerExtension(new TwigExtension());
$this->loadFromFile($container, 'full', $format);
$this->compileContainer($container);
// YAML, PHP
$container = new ContainerBuilder();
$loader = new TwigExtension();
$loader->load(array(array('globals' => array(
'foo' => '@bar',
'pi' => 3.14,
))), $container);
$config = $container->getDefinition('twig')->getMethodCalls();
$this->assertEquals('foo', $config[0][1][0]);
$this->assertEquals(new Reference('bar'), $config[0][1][1]);
$this->assertEquals('pi', $config[1][1][0]);
$this->assertEquals(3.14, $config[1][1][1]);
$this->assertEquals('Twig_Environment', $container->getParameter('twig.class'), '->load() loads the twig.xml file');
$this->assertTrue($container->getDefinition('templating.cache_warmer.templates_cache')->hasTag('kernel.cache_warmer'), '->load() enables cache warming');
// Extensions
foreach (array('twig.extension.debug', 'twig.extension.text') as $id) {
$config = $container->getDefinition($id);
$this->assertEquals(array('twig.extension'), array_keys($config->getTags()), '->load() adds tags to extension definitions');
}
// Form resources
$resources = $container->getParameter('twig.form.resources');
$this->assertContains('TwigBundle::form.html.twig', $resources, '->load() includes default template for form resources');
$this->assertContains('MyBundle::form.html.twig', $resources, '->load() merges new templates into form resources');
// Globals
$calls = $container->getDefinition('twig')->getMethodCalls();
$this->assertEquals('foo', $calls[0][1][0], '->load() registers services as Twig globals');
$this->assertEquals(new Reference('bar'), $calls[0][1][1], '->load() registers services as Twig globals');
$this->assertEquals('pi', $calls[1][1][0], '->load() registers variables as Twig globals');
$this->assertEquals(3.14, $calls[1][1][1], '->load() registers variables as Twig globals');
// Twig options
$options = $container->getParameter('twig.options');
$this->assertTrue($options['auto_reload'], '->load() sets the auto_reload option');
$this->assertTrue($options['autoescape'], '->load() sets the autoescape option');
$this->assertEquals('stdClass', $options['base_template_class'], '->load() sets the base_template_class option');
$this->assertEquals('/tmp', $options['cache'], '->load() sets the cache option');
$this->assertEquals('ISO-8859-1', $options['charset'], '->load() sets the charset option');
$this->assertTrue($options['debug'], '->load() sets the debug option');
$this->assertTrue($options['strict_variables'], '->load() sets the strict_variables option');
}
public function testConfigExtensions()
public function getFormats()
{
// XML
$container = new ContainerBuilder();
$container->register('foo', 'stdClass');
$loader = new TwigExtension();
$loader->load(array(array('extensions' => array(array('id' => 'foo')))), $container);
$config = $container->getDefinition('foo');
$this->assertEquals(array('twig.extension'), array_keys($config->getTags()));
return array(
array('php'),
array('yml'),
array('xml'),
);
}
// YAML, PHP
$container = new ContainerBuilder();
$container->register('foo', 'stdClass');
$loader = new TwigExtension();
$loader->load(array(array('extensions' => array('foo'))), $container);
$config = $container->getDefinition('foo');
$this->assertEquals(array('twig.extension'), array_keys($config->getTags()));
private function createContainer()
{
$container = new ContainerBuilder(new ParameterBag(array(
'kernel.cache_dir' => __DIR__,
'kernel.charset' => 'UTF-8',
'kernel.debug' => false,
)));
return $container;
}
private function compileContainer(ContainerBuilder $container)
{
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->compile();
}
private function loadFromFile(ContainerBuilder $container, $file, $format)
{
$locator = new FileLocator(__DIR__.'/Fixtures/'.$format);
switch ($format) {
case 'php':
$loader = new PhpFileLoader($container, $locator);
break;
case 'xml':
$loader = new XmlFileLoader($container, $locator);
break;
case 'yml':
$loader = new YamlFileLoader($container, $locator);
break;
default:
throw new \InvalidArgumentException('Unsupported format: '.$format);
}
$loader->load($file.'.'.$format);
}
}