[Routing] Add PHP fluent DSL for configuring routes
This commit is contained in:
parent
b1b686081b
commit
f433c9a79d
@ -0,0 +1,79 @@
|
|||||||
|
<?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\Configurator;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class CollectionConfigurator
|
||||||
|
{
|
||||||
|
use Traits\AddTrait;
|
||||||
|
use Traits\RouteTrait;
|
||||||
|
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
public function __construct(RouteCollection $parent, $name)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->collection = new RouteCollection();
|
||||||
|
$this->route = new Route('');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->collection->addPrefix(rtrim($this->route->getPath(), '/'));
|
||||||
|
$this->parent->addCollection($this->collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a route.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return RouteConfigurator
|
||||||
|
*/
|
||||||
|
final public function add($name, $path)
|
||||||
|
{
|
||||||
|
$this->collection->add($this->name.$name, $route = clone $this->route);
|
||||||
|
|
||||||
|
return new RouteConfigurator($this->collection, $route->setPath($path), $this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sub-collection.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
final public function collection($name = '')
|
||||||
|
{
|
||||||
|
return new self($this->collection, $this->name.$name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the prefix to add to the path of all child routes.
|
||||||
|
*
|
||||||
|
* @param string $prefix
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function prefix($prefix)
|
||||||
|
{
|
||||||
|
$this->route->setPath($prefix);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<?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\Configurator;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class ImportConfigurator
|
||||||
|
{
|
||||||
|
use Traits\RouteTrait;
|
||||||
|
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
public function __construct(RouteCollection $parent, RouteCollection $route)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->route = $route;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->parent->addCollection($this->route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the prefix to add to the path of all child routes.
|
||||||
|
*
|
||||||
|
* @param string $prefix
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function prefix($prefix)
|
||||||
|
{
|
||||||
|
$this->route->addPrefix($prefix);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?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\Configurator;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class RouteConfigurator
|
||||||
|
{
|
||||||
|
use Traits\AddTrait;
|
||||||
|
use Traits\RouteTrait;
|
||||||
|
|
||||||
|
public function __construct(RouteCollection $collection, Route $route, $name = '')
|
||||||
|
{
|
||||||
|
$this->collection = $collection;
|
||||||
|
$this->route = $route;
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
<?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\Configurator;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Loader\PhpFileLoader;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class RoutingConfigurator
|
||||||
|
{
|
||||||
|
use Traits\AddTrait;
|
||||||
|
|
||||||
|
private $loader;
|
||||||
|
private $path;
|
||||||
|
private $file;
|
||||||
|
|
||||||
|
public function __construct(RouteCollection $collection, PhpFileLoader $loader, $path, $file)
|
||||||
|
{
|
||||||
|
$this->collection = $collection;
|
||||||
|
$this->loader = $loader;
|
||||||
|
$this->path = $path;
|
||||||
|
$this->file = $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ImportConfigurator
|
||||||
|
*/
|
||||||
|
final public function import($resource, $type = null, $ignoreErrors = false)
|
||||||
|
{
|
||||||
|
$this->loader->setCurrentDir(dirname($this->path));
|
||||||
|
$subCollection = $this->loader->import($resource, $type, $ignoreErrors, $this->file);
|
||||||
|
|
||||||
|
return new ImportConfigurator($this->collection, $subCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CollectionConfigurator
|
||||||
|
*/
|
||||||
|
final public function collection($name = '')
|
||||||
|
{
|
||||||
|
return new CollectionConfigurator($this->collection, $name);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
<?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\Configurator\Traits;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
trait AddTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var RouteCollection
|
||||||
|
*/
|
||||||
|
private $collection;
|
||||||
|
|
||||||
|
private $name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a route.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return RouteConfigurator
|
||||||
|
*/
|
||||||
|
final public function add($name, $path)
|
||||||
|
{
|
||||||
|
$this->collection->add($this->name.$name, $route = new Route($path));
|
||||||
|
|
||||||
|
return new RouteConfigurator($this->collection, $route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a route.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @return RouteConfigurator
|
||||||
|
*/
|
||||||
|
final public function __invoke($name, $path)
|
||||||
|
{
|
||||||
|
return $this->add($name, $path);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
<?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\Configurator\Traits;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
|
trait RouteTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var RouteCollection|Route
|
||||||
|
*/
|
||||||
|
private $route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds defaults.
|
||||||
|
*
|
||||||
|
* @param array $defaults
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function defaults(array $defaults)
|
||||||
|
{
|
||||||
|
$this->route->addDefaults($defaults);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds requirements.
|
||||||
|
*
|
||||||
|
* @param array $requirements
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function requirements(array $requirements)
|
||||||
|
{
|
||||||
|
$this->route->addRequirements($requirements);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds options.
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function options(array $options)
|
||||||
|
{
|
||||||
|
$this->route->addOptions($options);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the condition.
|
||||||
|
*
|
||||||
|
* @param string $condition
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function condition($condition)
|
||||||
|
{
|
||||||
|
$this->route->setCondition($condition);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pattern for the host.
|
||||||
|
*
|
||||||
|
* @param string $pattern
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function host($pattern)
|
||||||
|
{
|
||||||
|
$this->route->setHost($pattern);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the schemes (e.g. 'https') this route is restricted to.
|
||||||
|
* So an empty array means that any scheme is allowed.
|
||||||
|
*
|
||||||
|
* @param array $schemes
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function schemes(array $schemes)
|
||||||
|
{
|
||||||
|
$this->route->setSchemes($schemes);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HTTP methods (e.g. 'POST') this route is restricted to.
|
||||||
|
* So an empty array means that any method is allowed.
|
||||||
|
*
|
||||||
|
* @param array $methods
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function methods(array $methods)
|
||||||
|
{
|
||||||
|
$this->route->setMethods($methods);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the "_controller" entry to defaults.
|
||||||
|
*
|
||||||
|
* @param callable $controller a callable or parseable pseudo-callable
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
final public function controller($controller)
|
||||||
|
{
|
||||||
|
$this->route->addDefaults(array('_controller' => $controller));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Routing\Loader;
|
|||||||
|
|
||||||
use Symfony\Component\Config\Loader\FileLoader;
|
use Symfony\Component\Config\Loader\FileLoader;
|
||||||
use Symfony\Component\Config\Resource\FileResource;
|
use Symfony\Component\Config\Resource\FileResource;
|
||||||
|
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
||||||
use Symfony\Component\Routing\RouteCollection;
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,7 +38,21 @@ class PhpFileLoader extends FileLoader
|
|||||||
$path = $this->locator->locate($file);
|
$path = $this->locator->locate($file);
|
||||||
$this->setCurrentDir(dirname($path));
|
$this->setCurrentDir(dirname($path));
|
||||||
|
|
||||||
$collection = self::includeFile($path, $this);
|
// the closure forbids access to the private scope in the included file
|
||||||
|
$loader = $this;
|
||||||
|
$load = \Closure::bind(function ($file) use ($loader) {
|
||||||
|
return include $file;
|
||||||
|
}, null, ProtectedPhpFileLoader::class);
|
||||||
|
|
||||||
|
$result = $load($path);
|
||||||
|
|
||||||
|
if ($result instanceof \Closure) {
|
||||||
|
$collection = new RouteCollection();
|
||||||
|
$result(new RoutingConfigurator($collection, $this, $path, $file), $this);
|
||||||
|
} else {
|
||||||
|
$collection = $result;
|
||||||
|
}
|
||||||
|
|
||||||
$collection->addResource(new FileResource($path));
|
$collection->addResource(new FileResource($path));
|
||||||
|
|
||||||
return $collection;
|
return $collection;
|
||||||
@ -50,17 +65,11 @@ class PhpFileLoader extends FileLoader
|
|||||||
{
|
{
|
||||||
return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type);
|
return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Safe include. Used for scope isolation.
|
/**
|
||||||
*
|
* @internal
|
||||||
* @param string $file File to include
|
*/
|
||||||
* @param PhpFileLoader $loader the loader variable is exposed to the included file below
|
final class ProtectedPhpFileLoader extends PhpFileLoader
|
||||||
*
|
{
|
||||||
* @return RouteCollection
|
|
||||||
*/
|
|
||||||
private static function includeFile($file, PhpFileLoader $loader)
|
|
||||||
{
|
|
||||||
return include $file;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
21
src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php
Normal file
21
src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Routing\Loader\Configurator;
|
||||||
|
|
||||||
|
return function (RoutingConfigurator $routes) {
|
||||||
|
$routes
|
||||||
|
->add('foo', '/foo')
|
||||||
|
->condition('abc')
|
||||||
|
->options(array('utf8' => true))
|
||||||
|
->add('buz', 'zub')
|
||||||
|
->controller('foo:act');
|
||||||
|
|
||||||
|
$routes->import('php_dsl_sub.php')
|
||||||
|
->prefix('/sub')
|
||||||
|
->requirements(array('id' => '\d+'));
|
||||||
|
|
||||||
|
$routes->add('ouf', '/ouf')
|
||||||
|
->schemes(array('https'))
|
||||||
|
->methods(array('GET'))
|
||||||
|
->defaults(array('id' => 0));
|
||||||
|
};
|
14
src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub.php
Normal file
14
src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Routing\Loader\Configurator;
|
||||||
|
|
||||||
|
return function (RoutingConfigurator $routes) {
|
||||||
|
$add = $routes->collection('c_')
|
||||||
|
->prefix('pub');
|
||||||
|
|
||||||
|
$add('bar', '/bar');
|
||||||
|
|
||||||
|
$add->collection('pub_')
|
||||||
|
->host('host')
|
||||||
|
->add('buz', 'buz');
|
||||||
|
};
|
@ -13,7 +13,10 @@ namespace Symfony\Component\Routing\Tests\Loader;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
use Symfony\Component\Config\Resource\FileResource;
|
||||||
use Symfony\Component\Routing\Loader\PhpFileLoader;
|
use Symfony\Component\Routing\Loader\PhpFileLoader;
|
||||||
|
use Symfony\Component\Routing\Route;
|
||||||
|
use Symfony\Component\Routing\RouteCollection;
|
||||||
|
|
||||||
class PhpFileLoaderTest extends TestCase
|
class PhpFileLoaderTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -80,4 +83,38 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
(string) $fileResource
|
(string) $fileResource
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRoutingConfigurator()
|
||||||
|
{
|
||||||
|
$locator = new FileLocator(array(__DIR__.'/../Fixtures'));
|
||||||
|
$loader = new PhpFileLoader($locator);
|
||||||
|
$routeCollection = $loader->load('php_dsl.php');
|
||||||
|
|
||||||
|
$expectedCollection = new RouteCollection();
|
||||||
|
|
||||||
|
$expectedCollection->add('foo', (new Route('/foo'))
|
||||||
|
->setOptions(array('utf8' => true))
|
||||||
|
->setCondition('abc')
|
||||||
|
);
|
||||||
|
$expectedCollection->add('buz', (new Route('/zub'))
|
||||||
|
->setDefaults(array('_controller' => 'foo:act'))
|
||||||
|
);
|
||||||
|
$expectedCollection->add('c_bar', (new Route('/sub/pub/bar'))
|
||||||
|
->setRequirements(array('id' => '\d+'))
|
||||||
|
);
|
||||||
|
$expectedCollection->add('c_pub_buz', (new Route('/sub/pub/buz'))
|
||||||
|
->setHost('host')
|
||||||
|
->setRequirements(array('id' => '\d+'))
|
||||||
|
);
|
||||||
|
$expectedCollection->add('ouf', (new Route('/ouf'))
|
||||||
|
->setSchemes(array('https'))
|
||||||
|
->setMethods(array('GET'))
|
||||||
|
->setDefaults(array('id' => 0))
|
||||||
|
);
|
||||||
|
|
||||||
|
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub.php')));
|
||||||
|
$expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl.php')));
|
||||||
|
|
||||||
|
$this->assertEquals($expectedCollection, $routeCollection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user