Skip to content

[DependencyInjection] [Routing] [Config] Recursive directory loading #11059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
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
8 changes: 8 additions & 0 deletions src/Symfony/Component/Config/Loader/FileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public function setCurrentDir($dir)
$this->currentDir = $dir;
}

/**
* @return string
*/
public function getCurrentDir()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be protected because it is only used in a child class.

{
return $this->currentDir;
}

/**
* Returns the file locator used by this loader.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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\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
{
/**
* @param mixed $file The resource

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be @param string ?

* @param string $type The resource type

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be @param string|null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would tend to agree with you, but I simply copied the other loaders: src/Symfony/Component/DependencyInjection/Loader

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the phpdoc is not good, wait for an official reviewer before making changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR should stay as is for now. Another PR could fix the phpdoc of all the loaders at the same time. If the doc gets fixed before my PR goes through, I will change it accordingly.

*/
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);
}
}
}

/**
* Returns true if this class supports the given resource.
*
* @param mixed $resource A resource
* @param string $type The resource type

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here string|null

*
* @return bool true if this class supports the given resource, false otherwise
*/
public function supports($resource, $type = null)
{
return 'directory' === $type || (!$type && preg_match('/\/$/', $resource) === 1); // ends with a slash
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[parameters]
ini = ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
parameters:
yaml: yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

$container->setParameter('php', 'php');
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?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
{
protected static $fixturesPath;

protected $container;
protected $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);
}

/**
* @covers Symfony\Component\DependencyInjection\Loader\DirectoryLoader::__construct
* @covers Symfony\Component\DependencyInjection\Loader\DirectoryLoader::load
*/
public function testDirectoryCanBeLoadedRecursively()
{
$this->loader->load('directory/');
$this->assertEquals(array('ini' => 'ini', 'yaml' => 'yaml', 'php' => 'php'), $this->container->getParameterBag()->all(), '->load() takes a single file name as its first argument');
}

/**
* @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('foo/'), '->supports() returns true if the resource is loadable');
$this->assertTrue($loader->supports('foo/', 'directory'), '->supports() returns true if the resource is loadable');
$this->assertTrue($loader->supports('foo', 'directory'), '->supports() returns true if the resource is loadable');
$this->assertFalse($loader->supports('foo'), '->supports() returns true if the resource is loadable');
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Component/HttpKernel/Kernel.php
Original file line number Diff line number Diff line change
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 @@ -741,6 +742,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
65 changes: 65 additions & 0 deletions src/Symfony/Component/Routing/Loader/DirectoryLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

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
{
private $currentDir;

/**
* @param mixed $file The resource
* @param string $type The resource type
*/
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);

$subType = is_dir("$path/$dir") ? 'directory' : null;
$subCollection = $this->import("$path/$dir", $subType, false, $path);
$collection->addCollection($subCollection);
}
}

return $collection;
}

/**
* Store here as well because FileLoader::currentDir is private

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing phpdoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my mind, I decided to add getCurrentDir to FileLoader instead and use it there. No need to store the same value two times.

*/
public function setCurrentDir($currentDir)
{
$this->currentDir = $currentDir;

parent::setCurrentDir($currentDir);
}

/**
* Returns true if this class supports the given resource.
*
* @param mixed $resource A resource
* @param string $type The resource type
*
* @return bool true if this class supports the given resource, false otherwise
*/
public function supports($resource, $type = null)
{
try {
$path = $this->locator->locate($resource, $this->currentDir);
} catch (\Exception $e) {
return false;
}

return is_string($resource) && 'directory' === $type;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
route1:
path: /route/1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
route2:
path: /route/2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
route3:
path: /route/3
63 changes: 63 additions & 0 deletions src/Symfony/Component/Routing/Tests/Loader/DirectoryLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?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;

class DirectoryLoaderTest extends AbstractAnnotationLoaderTest
{
protected $loader;
protected $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');
$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 true if the resource is loadable');
$this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable');

$this->assertTrue($this->loader->supports($fixturesDir, 'directory'), '->supports() checks the resource type if specified');
$this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified');
}
}