Skip to content

Commit

Permalink
feature #23227 Add support for "controller" keyword for configuring r…
Browse files Browse the repository at this point in the history
…outes controllers (voronkovich)

This PR was merged into the 3.4 branch.

Discussion
----------

Add support for "controller" keyword for configuring routes controllers

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| License       | MIT

This PR adds a syntax sugar for configuring routes controllers in more user-friendly way by using a `controller` keyword.

```yaml
# Current syntax
# It looks strange and exposes Symfony's internals
blog_show:
    path:     /blog/{slug}
    defaults: { _controller: AppBundle:Blog:show }

# Suggested syntax
blog_show:
    path:       /blog/{slug}
    controller: AppBundle:Blog:show
```
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <!-- Current syntax -->
    <route id="blog_list" path="/blog">
        <default key="_controller">AppBundle:Blog:list</default>
    </route>

    <!-- Suggested syntax -->
    <route id="blog_list" path="/blog" controller="AppBundle:Blog:list" />
</routes>
```

Commits
-------

06bbee8 [Routing] Use "controller" keyword for configuring routes controllers
  • Loading branch information
nicolas-grekas committed Aug 16, 2017
2 parents 9026aed + 06bbee8 commit 8c4a1e7
Show file tree
Hide file tree
Showing 16 changed files with 253 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/Symfony/Component/Routing/CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ CHANGELOG

* Added support for prioritized routing loaders.
* Add matched and default parameters to redirect responses
* Added support for a `controller` keyword for configuring route controllers in YAML and XML configurations.

3.3.0
-----
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/Routing/Loader/XmlFileLoader.php
Expand Up @@ -229,6 +229,16 @@ private function parseConfigs(\DOMElement $node, $path)
}
}

if ($controller = $node->getAttribute('controller')) {
if (isset($defaults['_controller'])) {
$name = $node->hasAttribute('id') ? sprintf('"%s"', $node->getAttribute('id')) : sprintf('the "%s" tag', $node->tagName);

throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for %s.', $path, $name));
}

$defaults['_controller'] = $controller;
}

return array($defaults, $requirements, $options, $condition);
}

Expand Down
13 changes: 12 additions & 1 deletion src/Symfony/Component/Routing/Loader/YamlFileLoader.php
Expand Up @@ -27,7 +27,7 @@
class YamlFileLoader extends FileLoader
{
private static $availableKeys = array(
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition',
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller',
);
private $yamlParser;

Expand Down Expand Up @@ -115,6 +115,10 @@ protected function parseRoute(RouteCollection $collection, $name, array $config,
$methods = isset($config['methods']) ? $config['methods'] : array();
$condition = isset($config['condition']) ? $config['condition'] : null;

if (isset($config['controller'])) {
$defaults['_controller'] = $config['controller'];
}

$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);

$collection->add($name, $route);
Expand All @@ -140,6 +144,10 @@ protected function parseImport(RouteCollection $collection, array $config, $path
$schemes = isset($config['schemes']) ? $config['schemes'] : null;
$methods = isset($config['methods']) ? $config['methods'] : null;

if (isset($config['controller'])) {
$defaults['_controller'] = $config['controller'];
}

$this->setCurrentDir(dirname($path));

$subCollection = $this->import($config['resource'], $type, false, $file);
Expand Down Expand Up @@ -203,5 +211,8 @@ protected function validate($config, $name, $path)
$name, $path
));
}
if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name));
}
}
}
Expand Up @@ -41,6 +41,7 @@
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="schemes" type="xsd:string" />
<xsd:attribute name="methods" type="xsd:string" />
<xsd:attribute name="controller" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="import">
Expand All @@ -52,6 +53,7 @@
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="schemes" type="xsd:string" />
<xsd:attribute name="methods" type="xsd:string" />
<xsd:attribute name="controller" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="default" mixed="true">
Expand Down
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<import resource="routing.xml">
<default key="_controller">FrameworkBundle:Template:template</default>
</import>
</routes>
@@ -0,0 +1,4 @@
_static:
resource: routing.yml
defaults:
_controller: FrameworkBundle:Template:template
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<import resource="routing.xml" controller="FrameworkBundle:Template:template" />
</routes>
@@ -0,0 +1,3 @@
_static:
resource: routing.yml
controller: FrameworkBundle:Template:template
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<import resource="routing.xml" controller="FrameworkBundle:Template:template">
<default key="_controller">AppBundle:Blog:index</default>
</import>
</routes>
@@ -0,0 +1,5 @@
_static:
resource: routing.yml
controller: FrameworkBundle:Template:template
defaults:
_controller: AppBundle:Homepage:show
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="app_blog" path="/blog" controller="AppBundle:Homepage:show">
<default key="_controller">AppBundle:Blog:index</default>
</route>
</routes>
@@ -0,0 +1,5 @@
app_blog:
path: /blog
controller: AppBundle:Homepage:show
defaults:
_controller: AppBundle:Blog:index
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing
http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="app_homepage" path="/" controller="AppBundle:Homepage:show" />

<route id="app_blog" path="/blog">
<default key="_controller">AppBundle:Blog:list</default>
</route>

<route id="app_logout" path="/logout" />
</routes>
@@ -0,0 +1,11 @@
app_homepage:
path: /
controller: AppBundle:Homepage:show

app_blog:
path: /blog
defaults:
_controller: AppBundle:Blog:list

app_logout:
path: /logout
74 changes: 74 additions & 0 deletions src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
Expand Up @@ -287,4 +287,78 @@ public function testNullValuesInMap()
$route->getDefault('map')
);
}

public function testLoadRouteWithControllerAttribute()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load('routing.xml');

$route = $routeCollection->get('app_homepage');

$this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller'));
}

public function testLoadRouteWithoutControllerAttribute()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load('routing.xml');

$route = $routeCollection->get('app_logout');

$this->assertNull($route->getDefault('_controller'));
}

public function testLoadRouteWithControllerSetInDefaults()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load('routing.xml');

$route = $routeCollection->get('app_blog');

$this->assertSame('AppBundle:Blog:list', $route->getDefault('_controller'));
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for "app_blog"/
*/
public function testOverrideControllerInDefaults()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$loader->load('override_defaults.xml');
}

/**
* @dataProvider provideFilesImportingRoutesWithControllers
*/
public function testImportRouteWithController($file)
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load($file);

$route = $routeCollection->get('app_homepage');
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));

$route = $routeCollection->get('app_blog');
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));

$route = $routeCollection->get('app_logout');
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
}

public function provideFilesImportingRoutesWithControllers()
{
yield array('import_controller.xml');
yield array('import__controller.xml');
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for the "import" tag/
*/
public function testImportWithOverriddenController()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$loader->load('import_override_defaults.xml');
}
}
74 changes: 74 additions & 0 deletions src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
Expand Up @@ -108,4 +108,78 @@ public function testLoadWithResource()
$this->assertSame('context.getMethod() == "POST"', $route->getCondition());
}
}

public function testLoadRouteWithControllerAttribute()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load('routing.yml');

$route = $routeCollection->get('app_homepage');

$this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller'));
}

public function testLoadRouteWithoutControllerAttribute()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load('routing.yml');

$route = $routeCollection->get('app_logout');

$this->assertNull($route->getDefault('_controller'));
}

public function testLoadRouteWithControllerSetInDefaults()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load('routing.yml');

$route = $routeCollection->get('app_blog');

$this->assertSame('AppBundle:Blog:list', $route->getDefault('_controller'));
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "app_blog"/
*/
public function testOverrideControllerInDefaults()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$loader->load('override_defaults.yml');
}

/**
* @dataProvider provideFilesImportingRoutesWithControllers
*/
public function testImportRouteWithController($file)
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$routeCollection = $loader->load($file);

$route = $routeCollection->get('app_homepage');
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));

$route = $routeCollection->get('app_blog');
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));

$route = $routeCollection->get('app_logout');
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller'));
}

public function provideFilesImportingRoutesWithControllers()
{
yield array('import_controller.yml');
yield array('import__controller.yml');
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "_static"/
*/
public function testImportWithOverriddenController()
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller')));
$loader->load('import_override_defaults.yml');
}
}

0 comments on commit 8c4a1e7

Please sign in to comment.