FastRoute
FastRoute copied to clipboard
RFC: Suggested changes for a possible 2.0 version
2.0 Changes and feature proposal
Preamble
@nikic I would love to hear your opinion on this because I really don't see that anyone is maintaining anything here and I don't want to waste time and effort to end up with a PR that might get discussed and merged in a very distant future. If somebody at all bothers to ever check the issues and PRs.
Nothing of the text below is set in stone, I'm just trying to come up with some solutions and ideas that are open to discussion, hence the RFC in the title.
Data Generator
While looking at this https://github.com/nikic/FastRoute/issues/198 issue I figured out that the only difference between all the generators is in fact the chunk processing. Because of this I would propose this change:
- Rename the RegexBasedAbstract to RegexGenerator
- Add GeneratorInterface
- Refactor all the other classes extending RegexBasedAbstract into ChunkProcessors
- Add a ChunkProcessorInterface
- Pass the processor to the Generator
The reason for that change is that all the generators are the same, just the chunk processing is different for each of them. The structure of the code should reflect this as well.
Routes
- Add a RouteInterface
- Add setName(string $name), setTemplate(string $template), reverse(array $args)
- Discuss if routes should be immutable objects (if yes change set* to with*)
RouteCollector
- Rename the RouteCollector to RouteCollection
- Add a RouteCollectionInterface https://github.com/nikic/FastRoute/issues/209
Add a route object factory
Adding a route factory will allow us to change the route object creation through injection a differnet factory to the constructor of the collection.
Named routes and reverse routing
// This will internally call the factory and return the route object
$collection->addRoute($method, $route, $handler)->setName('name')->setTemplate('/users/{id}');
$url = $collection->getRoute('name')->reverse(['id' => 'foo']);
echo $url; // Echos '/users/foo'
// Or shorten it to this, but this, calling the above internally
// but this would make the collection more than a collection, IMHO
$url = $collection->reverse('name', ['id' => 'foo']);
Having names allows us to get routes by their name. The collection will keep a list of all the route objects. Either iterate over them or used an index array and get them like $this->routes[$name]) if they exist. Should be faster than iterating.
The template (you're welcome to suggest better names) is used to do a simple str_replace() on the template with array keys provided by the array passed to reverse().
Honestly I see no easy and good way to deconstruct the regex and reverse the routing, but I'm no regex-wizard. If there actually is a good solution, that doesn't require a ton of code and complexity let's discuss it. The suggested solutions is dead simple. Of course you'll need to define it but this is IMHO less tragic than adding a lot complex code. Further improvements on this could be made in the next major release. But maybe the problem could be worked around by implementing a type of object that takes care of the reverse routing logic and consumes a route object for that purpose.
Maybe it is possible to use the regex string if names are used: '/{junk_variable:(?i)user}'
. The examle is taken from https://github.com/nikic/FastRoute/issues/210. In theory it could be possible to replace the matches with the data from the array? But this would only work if names are made mandatory for this feature. So you'll have to add them to the regex.
Dispatcher
Return a result object instead the not really intutive and expressive array https://github.com/nikic/FastRoute/issues/171.
For backward compatibility the object could implement \ArrayAccess but I don't like the idea.
$result = $dispatcher->resolve($httpMethod, $uri);
if ($result->routeMatched()) {
$route = $result->route(); // get the route object
} else {
if ($result->isNotAllowed()) {
/*...*/
}
if ($result->hasRouteNotFound()) {
/*...*/
}
}
Alternativly simply return a RouteInteface or null.
$route = $dispatcher->resolve($httpMethod, $uri);
if ($route === null) {
/*...*/
}
I'm OK with both ways, but I personally don't see the value in knowing if a route was not found or is not allowed. Not allowed in this case is just another term for "Yea, we have that route but not for your HTTP verb". Maybe there are some common use cases I can't think of right now that are worth the additional conditions?
Getting back the route as a result seems to be relativly easy by adding it to the array structure that is generated by the generators. I already have a working example of this.
This has been implemented in https://github.com/nikic/FastRoute/pull/222
I think a lot of APIs rely on the 405 - method not allowed status code as it can be frequent developpers use the wrong protocol.
For a classic HTML response, I see it as a benefit for logging it and informing developers that a wrong route has been used (might help debugging). Otherwise, I think we all agree the end user should not know what is a 405 at all (and in this case, I would send the same error message wether it is a 405 or a 500 imho, as the resolution is in the developers hand).
So I would be for the backward compatible version of the dispatcher.
By the way, thank you for the time you spent on the issue, I mostly agree on anything you propose.
I've opened a PR https://github.com/nikic/FastRoute/pull/223 and hope that @nikic is going to say something about it.
@burzum it seems it's been more than 12 months since this was posted and I myself am not sure if and when there will be a new release for this project. As I mentioned yesterday in #246 in a few months it will be 4 years since the last release, which was 1.3.0 in February of 2018.
Have you considered a fork, like burzum/FastRoute2 where you continue developing this project on your own? I am sure you've already read that @nikic is going to focus on different challenges in the future, and this project doesn't seem to be a priority.
Sure, I can do that. But I would prefer if everything gets integrated here but I have no time and interest in long waiting times and never ending discussions. If @lcobucci would be less passive this could be done more or less quickly. He should be able to integrate my changes with the changes he wants to make more or less quickly. You can also take my forks branch and create your own official Fastroute 2 based on the already done work, if you like. :) We can also improve it together but my time and mostly patience with open source is limited these days. After May 2022 I might be able to spend more time on it.
Meanwhile I investigated other router libraries as well. mezon/router looks good, the Spiral framework in general and its router as well. I wouldn't force myself to use an unmaintained library with missing (if you need them) features. You can consider it as third option or also check league/router, which is built on top of Fastroute.
I've closed this issue because I see no maintenance or development being done by the mainter of this package and I honestly gave up on making progress here.
@burzum I'll be glad if have a look at my router, Ertuo. I've taken a different approach than regular expressions, and I am not using conventional route definitions. There is still work to be done and polish further, but the results so far are great: it's 10x to 22x faster than the best options for both Symfony Routing and FastRoute.
@kktsvetkov I just looked at it briefly because I'm busy with preparations for the evening, this looks very interesting! 👍 I'll read it until Monday. I'm looking for a modern, extendible router that is well written and returns actual result objects instead of weird arrays and is dependency free. Performance alone is not my primary concern. The Smyfony router is great but I try to avoid Symfony and also packages with Symfony dependencies because slowly but steady you end up with the whole framework in your vendor folder but not using it. 😄 I really like to go for KISS and only add functionality when I need it. Worst case is that I have to switch a library but thats easy the way my app is written.
Have a great evening and a happy new year!