-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Add support for "controller" keyword for configuring routes controllers #23227
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
Conversation
|
||
app_blog: | ||
path: /blog | ||
controller: AppBundle:Homepage:show |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for this not to throw, but let the defaults override it?
IMHO, it'd be better to throw an exception when both controller
attribute and _controller
default are set, in order to avoid some potential WTF moments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ogizanagi, I think you're right. I've changed the code to throw the exception when both controller attribute and _controller default are set.
* | ||
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru> | ||
*/ | ||
class ControllerAwareXmlFileLoader extends XmlFileLoader |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why using a separate file loader instead of adding the feature in the existing one ? AFAICT, adding the new feature is fully backward compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked myself the same. But I came to the conclusion it's legit, as the controller parameter is "tied" to the HttpKernel usage of the Routing component.
But then, I wonder if this rather belong to the HttpKernel
component or the FrameworkBundle
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, RouteCollectionBuilder::add()
also sets the controller and is in the component itself...
Eventually, it's probably not worth it to create separate loaders for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof, The routing component doesn’t know about "controllers", "views", "models" and all things like that.
The Routing component maps an HTTP request to a set of configuration variables.
The controller
keyword is valueless when you use the routing component as a standalone library. So I thinks it's better to extend the existing loaders than change them.
@@ -27,7 +27,7 @@ | |||
*/ | |||
class YamlFileLoader extends FileLoader | |||
{ | |||
private static $availableKeys = array( | |||
protected static $availableKeys = array( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces inheritance extension points, which then have to be maintained forever. And inheritance extension points are the worse to maintain for BC. So -1
I'm not sure about this proposal. It's better for DX ... but it introduces another way to configure something that you can already configure it in several ways. |
Thus, it might then confuse people about why there is |
@stof, I think it's not a problem. Most users prefer to configure routes using annotations and don't know about the Request's |
I like the idea. Back then, I didn't know if we would have more than |
Same to me, DX wise, it's way easier to use. |
Out of curiosity, is there a way to not define a controller and have the route work? Aside from it being just a placeholder, like for example the logout route. If there is no other way to use routes that to map them to controllers, it should just by a first-class citizen. |
I totally agree about using the existing loader. It also means that other projects uisng the component without FrameworkBundle (Silex, Drupal, etc...) get the same experience. |
@stof, @nicolas-grekas, Okay, maybe you're right. I've made changes to use the existing loaders instead of creating the new ones. |
* A controller's key name (e.g. '_controller'). | ||
* | ||
* @var string | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't document private properties. This can be removed
private $controllerKey; | ||
|
||
/** | ||
* Constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can eb removed
/** | ||
* Constructor. | ||
* | ||
* @param FileLocatorInterface $locator A FileLocatorInterface instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed as well
throw new \InvalidArgumentException(sprintf( | ||
'The routing file "%s" must not specify both the "controller" attribute and the defaults key "%s" for %s.', | ||
$path, $this->controllerKey, $name | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be on one line
* | ||
* @var mixed | ||
*/ | ||
private $controllerKey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
phpdocs to be removed
private $controllerKey; | ||
|
||
/** | ||
* Constructor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be removed
/** | ||
* Constructor. | ||
* | ||
* @param FileLocatorInterface $locator A FileLocatorInterface instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be removed
throw new \InvalidArgumentException(sprintf( | ||
'The routing file "%s" must not specify both the "controller" key and the defaults key "%s" for "%s".', | ||
$path, $this->controllerKey, $name | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be on one line
@fabpot, I've fixed all the CS issues you pointed out. |
/** | ||
* @param string $controllerKey A controller's key name | ||
*/ | ||
public function __construct(FileLocatorInterface $locator, $controllerKey = 'controller') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not _controller
as default?
why do we need this to be configurable?
@@ -26,11 +26,13 @@ | |||
<service id="routing.loader.xml" class="Symfony\Component\Routing\Loader\XmlFileLoader"> | |||
<tag name="routing.loader" /> | |||
<argument type="service" id="file_locator" /> | |||
<argument>_controller</argument> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if _controller
is going to be the default in the loaders constructors, then this should be removed (same below)
@nicolas-grekas, Fixed. I've removed the |
W don't merge PRs with merge commit, please rebase instead. |
@nicolas-grekas, sorry, I used the GitHub's web interface to resolve conflict and didn't know that it creates merge commit. I've removed that commit and rebased the PR. Thanks! |
@@ -13,8 +13,8 @@ | |||
|
|||
use PHPUnit\Framework\TestCase; | |||
use Symfony\Component\Config\FileLocator; | |||
use Symfony\Component\Routing\Loader\YamlFileLoader; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please keep original ordering (that's our policy: alpha order for new items, "as is" for existing ones)
@@ -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 routes controllers in YAML and XML configurations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
routes controllers (route with no "s")
@@ -11,11 +11,11 @@ | |||
|
|||
namespace Symfony\Component\Routing\Loader; | |||
|
|||
use Symfony\Component\Routing\RouteCollection; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please keep original ordering
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing quotes around the 2nd %s
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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be great to formulate the message so that $path is last
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolas-grekas, I just copied an existing message from here: https://github.com/symfony/symfony/blob/3.4/src/Symfony/Component/Routing/Loader/YamlFileLoader.php#L191
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Route "%s" must must not specify both the "controller" attribute and the defaults key "_controller" in file "%s"'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sstok, yes, but all other messages have a format with a file path at the beginning. I think we should keep the consistency.
@@ -11,12 +11,12 @@ | |||
|
|||
namespace Symfony\Component\Routing\Loader; | |||
|
|||
use Symfony\Component\Routing\RouteCollection; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
original ordering
@@ -116,6 +116,10 @@ protected function parseRoute(RouteCollection $collection, $name, array $config, | |||
$methods = isset($config['methods']) ? $config['methods'] : array(); | |||
$condition = isset($config['condition']) ? $config['condition'] : null; | |||
|
|||
if ($controller = isset($config['controller']) ? $config['controller'] : null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!empty($config['controller'])) {
$defaults['_controller'] = $config['controller'];
}
@@ -141,6 +145,10 @@ protected function parseImport(RouteCollection $collection, array $config, $path | |||
$schemes = isset($config['schemes']) ? $config['schemes'] : null; | |||
$methods = isset($config['methods']) ? $config['methods'] : null; | |||
|
|||
if ($controller = isset($config['controller']) ? $config['controller'] : null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above
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" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should also have another imported route that does not specify the controller
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what about a case where an imported route makes use of the _controller
default?
* @expectedException \InvalidArgumentException | ||
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for the "import" tag/ | ||
*/ | ||
public function testImportWithOverridedController() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overridden
* @expectedException \InvalidArgumentException | ||
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "_static"/ | ||
*/ | ||
public function testImportWithOverridedController() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overridden
public function testOverrideControllerInDefaults() | ||
{ | ||
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); | ||
$routeCollection = $loader->load('override_defaults.yml'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$routeCollection
is not used
public function testImportWithOverridedController() | ||
{ | ||
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); | ||
$routeCollection = $loader->load('import_override_defaults.yml'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$routeCollection
is not used
public function testImportWithOverridedController() | ||
{ | ||
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); | ||
$routeCollection = $loader->load('import_override_defaults.xml'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$routeCollection
is not used
public function testOverrideControllerInDefaults() | ||
{ | ||
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/controller'))); | ||
$routeCollection = $loader->load('override_defaults.xml'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$routeCollection
is not used
@@ -141,6 +145,10 @@ protected function parseImport(RouteCollection $collection, array $config, $path | |||
$schemes = isset($config['schemes']) ? $config['schemes'] : null; | |||
$methods = isset($config['methods']) ? $config['methods'] : null; | |||
|
|||
if (!empty($config['controller'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not isset()
instead?
@@ -116,6 +116,10 @@ protected function parseRoute(RouteCollection $collection, $name, array $config, | |||
$methods = isset($config['methods']) ? $config['methods'] : array(); | |||
$condition = isset($config['condition']) ? $config['condition'] : null; | |||
|
|||
if (!empty($config['controller'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not isset()
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed my downvote :) this is solved rather elegant actually 👍
@@ -204,5 +212,8 @@ protected function validate($config, $name, $path) | |||
$name, $path | |||
)); | |||
} | |||
if (isset($config['controller']) && isset($config['defaults']['_controller'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be written as isset($a, $b)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly you can't. See #22684.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, i've forgot about that. 👍
@xabbuh, thanks for the code review! I've fixed all issues you pointed out and added the missing test cases. |
|
||
$route = $routeCollection->get('app_homepage'); | ||
|
||
$this->assertSame('FrameworkBundle:Template:template', $route->getDefault('_controller')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here should now have an assertion for the other import route to have the _controller
attribute to be set to the value configured in the import.xml
file (same for the YamlFileLoaderTest
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xabbuh, I've added the missing assertions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about a case where the importing file makes use of the new controller
option while the imported file makes use of the _controller
attribute (and vice versa)? Should that be a supported use case?
@xabbuh, I think you're right! I've added the test case you suggested. |
Thank you @voronkovich. |
…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
@voronkovich can you please open a doc PR/issue? |
@nicolas-grekas, the issue is already created (thank you, @HeahDude!): symfony/symfony-docs#8240 |
FYI, the |
This PR adds a syntax sugar for configuring routes controllers in more user-friendly way by using a
controller
keyword.