Skip to content

Commit

Permalink
[DI][Routing] recursive directory loading
Browse files Browse the repository at this point in the history
Issue #11045

For now, the Routing DirectoryLoader requires the type `directory`
to be specified so it does not conflict with `AnnotationDirectoryLoader`.
However, this could be refactored.
  • Loading branch information
lavoiesl authored and nicolas-grekas committed Jun 2, 2015
1 parent 78cf382 commit 73f0ee2
Show file tree
Hide file tree
Showing 16 changed files with 305 additions and 2 deletions.
Expand Up @@ -12,6 +12,7 @@
<parameter key="routing.loader.xml.class">Symfony\Component\Routing\Loader\XmlFileLoader</parameter>
<parameter key="routing.loader.yml.class">Symfony\Component\Routing\Loader\YamlFileLoader</parameter>
<parameter key="routing.loader.php.class">Symfony\Component\Routing\Loader\PhpFileLoader</parameter>
<parameter key="routing.loader.directory.class">Symfony\Component\Routing\Loader\DirectoryLoader</parameter>
<parameter key="router.options.generator_class">Symfony\Component\Routing\Generator\UrlGenerator</parameter>
<parameter key="router.options.generator_base_class">Symfony\Component\Routing\Generator\UrlGenerator</parameter>
<parameter key="router.options.generator_dumper_class">Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper</parameter>
Expand Down Expand Up @@ -45,6 +46,11 @@
<argument type="service" id="file_locator" />
</service>

<service id="routing.loader.directory" class="%routing.loader.directory.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
</service>

<service id="routing.loader" class="%routing.loader.class%">
<tag name="monolog.logger" channel="router" />
<argument type="service" id="controller_name_converter" />
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Expand Up @@ -18,7 +18,7 @@
"require": {
"php": ">=5.3.9",
"symfony/asset": "~2.7|~3.0.0",
"symfony/dependency-injection" : "~2.6,>=2.6.2",
"symfony/dependency-injection" : "~2.8",
"symfony/config" : "~2.4",
"symfony/event-dispatcher": "~2.5|~3.0.0",
"symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4|~3.0.0",
Expand Down
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Loader;

use Symfony\Component\Config\Resource\DirectoryResource;

/**
* DirectoryLoader is a recursive loader to go through directories
*
* @author Sebastien Lavoie <seb@wemakecustom.com>
*/
class DirectoryLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($file, $type = null)
{
$file = rtrim($file, '/');
$path = $this->locator->locate($file);
$this->container->addResource(new DirectoryResource($path));

foreach (scandir($path) as $dir) {
if ($dir[0] !== '.') {
if (is_dir($path.'/'.$dir)) {
$dir .= '/'; // append / to allow recursion
}

$this->setCurrentDir($path);

$this->import($dir, null, false, $path);
}
}
}

/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
if ('directory' === $type) {
return true;
}

if (null === $type) {
return preg_match('/\/$/', $resource) === 1;
}

return false;
}
}
@@ -0,0 +1,2 @@
imports:
- { resource: ../recurse/ }
@@ -0,0 +1,2 @@
[parameters]
ini = ini
@@ -0,0 +1,2 @@
parameters:
yaml: yaml
@@ -0,0 +1,3 @@
<?php

$container->setParameter('php', 'php');
@@ -0,0 +1,85 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Loader;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\FileLocator;

class DirectoryLoaderTest extends \PHPUnit_Framework_TestCase
{
private static $fixturesPath;

private $container;
private $loader;

public static function setUpBeforeClass()
{
self::$fixturesPath = realpath(__DIR__.'/../Fixtures/');
}

protected function setUp()
{
$locator = new FileLocator(self::$fixturesPath);
$this->container = new ContainerBuilder();
$this->loader = new DirectoryLoader($this->container, $locator);
$resolver = new LoaderResolver(array(
new PhpFileLoader($this->container, $locator),
new IniFileLoader($this->container, $locator),
new YamlFileLoader($this->container, $locator),
$this->loader,
));
$this->loader->setResolver($resolver);
}

public function testDirectoryCanBeLoadedRecursively()
{
$this->loader->load('directory/');
$this->assertEquals(array('ini' => 'ini', 'yaml' => 'yaml', 'php' => 'php'), $this->container->getParameterBag()->all(), '->load() takes a single directory');
}

public function testImports()
{
$this->loader->resolve('directory/import/import.yml')->load('directory/import/import.yml');
$this->assertEquals(array('ini' => 'ini', 'yaml' => 'yaml'), $this->container->getParameterBag()->all(), '->load() takes a single file that imports a directory');
}

/**
* @covers Symfony\Component\DependencyInjection\Loader\DirectoryLoader::__construct
* @covers Symfony\Component\DependencyInjection\Loader\DirectoryLoader::load
*
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The file "foo" does not exist (in:
*/
public function testExceptionIsRaisedWhenDirectoryDoesNotExist()
{
$this->loader->load('foo/');
}

/**
* @covers Symfony\Component\DependencyInjection\Loader\DirectoryLoader::supports
*/
public function testSupports()
{
$loader = new DirectoryLoader(new ContainerBuilder(), new FileLocator());

$this->assertTrue($loader->supports('directory/'), '->supports("directory/") returns true');
$this->assertTrue($loader->supports('directory/', 'directory'), '->supports("directory/", "directory") returns true');
$this->assertFalse($loader->supports('directory'), '->supports("directory") returns false');
$this->assertTrue($loader->supports('directory', 'directory'), '->supports("directory", "directory") returns true');
$this->assertFalse($loader->supports('directory', 'foo'), '->supports("directory, "foo") returns false');
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/HttpKernel/Kernel.php
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -721,6 +722,7 @@ protected function getContainerLoader(ContainerInterface $container)
new YamlFileLoader($container, $locator),
new IniFileLoader($container, $locator),
new PhpFileLoader($container, $locator),
new DirectoryLoader($container, $locator),
new ClosureLoader($container),
));

Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpKernel/composer.json
Expand Up @@ -34,7 +34,7 @@
"symfony/expression-language": "~2.4|~3.0.0",
"symfony/finder": "~2.0,>=2.0.5|~3.0.0",
"symfony/process": "~2.0,>=2.0.5|~3.0.0",
"symfony/routing": "~2.2|~3.0.0",
"symfony/routing": "~2.7|~3.0.0",
"symfony/stopwatch": "~2.3|~3.0.0",
"symfony/templating": "~2.2|~3.0.0",
"symfony/translation": "~2.0,>=2.0.5|~3.0.0",
Expand Down
58 changes: 58 additions & 0 deletions src/Symfony/Component/Routing/Loader/DirectoryLoader.php
@@ -0,0 +1,58 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Routing\Loader;

use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Config\Resource\DirectoryResource;

class DirectoryLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
public function load($file, $type = null)
{
$path = $this->locator->locate($file);

$collection = new RouteCollection();
$collection->addResource(new DirectoryResource($path));

foreach (scandir($path) as $dir) {
if ($dir[0] !== '.') {
$this->setCurrentDir($path);
$subPath = $path.'/'.$dir;
$subType = null;

if (is_dir($subPath)) {
$subPath .= '/';
$subType = 'directory';
}

$subCollection = $this->import($subPath, $subType, false, $path);
$collection->addCollection($subCollection);
}
}

return $collection;
}

/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
// only when type is forced to directory, not to conflict with AnnotationLoader

return 'directory' === $type;
}
}
@@ -0,0 +1,2 @@
route1:
path: /route/1
@@ -0,0 +1,2 @@
route2:
path: /route/2
@@ -0,0 +1,2 @@
route3:
path: /route/3
@@ -0,0 +1,3 @@
_directory:
resource: "../directory"
type: directory
74 changes: 74 additions & 0 deletions src/Symfony/Component/Routing/Tests/Loader/DirectoryLoaderTest.php
@@ -0,0 +1,74 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Routing\Tests\Loader;

use Symfony\Component\Routing\Loader\DirectoryLoader;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Routing\Loader\AnnotationFileLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\RouteCollection;

class DirectoryLoaderTest extends AbstractAnnotationLoaderTest
{
private $loader;
private $reader;

protected function setUp()
{
parent::setUp();

$locator = new FileLocator();
$this->reader = $this->getReader();
$this->loader = new DirectoryLoader($locator);
$resolver = new LoaderResolver(array(
new YamlFileLoader($locator),
new AnnotationFileLoader($locator, $this->getClassLoader($this->reader)),
$this->loader,
));
$this->loader->setResolver($resolver);
}

public function testLoadDirectory()
{
$collection = $this->loader->load(__DIR__.'/../Fixtures/directory', 'directory');
$this->verifyCollection($collection);
}

public function testImportDirectory()
{
$collection = $this->loader->load(__DIR__.'/../Fixtures/directory_import', 'directory');
$this->verifyCollection($collection);
}

private function verifyCollection(RouteCollection $collection)
{
$routes = $collection->all();

$this->assertCount(3, $routes, 'Three routes are loaded');
$this->assertContainsOnly('Symfony\Component\Routing\Route', $routes);

for ($i = 1; $i <= 3; $i++) {
$this->assertSame('/route/'.$i, $routes["route".$i]->getPath());
}
}

public function testSupports()
{
$fixturesDir = __DIR__.'/../Fixtures';

$this->assertFalse($this->loader->supports($fixturesDir), '->supports(*) returns false');

$this->assertTrue($this->loader->supports($fixturesDir, 'directory'), '->supports(*, "directory") returns true');
$this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports(*, "foo") returns false');
}
}

0 comments on commit 73f0ee2

Please sign in to comment.