Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[TwigBundle] added support for Twig namespaced paths (Twig 1.10)

In a template, you can now use native Twig template names, instead of
the Symfony ones:

Before (still works):

    {% extends "AcmeDemoBundle::layout.html.twig" %}
    {% include "AcmeDemoBundle:Foo:bar.html.twig" %}

After:

    {% extends "@AcmeDemo/layout.html.twig" %}
    {% include "@AcmeDemo/Foo/bar.html.twig" %}

Using native template names is also faster.

The only drawback is that the new notation looks similar to the way we
locate resources in Symfony, which would be
@AcmeDemoBundle/Resources/views/Foo/bar.html.twig. We could have used
the same notation, but it is rather verbose (and by the way, using this
notation did not work anyway in templates).
  • Loading branch information...
commit 5c809d8ffb5e66f532cca34597c3f513ff017691 1 parent 0bfa86c
@fabpot fabpot authored
View
6 src/Symfony/Bundle/TwigBundle/CHANGELOG.md
@@ -1,6 +1,12 @@
CHANGELOG
=========
+2.2.0
+-----
+
+ * added automatic registration of namespaced paths for registered bundles
+ * added support for namespaced paths
+
2.1.0
-----
View
23 src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
@@ -126,6 +126,29 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode)
->scalarNode('auto_reload')->end()
->scalarNode('optimizations')->end()
->arrayNode('paths')
+ ->beforeNormalization()
+ ->always()
+ ->then(function ($paths) {
+ $normalized = array();
+ foreach ($paths as $path => $namespace) {
+ if (is_array($namespace)) {
+ // xml
+ $path = $namespace['value'];
+ $namespace = $namespace['namespace'];
+ }
+
+ // path within the default namespace
+ if (ctype_digit((string) $path)) {
+ $path = $namespace;
+ $namespace = null;
+ }
+
+ $normalized[$path] = $namespace;
+ }
+
+ return $normalized;
+ })
+ ->end()
->prototype('variable')->end()
->end()
->end()
View
36 src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
@@ -60,12 +60,33 @@ public function load(array $configs, ContainerBuilder $container)
$reflClass = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension');
$container->getDefinition('twig.loader')->addMethodCall('addPath', array(dirname(dirname($reflClass->getFileName())).'/Resources/views/Form'));
- if (!empty($config['paths'])) {
- foreach ($config['paths'] as $path) {
- $container->getDefinition('twig.loader')->addMethodCall('addPath', array($path));
+ $twigLoaderDefinition = $container->getDefinition('twig.loader');
+
+ // register user-configured paths
+ foreach ($config['paths'] as $path => $namespace) {
+ if (!$namespace) {
+ $twigLoaderDefinition->addMethodCall('addPath', array($path));
+ } else {
+ $twigLoaderDefinition->addMethodCall('addPath', array($path, $namespace));
}
}
+ // register bundles as Twig namespaces
+ foreach ($container->getParameter('kernel.bundles') as $bundle => $class) {
+ if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$bundle.'/views')) {
+ $this->addTwigPath($twigLoaderDefinition, $dir, $bundle);
+ }
+
+ $reflection = new \ReflectionClass($class);
+ if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/views')) {
+ $this->addTwigPath($twigLoaderDefinition, $dir, $bundle);
+ }
+ }
+
+ if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/views')) {
+ $twigLoaderDefinition->addMethodCall('addPath', array($dir));
+ }
+
if (!empty($config['globals'])) {
$def = $container->getDefinition('twig');
foreach ($config['globals'] as $key => $global) {
@@ -108,6 +129,15 @@ public function load(array $configs, ContainerBuilder $container)
));
}
+ private function addTwigPath($twigLoaderDefinition, $dir, $bundle)
+ {
+ $name = $bundle;
+ if ('Bundle' === substr($name, -6)) {
+ $name = substr($name, 0, -6);
+ }
+ $twigLoaderDefinition->addMethodCall('addPath', array($dir, $name));
+ }
+
/**
* Returns the base path for the XSD files.
*
View
21 src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php
@@ -64,16 +64,19 @@ protected function findTemplate($template)
$file = null;
$previous = null;
try {
- $template = $this->parser->parse($template);
- try {
- $file = $this->locator->locate($template);
- } catch (\InvalidArgumentException $e) {
- $previous = $e;
- }
- } catch (\Exception $e) {
+ $file = parent::findTemplate($template);
+ } catch (\Twig_Error_Loader $e) {
+ $previous = $e;
+
+ // for BC
try {
- $file = parent::findTemplate($template);
- } catch (\Twig_Error_Loader $e) {
+ $template = $this->parser->parse($template);
+ try {
+ $file = $this->locator->locate($template);
+ } catch (\InvalidArgumentException $e) {
+ $previous = $e;
+ }
+ } catch (\Exception $e) {
$previous = $e;
}
}
View
6 src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd
@@ -11,7 +11,7 @@
<xsd:sequence>
<xsd:element name="form" type="form" minOccurs="0" maxOccurs="1" />
<xsd:element name="global" type="global" minOccurs="0" maxOccurs="unbounded" />
- <xsd:element name="path" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
+ <xsd:element name="path" type="path" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="auto-reload" type="xsd:string" />
@@ -30,6 +30,10 @@
</xsd:choice>
</xsd:complexType>
+ <xsd:complexType name="path" mixed="true">
+ <xsd:attribute name="namespace" type="xsd:string" />
+ </xsd:complexType>
+
<xsd:complexType name="global" mixed="true">
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="type" type="global_type" />
View
1  ...Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/Resources/TwigBundle/views/layout.html.twig
@@ -0,0 +1 @@
+This is a layout
View
1  src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/Resources/views/layout.html.twig
@@ -0,0 +1 @@
+This is a layout
View
7 src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php
@@ -18,5 +18,10 @@
'charset' => 'ISO-8859-1',
'debug' => true,
'strict_variables' => true,
- 'paths' => array('path1', 'path2'),
+ 'paths' => array(
+ 'path1',
+ 'path2',
+ 'namespaced_path1' => 'namespace',
+ 'namespaced_path2' => 'namespace',
+ ),
));
View
2  src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/full.xml
@@ -14,5 +14,7 @@
<twig:global key="pi">3.14</twig:global>
<twig:path>path1</twig:path>
<twig:path>path2</twig:path>
+ <twig:path namespace="namespace">namespaced_path1</twig:path>
+ <twig:path namespace="namespace">namespaced_path2</twig:path>
</twig:config>
</container>
View
6 src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/full.yml
@@ -13,4 +13,8 @@ twig:
charset: ISO-8859-1
debug: true
strict_variables: true
- paths: [path1, path2]
+ paths:
+ path1: ''
+ path2: ''
+ namespaced_path1: namespace
+ namespaced_path2: namespace
View
33 src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php
@@ -108,6 +108,37 @@ public function testGlobalsWithDifferentTypesAndValues()
}
}
+ /**
+ * @dataProvider getFormats
+ */
+ public function testTwigLoaderPaths($format)
+ {
+ $container = $this->createContainer();
+ $container->registerExtension(new TwigExtension());
+ $this->loadFromFile($container, 'full', $format);
+ $this->compileContainer($container);
+
+ $def = $container->getDefinition('twig.loader');
+ $paths = array();
+ foreach ($def->getMethodCalls() as $call) {
+ if ('addPath' === $call[0]) {
+ if (false === strpos($call[1][0], 'Form')) {
+ $paths[] = $call[1];
+ }
+ }
+ }
+
+ $this->assertEquals(array(
+ array('path1'),
+ array('path2'),
+ array('namespaced_path1', 'namespace'),
+ array('namespaced_path2', 'namespace'),
+ array(__DIR__.'/Fixtures/Resources/TwigBundle/views', 'Twig'),
+ array(realpath(__DIR__.'/../../Resources/views'), 'Twig'),
+ array(__DIR__.'/Fixtures/Resources/views'),
+ ), $paths);
+ }
+
public function getFormats()
{
return array(
@@ -121,8 +152,10 @@ private function createContainer()
{
$container = new ContainerBuilder(new ParameterBag(array(
'kernel.cache_dir' => __DIR__,
+ 'kernel.root_dir' => __DIR__.'/Fixtures',
'kernel.charset' => 'UTF-8',
'kernel.debug' => false,
+ 'kernel.bundles' => array('TwigBundle' => 'Symfony\\Bundle\\TwigBundle\\TwigBundle'),
)));
return $container;
View
88 src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php
@@ -16,59 +16,73 @@
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\Templating\TemplateNameParserInterface;
-use InvalidArgumentException;
class FilesystemLoaderTest extends TestCase
{
- /** @var FileLocatorInterface */
- private $locator;
- /** @var TemplateNameParserInterface */
- private $parser;
- /** @var FilesystemLoader */
- private $loader;
-
- protected function setUp()
+ public function testGetSource()
{
- parent::setUp();
-
- $this->locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface');
- $this->parser = $this->getMock('Symfony\Component\Templating\TemplateNameParserInterface');
- $this->loader = new FilesystemLoader($this->locator, $this->parser);
-
- $this->parser->expects($this->once())
- ->method('parse')
- ->with('name.format.engine')
- ->will($this->returnValue(new TemplateReference('', '', 'name', 'format', 'engine')))
+ $parser = $this->getMock('Symfony\Component\Templating\TemplateNameParserInterface');
+ $locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface');
+ $locator
+ ->expects($this->once())
+ ->method('locate')
+ ->will($this->returnValue(__DIR__.'/../DependencyInjection/Fixtures/Resources/views/layout.html.twig'))
;
- }
+ $loader = new FilesystemLoader($locator, $parser);
+ $loader->addPath(__DIR__.'/../DependencyInjection/Fixtures/Resources/views', 'namespace');
- protected function tearDown()
- {
- parent::tearDown();
+ // Twig-style
+ $this->assertEquals("This is a layout\n", $loader->getSource('@namespace/layout.html.twig'));
- $this->locator = null;
- $this->parser = null;
- $this->loader = null;
+ // Symfony-style
+ $this->assertEquals("This is a layout\n", $loader->getSource('TwigBundle::layout.html.twig'));
}
+ /**
+ * @expectedException Twig_Error_Loader
+ */
public function testTwigErrorIfLocatorThrowsInvalid()
{
- $this->setExpectedException('Twig_Error_Loader');
- $invalidException = new InvalidArgumentException('Unable to find template "NonExistent".');
- $this->locator->expects($this->once())
- ->method('locate')
- ->will($this->throwException($invalidException));
+ $parser = $this->getMock('Symfony\Component\Templating\TemplateNameParserInterface');
+ $parser
+ ->expects($this->once())
+ ->method('parse')
+ ->with('name.format.engine')
+ ->will($this->returnValue(new TemplateReference('', '', 'name', 'format', 'engine')))
+ ;
- $this->loader->getCacheKey('name.format.engine');
+ $locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface');
+ $locator
+ ->expects($this->once())
+ ->method('locate')
+ ->will($this->throwException(new \InvalidArgumentException('Unable to find template "NonExistent".')))
+ ;
+
+ $loader = new FilesystemLoader($locator, $parser);
+ $loader->getCacheKey('name.format.engine');
}
+ /**
+ * @expectedException Twig_Error_Loader
+ */
public function testTwigErrorIfLocatorReturnsFalse()
{
- $this->setExpectedException('Twig_Error_Loader');
- $this->locator->expects($this->once())
- ->method('locate')
- ->will($this->returnValue(false));
+ $parser = $this->getMock('Symfony\Component\Templating\TemplateNameParserInterface');
+ $parser
+ ->expects($this->once())
+ ->method('parse')
+ ->with('name.format.engine')
+ ->will($this->returnValue(new TemplateReference('', '', 'name', 'format', 'engine')))
+ ;
+
+ $locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface');
+ $locator
+ ->expects($this->once())
+ ->method('locate')
+ ->will($this->returnValue(false))
+ ;
- $this->loader->getCacheKey('name.format.engine');
+ $loader = new FilesystemLoader($locator, $parser);
+ $loader->getCacheKey('name.format.engine');
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.