Skip to content

Commit f0d2ce7

Browse files
committed
[TwigBundle] Refactored TwigExtension class and implemented configuration 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.
1 parent 9b60262 commit f0d2ce7

File tree

8 files changed

+344
-141
lines changed

8 files changed

+344
-141
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
4+
5+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
6+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
7+
8+
/**
9+
* TwigExtension configuration structure.
10+
*
11+
* @author Jeremy Mikola <jmikola@gmail.com>
12+
*/
13+
class Configuration
14+
{
15+
/**
16+
* Generates the configuration tree.
17+
*
18+
* @return \Symfony\Component\Config\Definition\NodeInterface
19+
*/
20+
public function getConfigTree()
21+
{
22+
$treeBuilder = new TreeBuilder();
23+
$rootNode = $treeBuilder->root('twig', 'array');
24+
25+
$rootNode
26+
->scalarNode('cache_warmer')->end()
27+
;
28+
29+
$this->addExtensionsSection($rootNode);
30+
$this->addFormSection($rootNode);
31+
$this->addGlobalsSection($rootNode);
32+
$this->addTwigOptions($rootNode);
33+
34+
return $treeBuilder->buildTree();
35+
}
36+
37+
private function addExtensionsSection(NodeBuilder $rootNode)
38+
{
39+
$rootNode
40+
->fixXmlConfig('extension')
41+
->arrayNode('extensions')
42+
->prototype('scalar')
43+
->beforeNormalization()
44+
->ifTrue(function($v) { return is_array($v) && isset($v['id']); })
45+
->then(function($v){ return $v['id']; })
46+
->end()
47+
->end()
48+
->end()
49+
;
50+
}
51+
52+
private function addFormSection(NodeBuilder $rootNode)
53+
{
54+
$rootNode
55+
->arrayNode('form')
56+
->addDefaultsIfNotSet()
57+
->fixXmlConfig('resource')
58+
->arrayNode('resources')
59+
->addDefaultsIfNotSet()
60+
->defaultValue(array('TwigBundle::form.html.twig'))
61+
->validate()
62+
->always()
63+
->then(function($v){
64+
return array_merge(array('TwigBundle::form.html.twig'), $v);
65+
})
66+
->end()
67+
->prototype('scalar')->end()
68+
->end()
69+
->end()
70+
;
71+
}
72+
73+
private function addGlobalsSection(NodeBuilder $rootNode)
74+
{
75+
$rootNode
76+
->fixXmlConfig('global')
77+
->arrayNode('globals')
78+
->useAttributeAsKey('key')
79+
->prototype('array')
80+
->beforeNormalization()
81+
->ifTrue(function($v){ return is_scalar($v); })
82+
->then(function($v){
83+
return ('@' === substr($v, 0, 1))
84+
? array('id' => substr($v, 1), 'type' => 'service')
85+
: array('value' => $v);
86+
})
87+
->end()
88+
->scalarNode('id')->end()
89+
->scalarNode('type')
90+
->validate()
91+
->ifNotInArray(array('service'))
92+
->thenInvalid('The %s type is not supported')
93+
->end()
94+
->end()
95+
->scalarNode('value')->end()
96+
->end()
97+
->end()
98+
;
99+
}
100+
101+
private function addTwigOptions(NodeBuilder $rootNode)
102+
{
103+
$rootNode
104+
->scalarNode('autoescape')->end()
105+
->scalarNode('base_template_class')->end()
106+
->scalarNode('cache')
107+
->addDefaultsIfNotSet()
108+
->defaultValue('%kernel.cache_dir%/twig')
109+
->end()
110+
->scalarNode('charset')
111+
->addDefaultsIfNotSet()
112+
->defaultValue('%kernel.charset%')
113+
->end()
114+
->scalarNode('debug')
115+
->addDefaultsIfNotSet()
116+
->defaultValue('%kernel.debug%')
117+
->end()
118+
->scalarNode('strict_variables')->end()
119+
->scalarNode('auto_reload')->end()
120+
;
121+
}
122+
}

src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php

Lines changed: 43 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -11,113 +11,81 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
1313

14-
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
15-
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
14+
use Symfony\Component\Config\FileLocator;
15+
use Symfony\Component\Config\Definition\Processor;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Reference;
18-
use Symfony\Component\Config\FileLocator;
18+
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
19+
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
1920

2021
/**
2122
* TwigExtension.
2223
*
2324
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
25+
* @author Jeremy Mikola <jmikola@gmail.com>
2426
*/
2527
class TwigExtension extends Extension
2628
{
29+
/**
30+
* Responds to the twig configuration parameter.
31+
*
32+
* @param array $configs
33+
* @param ContainerBuilder $container
34+
*/
2735
public function load(array $configs, ContainerBuilder $container)
2836
{
2937
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
3038
$loader->load('twig.xml');
3139

32-
$this->addClassesToCompile(array(
33-
'Twig_Environment',
34-
'Twig_ExtensionInterface',
35-
'Twig_Extension',
36-
'Twig_Extension_Core',
37-
'Twig_Extension_Escaper',
38-
'Twig_Extension_Optimizer',
39-
'Twig_LoaderInterface',
40-
'Twig_Markup',
41-
'Twig_TemplateInterface',
42-
'Twig_Template',
43-
));
40+
$processor = new Processor();
41+
$configuration = new Configuration();
4442

45-
foreach ($configs as $config) {
46-
$this->doConfigLoad($config, $container);
47-
}
48-
}
43+
$config = $processor->process($configuration->getConfigTree(), $configs);
4944

50-
/**
51-
* Loads the Twig configuration.
52-
*
53-
* @param array $config An array of configuration settings
54-
* @param ContainerBuilder $container A ContainerBuilder instance
55-
*/
56-
protected function doConfigLoad(array $config, ContainerBuilder $container)
57-
{
58-
// form resources
59-
foreach (array('resources', 'resource') as $key) {
60-
if (isset($config['form'][$key])) {
61-
$resources = (array) $config['form'][$key];
62-
$container->setParameter('twig.form.resources', array_merge($container->getParameter('twig.form.resources'), $resources));
63-
unset($config['form'][$key]);
64-
}
65-
}
45+
$container->setParameter('twig.form.resources', $config['form']['resources']);
6646

67-
// globals
68-
$def = $container->getDefinition('twig');
69-
$globals = $this->normalizeConfig($config, 'global');
70-
if (isset($globals[0])) {
71-
foreach ($globals as $global) {
47+
if (!empty($config['globals'])) {
48+
$def = $container->getDefinition('twig');
49+
foreach ($config['globals'] as $key => $global) {
7250
if (isset($global['type']) && 'service' === $global['type']) {
73-
$def->addMethodCall('addGlobal', array($global['key'], new Reference($global['id'])));
74-
} elseif (isset($global['value'])) {
75-
$def->addMethodCall('addGlobal', array($global['key'], $global['value']));
76-
} else {
77-
throw new \InvalidArgumentException(sprintf('Unable to understand global configuration (%s).', var_export($global, true)));
78-
}
79-
}
80-
} else {
81-
foreach ($globals as $key => $value) {
82-
if (is_string($value) && '@' === substr($value, 0, 1)) {
83-
$def->addMethodCall('addGlobal', array($key, new Reference(substr($value, 1))));
51+
$def->addMethodCall('addGlobal', array($key, new Reference($global['id'])));
8452
} else {
85-
$def->addMethodCall('addGlobal', array($key, $value));
53+
$def->addMethodCall('addGlobal', array($key, $global['value']));
8654
}
8755
}
8856
}
89-
unset($config['globals'], $config['global']);
9057

91-
// extensions
92-
$extensions = $this->normalizeConfig($config, 'extension');
93-
if (isset($extensions[0]) && is_array($extensions[0])) {
94-
foreach ($extensions as $extension) {
95-
$container->getDefinition($extension['id'])->addTag('twig.extension');
96-
}
97-
} else {
98-
foreach ($extensions as $id) {
58+
if (!empty($config['extensions'])) {
59+
foreach ($config['extensions'] as $id) {
9960
$container->getDefinition($id)->addTag('twig.extension');
10061
}
10162
}
102-
unset($config['extensions'], $config['extension']);
10363

104-
// convert - to _
105-
foreach ($config as $key => $value) {
106-
if (false !== strpos($key, '-')) {
107-
unset($config[$key]);
108-
$config[str_replace('-', '_', $key)] = $value;
109-
}
64+
if (!empty($config['cache_warmer'])) {
65+
$container->getDefinition('templating.cache_warmer.templates_cache')->addTag('kernel.cache_warmer');
11066
}
11167

112-
if (isset($config['cache-warmer'])) {
113-
$config['cache_warmer'] = $config['cache-warmer'];
114-
}
68+
unset(
69+
$config['form'],
70+
$config['globals'],
71+
$config['extensions'],
72+
$config['cache_warmer']
73+
);
11574

116-
if (isset($config['cache_warmer']) && $config['cache_warmer']) {
117-
$container->getDefinition('templating.cache_warmer.templates_cache')->addTag('kernel.cache_warmer');
118-
}
75+
$container->setParameter('twig.options', $config);
11976

120-
$container->setParameter('twig.options', array_replace($container->getParameter('twig.options'), $config));
77+
$this->addClassesToCompile(array(
78+
'Twig_Environment',
79+
'Twig_ExtensionInterface',
80+
'Twig_Extension',
81+
'Twig_Extension_Core',
82+
'Twig_Extension_Escaper',
83+
'Twig_Extension_Optimizer',
84+
'Twig_LoaderInterface',
85+
'Twig_Markup',
86+
'Twig_TemplateInterface',
87+
'Twig_Template',
88+
));
12189
}
12290

12391
/**

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
<xsd:element name="extension" type="extension" minOccurs="0" maxOccurs="unbounded" />
1515
</xsd:sequence>
1616

17-
<xsd:attribute name="charset" type="xsd:string" />
18-
<xsd:attribute name="debug" type="xsd:string" />
19-
<xsd:attribute name="cache" type="xsd:string" />
20-
<xsd:attribute name="strict-variables" type="xsd:string" />
21-
<xsd:attribute name="auto-reload" type="xsd:string" />
17+
<xsd:attribute name="auto-reload" type="xsd:boolean" />
18+
<xsd:attribute name="autoescape" type="xsd:boolean" />
2219
<xsd:attribute name="base-template-class" type="xsd:string" />
23-
<xsd:attribute name="autoescape" type="xsd:string" />
20+
<xsd:attribute name="cache" type="xsd:string" />
2421
<xsd:attribute name="cache-warmer" type="cache_warmer" />
22+
<xsd:attribute name="charset" type="xsd:string" />
23+
<xsd:attribute name="debug" type="xsd:boolean" />
24+
<xsd:attribute name="strict-variables" type="xsd:boolean" />
2525
</xsd:complexType>
2626

2727
<xsd:complexType name="form">
@@ -32,7 +32,7 @@
3232

3333
<xsd:complexType name="global" mixed="true">
3434
<xsd:attribute name="key" type="xsd:string" use="required" />
35-
<xsd:attribute name="type" type="xsd:string" />
35+
<xsd:attribute name="type" type="global_type" />
3636
<xsd:attribute name="id" type="xsd:string" />
3737
</xsd:complexType>
3838

@@ -47,4 +47,10 @@
4747
<xsd:enumeration value="full" />
4848
</xsd:restriction>
4949
</xsd:simpleType>
50+
51+
<xsd:simpleType name="global_type">
52+
<xsd:restriction base="xsd:string">
53+
<xsd:enumeration value="service" />
54+
</xsd:restriction>
55+
</xsd:simpleType>
5056
</xsd:schema>

src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,8 @@
66

77
<parameters>
88
<parameter key="twig.class">Twig_Environment</parameter>
9-
<parameter key="twig.options" type="collection">
10-
<parameter key="charset">%kernel.charset%</parameter>
11-
<parameter key="debug">%kernel.debug%</parameter>
12-
<parameter key="cache">%kernel.cache_dir%/twig</parameter>
13-
</parameter>
149
<parameter key="twig.loader.class">Symfony\Bundle\TwigBundle\Loader\FilesystemLoader</parameter>
1510
<parameter key="twig.globals.class">Symfony\Bundle\TwigBundle\GlobalVariables</parameter>
16-
<parameter key="twig.form.resources" type="collection">
17-
<parameter>TwigBundle::form.html.twig</parameter>
18-
</parameter>
1911
<parameter key="templating.engine.twig.class">Symfony\Bundle\TwigBundle\TwigEngine</parameter>
2012
<parameter key="templating.cache_warmer.templates_cache.class">Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer</parameter>
2113
</parameters>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
$container->loadFromExtension('twig', array(
4+
'form' => array(
5+
'resources' => array(
6+
'MyBundle::form.html.twig',
7+
)
8+
),
9+
'extensions' => array(
10+
'twig.extension.debug',
11+
'twig.extension.text',
12+
),
13+
'globals' => array(
14+
'foo' => '@bar',
15+
'pi' => 3.14,
16+
),
17+
'auto_reload' => true,
18+
'autoescape' => true,
19+
'base_template_class' => 'stdClass',
20+
'cache' => '/tmp',
21+
'cache_warmer' => true,
22+
'charset' => 'ISO-8859-1',
23+
'debug' => true,
24+
'strict_variables' => true,
25+
));
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://www.symfony-project.org/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:twig="http://www.symfony-project.org/schema/dic/twig"
6+
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd
7+
http://www.symfony-project.org/schema/dic/twig http://www.symfony-project.org/schema/dic/twig/twig-1.0.xsd">
8+
9+
<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">
10+
<twig:form>
11+
<twig:resource>MyBundle::form.html.twig</twig:resource>
12+
</twig:form>
13+
<twig:global key="foo" id="bar" type="service" />
14+
<twig:global key="pi">3.14</twig:global>
15+
<twig:extension id="twig.extension.debug" />
16+
<twig:extension id="twig.extension.text" />
17+
</twig:config>
18+
</container>

0 commit comments

Comments
 (0)