diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f86af8..3ddc5d80 100644 --- a/CHANGELOG.md +++ b/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 ----- diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 7936d219..3b3c8ed6 100644 --- a/DependencyInjection/Configuration.php +++ b/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() diff --git a/DependencyInjection/TwigExtension.php b/DependencyInjection/TwigExtension.php index d65dc9fb..e13fe086 100644 --- a/DependencyInjection/TwigExtension.php +++ b/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. * diff --git a/Loader/FilesystemLoader.php b/Loader/FilesystemLoader.php index a79d35c8..64d5616c 100644 --- a/Loader/FilesystemLoader.php +++ b/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; } } diff --git a/Resources/config/schema/twig-1.0.xsd b/Resources/config/schema/twig-1.0.xsd index 2a72ef6f..3706f148 100644 --- a/Resources/config/schema/twig-1.0.xsd +++ b/Resources/config/schema/twig-1.0.xsd @@ -11,7 +11,7 @@ - + @@ -30,6 +30,10 @@ + + + + diff --git a/Tests/DependencyInjection/Fixtures/Resources/TwigBundle/views/layout.html.twig b/Tests/DependencyInjection/Fixtures/Resources/TwigBundle/views/layout.html.twig new file mode 100644 index 00000000..bb07ecfe --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/Resources/TwigBundle/views/layout.html.twig @@ -0,0 +1 @@ +This is a layout diff --git a/Tests/DependencyInjection/Fixtures/Resources/views/layout.html.twig b/Tests/DependencyInjection/Fixtures/Resources/views/layout.html.twig new file mode 100644 index 00000000..bb07ecfe --- /dev/null +++ b/Tests/DependencyInjection/Fixtures/Resources/views/layout.html.twig @@ -0,0 +1 @@ +This is a layout diff --git a/Tests/DependencyInjection/Fixtures/php/full.php b/Tests/DependencyInjection/Fixtures/php/full.php index ba3150ea..bad71a38 100644 --- a/Tests/DependencyInjection/Fixtures/php/full.php +++ b/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', + ), )); diff --git a/Tests/DependencyInjection/Fixtures/xml/full.xml b/Tests/DependencyInjection/Fixtures/xml/full.xml index 63cbe3e6..0d3c053c 100644 --- a/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -14,5 +14,7 @@ 3.14 path1 path2 + namespaced_path1 + namespaced_path2 diff --git a/Tests/DependencyInjection/Fixtures/yml/full.yml b/Tests/DependencyInjection/Fixtures/yml/full.yml index 8378e331..afc14615 100644 --- a/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/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 diff --git a/Tests/DependencyInjection/TwigExtensionTest.php b/Tests/DependencyInjection/TwigExtensionTest.php index eccc1f2e..01a5e54a 100644 --- a/Tests/DependencyInjection/TwigExtensionTest.php +++ b/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; diff --git a/Tests/Loader/FilesystemLoaderTest.php b/Tests/Loader/FilesystemLoaderTest.php index 0d29d30b..8bb1c169 100644 --- a/Tests/Loader/FilesystemLoaderTest.php +++ b/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'); } }