diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000..07f2a12046 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -0,0 +1,122 @@ + + */ +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() + ; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 29bb03e0e5..d530d1b2f6 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -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 + * @author Jeremy Mikola */ 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)); } /** diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd b/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd index 932983c7df..fd98eb90d7 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd @@ -14,14 +14,14 @@ - - - - - + + - + + + + @@ -32,7 +32,7 @@ - + @@ -47,4 +47,10 @@ + + + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 5067d1896d..251e31487b 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -6,16 +6,8 @@ Twig_Environment - - %kernel.charset% - %kernel.debug% - %kernel.cache_dir%/twig - Symfony\Bundle\TwigBundle\Loader\FilesystemLoader Symfony\Bundle\TwigBundle\GlobalVariables - - TwigBundle::form.html.twig - Symfony\Bundle\TwigBundle\TwigEngine Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php new file mode 100644 index 0000000000..840e393aa1 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -0,0 +1,25 @@ +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, +)); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/full.xml new file mode 100644 index 0000000000..07677caca0 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -0,0 +1,18 @@ + + + + + + + MyBundle::form.html.twig + + + 3.14 + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/full.yml new file mode 100644 index 0000000000..55378d6acc --- /dev/null +++ b/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 diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 9e1394f438..9abe53ebfa 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/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 = 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); } }