Skip to content

Commit

Permalink
[TwigBundle] Refactored TwigExtension class and implemented configura…
Browse files Browse the repository at this point in the history
…tion tree

Added config fixtures in each format to demonstrate the possible styles of all of the extension options. These should all be covered by the updated tests. Made XSD slightly more restrictive, with regards to the "type" attribute on globals. This is coupled with validation in the configuration class.
  • Loading branch information
jmikola committed Feb 18, 2011
1 parent 9b60262 commit f0d2ce7
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 141 deletions.
122 changes: 122 additions & 0 deletions 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()
;
}
}
118 changes: 43 additions & 75 deletions src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
Expand Up @@ -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',
));
}

/**
Expand Down
20 changes: 13 additions & 7 deletions src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd
Expand Up @@ -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">
Expand All @@ -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>

Expand All @@ -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>
8 changes: 0 additions & 8 deletions src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml
Expand Up @@ -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>
Expand Down
@@ -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,
));
@@ -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>

0 comments on commit f0d2ce7

Please sign in to comment.