Browse Source

Merge remote branch 'opensky/TwigExtension-configuration'

* opensky/TwigExtension-configuration:
  [TwigBundle] Refactored TwigExtension class and implemented configuration tree
Fabien Potencier 14 years ago
parent
commit
4833acf301

+ 122 - 0
src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php

@@ -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()
+        ;
+    }
+}

+ 43 - 75
src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php

@@ -11,113 +11,81 @@
 
 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');
 
-        $this->addClassesToCompile(array(
-            'Twig_Environment',
-            'Twig_ExtensionInterface',
-            'Twig_Extension',
-            'Twig_Extension_Core',
-            'Twig_Extension_Escaper',
-            'Twig_Extension_Optimizer',
-            'Twig_LoaderInterface',
-            'Twig_Markup',
-            'Twig_TemplateInterface',
-            'Twig_Template',
-        ));
+        $processor = new Processor();
+        $configuration = new Configuration();
 
-        foreach ($configs as $config) {
-            $this->doConfigLoad($config, $container);
-        }
-    }
+        $config = $processor->process($configuration->getConfigTree(), $configs);
 
-    /**
-     * 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]);
-            }
-        }
+        $container->setParameter('twig.form.resources', $config['form']['resources']);
 
-        // globals
-        $def = $container->getDefinition('twig');
-        $globals = $this->normalizeConfig($config, 'global');
-        if (isset($globals[0])) {
-            foreach ($globals as $global) {
+        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($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))));
+                    $def->addMethodCall('addGlobal', array($key, new Reference($global['id'])));
                 } else {
-                    $def->addMethodCall('addGlobal', array($key, $value));
+                    $def->addMethodCall('addGlobal', array($key, $global['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) {
+        if (!empty($config['extensions'])) {
+            foreach ($config['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 (!empty($config['cache_warmer'])) {
+            $container->getDefinition('templating.cache_warmer.templates_cache')->addTag('kernel.cache_warmer');
         }
 
-        if (isset($config['cache-warmer'])) {
-            $config['cache_warmer'] = $config['cache-warmer'];
-        }
+        unset(
+            $config['form'],
+            $config['globals'],
+            $config['extensions'],
+            $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', $config);
 
-        $container->setParameter('twig.options', array_replace($container->getParameter('twig.options'), $config));
+        $this->addClassesToCompile(array(
+            'Twig_Environment',
+            'Twig_ExtensionInterface',
+            'Twig_Extension',
+            'Twig_Extension_Core',
+            'Twig_Extension_Escaper',
+            'Twig_Extension_Optimizer',
+            'Twig_LoaderInterface',
+            'Twig_Markup',
+            'Twig_TemplateInterface',
+            'Twig_Template',
+        ));
     }
 
     /**

+ 13 - 7
src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd

@@ -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>

+ 0 - 8
src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml

@@ -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>

+ 25 - 0
src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php

@@ -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,
+));

+ 18 - 0
src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/full.xml

@@ -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>

+ 18 - 0
src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/full.yml

@@ -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

+ 105 - 51
src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php

@@ -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 = $this->createContainer();
+        $container->registerExtension(new TwigExtension());
+        $container->loadFromExtension('twig', array());
+        $this->compileContainer($container);
+
+        $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');
+
+        // Twig options
+        $options = $container->getParameter('twig.options');
+        $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');
+    }
+
+    /**
+     * @dataProvider getFormats
+     */
+    public function testLoadFullConfiguration($format)
     {
-        $container = new ContainerBuilder();
-        $loader = new TwigExtension();
+        $container = $this->createContainer();
+        $container->registerExtension(new TwigExtension());
+        $this->loadFromFile($container, 'full', $format);
+        $this->compileContainer($container);
+
+        $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');
+        }
 
-        $loader->load(array(array()), $container);
-        $this->assertEquals('Twig_Environment', $container->getParameter('twig.class'), '->load() loads the twig.xml file if not already loaded');
+        // 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');
 
-        $loader->load(array(array('charset' => 'ISO-8859-1')), $container);
+        // 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->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->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 getFormats()
+    {
+        return array(
+            array('php'),
+            array('yml'),
+            array('xml'),
+        );
+    }
+
+    private function createContainer()
+    {
+        $container = new ContainerBuilder(new ParameterBag(array(
+            'kernel.cache_dir' => __DIR__,
+            'kernel.charset'   => 'UTF-8',
+            'kernel.debug'     => false,
+        )));
+
+        return $container;
     }
 
-    public function testConfigGlobals()
+    private function compileContainer(ContainerBuilder $container)
     {
-        // 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]);
-
-        // 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]);
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
+        $container->compile();
     }
 
-    public function testConfigExtensions()
+    private function loadFromFile(ContainerBuilder $container, $file, $format)
     {
-        // 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()));
-
-        // 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()));
+        $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);
     }
 }