Skip to content

[Routing] Add support for 'priority' option for annotated Routes #11753

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 4 commits into from

Conversation

ghostika
Copy link

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets [sensiolabs/SensioFrameworkExtraBundle#320]
License MIT

If the given route option parameter has a priority key, it will be sorted. The use case is described in the linked ticket. This is a new implementation, previous one (#11747) was proven bad.

If the changes are acceptable, then I will create the changes also for the Doc

@@ -122,6 +132,10 @@ public function load($class, $type = null)
$this->defaultRouteIndex = 0;
foreach ($this->reader->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $this->routeAnnotationClass) {
if ($annot->getOption('priority')) {
$this->routesWithPriority[$annot->getOption('priority')][]
Copy link
Member

Choose a reason for hiding this comment

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

Where $this->routesWithPriority[$annot->getOption('priority')] is initialized ?

Copy link
Author

Choose a reason for hiding this comment

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

Which part do you mean? Priority key in option or routesWithPriority? first one will be in Route annotation from frameworkextrabundle, second is before the constructor

Copy link
Member

Choose a reason for hiding this comment

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

Nevermind, I thought, that this code will raise a notice...

$foo = array();
$foo['bar'][] = 'baz';

Because $foo['bar'] is not defined, but it's works with E_STRICT...

->will($this->returnValue($routeDatas));

$routeCollection = $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/BarClass.php');
$expectedOrder = array('foo', 'bar', 'home', 'login', 'static_contact', 'static_location', 'static_id');
Copy link
Member

Choose a reason for hiding this comment

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

should'nt be array('static_contact', 'static_location', 'static_id', 'foo', 'bar', 'home', 'login'); ? How to prioritize route before a route without priority.
Mays be routes without priority could be sets to 0.

Copy link
Author

Choose a reason for hiding this comment

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

In the original Issue there were no actual discussion about this and Ryan suggested to implement it in a way and let's see what will the community think. Of course that is also a viable option to set them to 0.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I agree with @jeremy-derusse: If there's no priority on a route, it should be treated like a 0. And so, a priority of 1 should be before all routes without a priority. This would be consistent with how event priority's are done: (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/EventDispatcher/EventDispatcher.php#L95 and (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/EventDispatcher/EventDispatcher.php#L176)

As far as implementing this, I'm not sure if the current approach of calling addPriorityRoutes will work or not. We need to somehow take the final route $collection (i.e. after the foreach in AnnotationClassLoader and re-order it based on the priority of all routes in that list. Perhaps a function that looks like $this->loader->reorderCollection($collection) that compares the route names with an internal priority array that AnnotationClassLoader has?

Thanks!

Copy link
Author

Choose a reason for hiding this comment

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

Actually, I didn't wanted to touch RouteCollection at all, but I have a proposal, RouteCollection has a method, addOptions(), where if there is a route with priority at the end of the process I can pass in array(priority => 0)
and then every route in the RouteCollection will have this value, and at the end in the DirectoryLoader I add the routes with real priority option and with this, I don't need to change anything in the RouterCollection.

Copy link
Member

Choose a reason for hiding this comment

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

That sounds good to me! My proposal above also didn't touch RouteCollection either, but as long as there's no downside to adding the option (I don't know of any), your idea will be cleaner.

@ghostika
Copy link
Author

At the end the solution was much easier, I created originally a new Collection of routes for the routes with priority and appended that one to the end of the original Collection, now I changed the order, I append the original routes to the end of the new Collection.

@jderusse
Copy link
Member

What if I set a priority with a negative value (as I can do for events)? Your code will not place my route AFTER the routes without priority?
I think you should not separates routes with priorities, from routes without priorities. (And the code will be simpler

foreach ($this->reader->getMethodAnnotations($method) as $annot) {
    if ($annot instanceof $this->routeAnnotationClass) {
        if (null === $priority = $annot->getOption('priority')) {
            $priority = 0;
        }
        $this->routesWithPriority[$priority][] = array($annot, $globals, $class, $method);
    }
}

@jderusse
Copy link
Member

or simpler $priority = (int) $annot->getOption('priority');

@ghostika
Copy link
Author

You are totally right Jeremy, I changed it as you suggested.

}
}
}

$collection = $this->addPriorityRoutes($collection);

Copy link
Member

Choose a reason for hiding this comment

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

you must reset the state of the loader here

Copy link
Author

Choose a reason for hiding this comment

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

I looked for a way for that, but at the moment RouteCollection don't have such function, and if it's called in a directoryLoader, the named route will be removed and always appended to the end of the array, here in priority order. Or what do you mean by reseting?

Copy link
Member

Choose a reason for hiding this comment

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

$routesWithPriority is making the loader stateful. This means that if you import the routes from several classes explicitly, the second import will still see the priority routes of the first import, potentially creating weird issues

Copy link
Author

Choose a reason for hiding this comment

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

Oh, I see what you mean. The original goal was that if you import the routes for the Controller directory, you can set up priority between the files.

Copy link
Member

Choose a reason for hiding this comment

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

see my comment in the main discussion. This "feature" makes it very easy to break lots of things

@stof
Copy link
Member

stof commented Aug 26, 2014

I don't like the way the priority are working currently. Currently, the order of routes depends on the order of imports, and on the order inside the imported resource. This priority creates a new way for routes loaded through annotations, with interactions between several imported resources but not with resources from other formats.
I see 2 consistent solutions:

  • support the priority everywhere. It would be added as a route option and the sorting would be done at the end. This is bad IMO as it makes it far too complex to understand the final ordering, and removes control on the ordering from the app (as a bundle could decide to use a higher priority). I'm -1 on this approach
  • consider that the priority is only about ordering the routes loaded by annotations in a given class (because contrary to YAML or XML, it is hard to control the order in which annotated methods are found when iterating).

I would vote for the second approach. And the implementation would be easy:

  • revert any change done in the AnnotationFileLoader and the AnnotationDirectoryLoader
  • change the class member $routesWithPriority to a local variable in the AnnotationClassLoader::load method (which solves the issue with the loader being stateful btw)

@stof
Copy link
Member

stof commented Aug 26, 2014

hmm, actually, the related ticket talks about controlling the order of files. This is much more complex (and is exactly the reason why the DirectoryLoader has been rejected from core)

@ghostika
Copy link
Author

Yes, the original was that, but then if it's only for the directory, then it would be also a bit confusing to use it only there and why not in everywhere for annotations. But I also understand your problem here.

@stof
Copy link
Member

stof commented Aug 26, 2014

Well, the issue is that ordering controllers compared to each other requires a different way to sort them, handled by the AnnotationDirectoryLoader, and sorting the controllers compared to each other only, not allowing to mix routes from each controller

@ghostika
Copy link
Author

So I should implement it like I compare them only in the DirectoryLoader right?

@stof
Copy link
Member

stof commented Aug 26, 2014

@ghostika if you want to solve the ordering of controllers, what should be found is a way to sort the different sub-collections loaded in the AnnotationDirectoryLoader. I don't know how we should define the priority then

@fabpot fabpot added the Routing label Sep 5, 2014
@fabpot
Copy link
Member

fabpot commented Oct 2, 2015

Closing this stale PR as the current implementation is not desirable.

@cpaulsen1812
Copy link

cherry picked, nice quick solutation still waiting for this feature in major symfony releases ... ;-) majbe not desirable for the team but desired from the people use symfony ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants