-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Fluid interface for building routes in PHP #15778
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
/** | ||
* @param Loader $loader | ||
*/ | ||
public function __construct(Loader $loader) |
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.
Like in #15742, this could be LoaderInterface
instead... which I believe would be ok, since I think we could still "import", just using the longer syntax below (which the user wouldn't need to be bothered with).
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.
IMO, the interface should be used. We should not encourage typehinting against implementations
👍 for this feature, really like this :) |
@stloyd read the blog post you linked. Builders are precisely the place where they are not evil |
@stof I read that blog post & I checked the proposed code, as you can see that there is builder (for collection) & new class |
f7dd327
to
c87515a
Compare
Ping @symfony/deciders There are some deprecated warnings being triggered by this PR on some PHP version - I haven't tracked that down yet, but it's a non-issue. |
@weaverryan IMO, it is not a non issue |
$controller = $route->getDefault('_controller'); | ||
|
||
// only apply controller class to a (non-empty) string | ||
if (!is_string($controller) || !$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.
I would say '' === $controller
. It would be more readable
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.
It's not quite the same thing - I'm also filtering for other things, like array()s
@stof without looking into it too far, you don't know why deprecation warnings would be thrown only when "deps" isn't set (deps=high and deps=low looks good)? I can't trigger the same deprecation warnings locally with this code, nor can I see how it's possible that this part of the code is being executed. It is an issue that will need to be resolved before merging, but fundamentally, the PR won't need to use deprecated functionality. I just have to track it down :) |
Nevermind - tests should now be green. |
*/ | ||
public function addResource(ResourceInterface $resource) | ||
{ | ||
$this->resources[] = $resource; |
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 check before if the configured loader actually supports the given resource.
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 purposefully mirrors RouteCollection::addResource()
, and I think we should keep it dumb: I could choose to add a "resource" here for the purposes of cache-clearing, even if my loader doesn't support that resource (perhaps I know better than the loader, that this resource was in fact used to determine some route information).
b309f39
to
678e622
Compare
LGTM 👍 |
678e622
to
0a74034
Compare
Status: Reviewed I'm really hoping this will be considered for 2.8 to make adding PHP routes easier. I've already trimmed it down a lot to make it an easier merge. If I need to, I can remove the |
/** | ||
* @param LoaderInterface $loader | ||
*/ | ||
public function __construct(LoaderInterface $loader) |
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 making $loader
optional? If not passed, throw an exception when the user calls import()
. Everything else would still work.
…ut how to write this
Status: Needs Review |
👍 |
public function import($resource, $type = null) | ||
{ | ||
/** @var RouteCollection $collection */ | ||
$collection = $this->load($resource, $type); |
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.
The load()
message can throw a FileLoaderLoadException
which should be documented in the docblock.
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.
Do we add that here, even though it's a second method call that throws the exception? I'm actually not sure what the standard is here.
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'd say we should do that here. The nested method call is a private method and people will never realise they may have to be aware of the exception if we don't document that here.
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.
makes sense to me - I've added it - easy to remove if others don't want it
* @param string $controller The route's controller | ||
* @param string|null $name The name to give this route | ||
* | ||
* @return Route |
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 is a bit counter-intuitive as almost all other methods return the builder itself. Though probably there is no better solution.
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 almost wrote the same comment but I think it makes sense for this one to return the route.
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.
The name is disturbing, you don not expect a Route
. I think add
should do what addRoute
does (just adding a route to the collection), and then this one, like in the ContainerBuilder
should be named register
or something like that ? cf https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/DependencyInjection/ContainerBuilder.php#L728
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 tried to make this method the most easily guessable, since you will call this much more often than addRoute()
. If we called this register()
, I think people would be looking for a method like add()
or addRoute()
, and might try calling addRoute()
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.
It also makes sense, as there is no set
method in the ContainerBuilder
too (it's directly setDefinition
)
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.
When looking at the code written with this builder, add()
looks much better and I think it's quite intuitive.
👍 Status: Reviewed |
👍 |
1 similar comment
👍 |
Thank you @weaverryan. |
} | ||
|
||
/** | ||
* Creates the final ArrayCollection, returns it, and clears everything. |
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.
ArrayCollection?
These exciting features really deserve some docs before the stable release 😉 |
I will definitely be taking care of that :) |
public function add($path, $controller, $name = null) | ||
{ | ||
$route = new Route($path); | ||
$route->setDefault('_controller', $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.
The routing component doesn't know anything about a _controller
. So this seems misplaced. Such assumption can only be made in the framework bundle.
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.
@Tobion you're right that the Routing component doesn't know about _controller
. But HttpKernel does know about this, and I wanted this to be available for things like Drupal. Since there is no bridge between HttpKernel and Routing, I put it here, which purposefully makes this class useable only by people who follow this convention. It's not perfect, but it's the best place. Of course, if you wanted to use this in another system, you could override this method.
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 HttpKernel does, why not putting it in HttpKernel 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.
If we put it in HttpKernel, someone will ask why we don't put it in Routing :)
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 then we have a clear answer. HttpKernel contains classes that add just a little "HttpKernel convention sauce" on top of classes provided by other components (DI Extension, File locator, ...)
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.
Good point. My counterpoint is that it makes sense to use this without HttpKernel - maybe you have a system where _controller is meaningful or you override this method. That should be possible without requiring http-kernel, which this class doesn't actually depend on.
So there's no perfect spot, but I think this is best
…tionBuilder (weaverryan) This PR was merged into the 2.8 branch. Discussion ---------- Re-adding the ability to add a resource to the RouteCollectionBuilder | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a At the end of #15778, I removed the ability to add a resource to the RouteCollectionBuilder. But, this is needed (at least internally): if you import, the returned RouteCollection may have resources, which need to be brought forward into the new RouteCollectionBuilder (the code in `import()` to do this already exists, but is calling an undefined `addResource()` method). This adds the test with minimal code to fix. P.S. Fabien told me to remove `addResource` originally... so isn't this *kind of* his fault? Commits ------- 3b67daa Re-adding the ability to add a resource to the RouteCollectionBuilder
This - along with #15742 - attempts to making adding routes in PHP (via an actual class+method) not only possible, but also useful.
The two classes -
Route
andRouteCollectionBuilder
are based off of Silex'sController
andControllerCollection
. TheRouteCollectionBuilder
is basically aRouteCollection
that's able to import other resources. Here are the goals:A) Import routes easily
B) Fluid addition of routes into the collection
C) Ability to create routes with auto-generating names
D) Ability to add a "sub-collection" (kind of like an import, without pointing to another file). Included is the ability to set the controller class:
E) The collection options can be set before or after the routes. With
RouteCollection
, if you set something - e.g. a prefix or a default - and THEN add more routes, those options are not passed to those routes. This is by design, but not ideal for building routes (e.g. in the previous code example, the controllerClass would not be applied using the opposite logic, since it's set before adding the routes).Thanks!