8000 Fluid interface for building routes in PHP by weaverryan · Pull Request #15778 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

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

Closed
wants to merge 34 commits into from

Conversation

weaverryan
Copy link
Member
Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets n/a
License MIT
Doc PR not yet...

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 and RouteCollectionBuilder are based off of Silex's Controller and ControllerCollection. The RouteCollectionBuilder is basically a RouteCollection that's able to import other resources. Here are the goals:

A) Import routes easily

$routes->import('routing.yml');

B) Fluid addition of routes into the collection

$routes->add('/admin', 'AppBundle:Admin:index', 'admin_index')
    ->setMethods(['GET']);

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:

$blogRoutes = $routes->createBuilder('/blog')
   ->setControllerClass('AppBundle\Controller\BlogController');
$blogRoutes->add('/', 'indexAction');
$blogRoutes->add('/{id}', 'editAction');
$routes->addBuilder($blogRoutes);

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!

/**
* @param Loader $loader
*/
public function __construct(Loader $loader)
Copy link
Member Author

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).

Copy link
Member

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

@sstok
8000 Copy link
Contributor
sstok commented Sep 14, 2015

👍 for this feature, really like this :)

@stloyd
Copy link
Contributor
stloyd commented Sep 14, 2015

@stof
Copy link
Member
stof commented Sep 14, 2015

@stloyd read the blog post you linked. Builders are precisely the place where they are not evil

@stloyd
Copy link
Contributor
stloyd commented Sep 14, 2015

@stof I read that blog post & I checked the proposed code, as you can see that there is builder (for collection) & new class Route which is not real builder (if it would be real builder I would not mention this post)...

@weaverryan
Copy link
Member Author

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.

@stof
Copy link
Member
stof commented Sep 16, 2015

@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) {
Copy link
Member

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

Copy link
Member Author

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

@weaverryan
Copy link
Member Author

@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 :)

@weaverryan
Copy link
Member Author

Nevermind - tests should now be green.

*/
public function addResource(ResourceInterface $resource)
{
$this->resources[] = $resource;
Copy link
Member

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.

Copy link
Member Author

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).

@xabbuh
Copy link
Member
xabbuh commented Sep 27, 2015

LGTM 👍

@weaverryan
Copy link
Member Author

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 setControllerClass() functionality, as it looks like the most controversial part.

/**
* @param LoaderInterface $loader
*/
public function __construct(LoaderInterface $loader)
Copy link
Member

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.

@weaverryan
Copy link
Member Author

Status: Needs Review

@fabpot
Copy link
Member
fabpot commented Oct 1, 2015

👍

public function import($resource, $type = null)
{
/** @var RouteCollection $collection */
$collection = $this->load($resource, $type);
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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)

Copy link
Member

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.

@xabbuh
Copy link
Member
xabbuh commented Oct 1, 2015

👍

Status: Reviewed

@fabpot
Copy link
Member
fabpot commented Oct 1, 2015

👍

1 similar comment
@aitboudad
Copy link
Contributor

👍

@fabpot
Copy link
Member
fabpot commented Oct 1, 2015

Thank you @weaverryan.

}

/**
* Creates the final ArrayCollection, returns it, and clears everything.
Copy link
Contributor

Choose a reason for hiding this comment

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

ArrayCollection?

@wouterj
Copy link
Member
wouterj commented Oct 28, 2015

These exciting features really deserve some docs before the stable release 😉

@weaverryan
Copy link
Member Author

I will definitely be taking care of that :)

fabpot added a commit that referenced this pull request Oct 29, 2015
…d (xabbuh)

This PR was merged into the 2.8 branch.

Discussion
----------

[Routing] fix docblock description for the build() method

| Q             | A
| ------------- | ---
| Fixed tickets | #15778
| License       | MIT

Commits
-------

ca32ed4 fix docblock description for the build() method
public function add($path, $controller, $name = null)
{
$route = new Route($path);
$route->setDefault('_controller', $controller);
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member Author

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 :)

Copy link
Member

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, ...)

Copy link
Member Author

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

fabpot added a commit that referenced this pull request Nov 5, 2015
…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
@weaverryan weaverryan deleted the route_builder branch November 5, 2015 16:18
@fabpot fabpot mentioned this pull request Nov 16, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0