Problem/Motivation
According to Symfony2 Routing documentation in http://symfony.com/doc/current/book/routing.html#adding-requirements, the same paths can be declared in different routes using regex requirements to set the matching conditions.
If we write a module declaring this routes in YAML:
my_module.zone:
path: '/news/{zone}'
defaults:
_controller: '\Drupal\my_module\Controller\BasicController::newsByZone'
zone: 'us'
requirements:
_access: 'TRUE'
zone: 'eu|us'
my_module.news:
path: '/news/{title}'
defaults:
_controller: '\Drupal\my_module\Controller\BasicController::newsByTitle'
requirements:
_access: 'TRUE'
The expected behavior when entering /news/eu or /news/us would be the response of newsByZone(), but for some reason I don't understand the response is always returned by newsByTitle even if we change the route declaration order. If am not doing something wrong I think this is a bug.
Comments
Comment #1
dawehnerHere is a short test which tries to explain existing behavior.
Both routes do match /news/eu so one of them will be chosen. Maybe you have to implement some additional class implement RouteFilterInterface to have the exact controller you need.
Comment #2
shivansunfire commentedFrom http://symfony.com/doc/current/book/routing.html#adding-requirements
This is not happening now for some reason. If this is not the criteria to decide the route (earlier routes always win in Symfony2), which crieria is being applied?
Earlier routes always win, except that you declare requirements for the parameters in the first route, so that way the first route will not match and so will do the second route.
I think in the test we shouldn't expect two routes matching the path but only the first route like Symfony does.
Comment #3
dawehnerWell, it finds the first matching route, we don't ensure order when we push and pull routes from the database.
The only problem is that we just sort routes by fit.
Comment #4
Crell commentedI am open to adding another order by column to help with these cases if we can figure out a good one to use. "Order defined in the file" doesn't quite work in Drupal's case as well as it does in fullstack or Silex. :-/
Comment #5
Crell commentedHm, actually, should we instead integrate the presence of regex requirements into calculating the "fitness" of a route? Right now it's just path placeholder based, but we could certainly adjust that if needed. Adding a regex makes a route "more specific", and we order most specific to least specific, so...
Comment #6
shivansunfire commentedI think we should leave fit as it is now, "a numeric representation of how specific the path is", or maybe we should say "how specific the pattern_outline is". Two routes with the "same" path but different parameter names still has the "same" path, equivalent to the same pattern_outline.
Besides, the RouteCompiler::getFit() method does a good job using bitwise operators and binary math. For each different "number_parts" path type there are 2 raised to the power of number_parts, minus 1 fit combinations, depending on the number of parameters and their position. Introducing regex existence checking would raise the number of combinations to 3 raised to the power of number_parts, minus 1, and we would need "ternary math" instead of using bitwise operators, too much complexity.
The query now sorts by fit, and draw is resolved by MySQL sorting the results by the length of the first column: name, which seems weird and random. I think we need a new column.
As a second sort column, we could set another integer that takes into account if the parameter has a requirement for it or not. We could use the same binary approach that in RouteCompiler::getFit(), applying 0 to each bit (path position) where there is a parameter and it hasn't a requirement, and applying 1 otherwise.
We could also add a weight as an option for each route in the .routing.yml, so developers could specify which route has to be considered first. This secondary sorting option would allow much more flexibility defining routes with parameters, and would improve the interaction between modules. The "weight" (or some similar name) column would move the regex parameter requirement as the third sorting option, so if nobody define a weight, the RouteProvider would still returning first the route with the regex parameter. It would be something like "ORDER BY fit DESC, weight, regex_param_rate"
Comment #7
sunThis bug is at least be major, I think.
IIRC, e.g. in #2068471: Normalize Controller/View-listener behavior with a Page object we were not able to rename a route, because the current matching algo ignores other route aspects, so another definition (of the same routing.yml file) suddenly came first.
I first wanted to agree with simply lumping this as a factor into 'fit', but on a second thought, I don't think that's going to be a reliable and maintainable approach → the final fitness would become a "magic" value and we'd also have to lump 'methods' and possibly other factors into that sum.
Comment #8
dawehnerAn alternative could be to introduce a step / use RouteFilters to just sort the returned routes, so more specific ones comes later.
Comment #9
Crell commentedThat's more work along the critical path. If we can put this logic into the provider, I'd rather do that.
Of course, the schema of the router table is an internal detail, not public (since the provider is swappable and we don't promise that it's SQL at all), so we can certainly do things like add a column for "has a regex", "has a method", "has a mime type", etc. Or even just a "number of restrictions" column. That's quite easy for us to do, I'd think, and would let us order by fitness, then the number of specifiers/restrictions.
We could also, on selected backends, bake the regex into the query itself. That's probably not worth doing for the default implementation, though; it just needs to work on all SQL backends, not be fully optimized.
Comment #10
dawehner@crell
So do you have any recommendations how/if we should move this issue forward? It seems indeed like an issue,
but can't we figure out on route compile time whether we need to do regex checking, which should basically no overhead at all? Maybe one function call by default + the calls to the regexes in case somebody decided to use them.
Comment #11
jhedstromHere's an attempt to pick this up. It includes the test, rerolled and amended, from #1. Not sure if this is the appropriate way to do this sorting in the route provider or not.
Comment #16
jhedstromThe patch in #11 breaks certain local action links (eg, 'Add a custom block', etc) for some reason.
Comment #17
jhedstromThe fails were because views-provided routes had more specificity than the routes they replace (eg, node.add_page vs views.content.page_1--the latter had
_methodrequirement).This patch filters out the deprecated
_methodand_schemerequirements before sorting on requirements.Comment #21
tim.plunkettComment #22
kostyashupenkore-rolled with auto merge
Comment #23
andypostComment #26
tim.plunkettStill a bug, but has not proven major over time.
Comment #27
Anonymous (not verified) commented#22 re-roll.
Comment #29
Anonymous (not verified) commentedComment #31
Munavijayalakshmi commentedRerolled the patch.
Comment #32
dawehnerDo you mind filling a follow up so we don't forget about it?
For a better test coverage it feels like it would be better to include access checkers as well, given that they have to be ignored in that counting process.
Comment #35
Anonymous (not verified) commentedForgive me, @dawehner. In those days I was not experienced enough to realize your review :)
Done now.
Comment #37
borisson_#35 fixes the remarks @dawehner had in #32. I don't really understand the routing system well enough to feel comfortable setting this issue to RTBC, but I couldn't find anything that I'd like to see changed.
Comment #47
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.