Intro
During our sprint we discussed issues and potential solutions with our current routing and MVC implementations
Problem space
The routing and MVC framework are two of the oldest components of Flow.
While they have been a solid foundation for years, they come with a couple of issues, for example:
- The routing implementation is tightly coupled to MVC but sometimes it makes sense to just route HTTP requests to a PHP function vice versa
- It’s not easy to hook into the routing configuration (e.g. provide routes via PHP attributes)
- The
ActionController
implementation is quite bloated and it’s not easy to work around the expensive and magic mapping of arguments
Solution space
Some ideas to improve the situation without re-inventing the wheel (and introducing needless breaking changes).
Disclaimer: These are some ideas I take away from our session this morning. Feel free to discuss/correct me
1. Routes provider
Introduce the concept of a routes provider, that could be an interface as simple as:
interface RoutesProvider {
public function getRoutes(): Routes;
}
Extend the mvc.routes
settings by a provider
option.
A complete merged settings.yaml could look like this:
Neos:
Flow:
mvc:
routes:
'Some.PackageA': true
'Some.PackageB':
position: 'before Some.PackageA'
'Neos.Neos':
variables:
'defaultUriSuffix': '.html'
'Some.PackageC':
provider: `Some\PackageC\Routing\Provider`
(only the last line is new, but the example shows how this could be combined with the existing features)
With that, a 3rd party package (or the Flow core) could implement a provider that reads attributes from PHP classes and turns these into corresponding routes
Note The interface should probably also get some method like onRouteChanges(Closure $callback): void
that allows us to flush routing caches automatically upon changes – alternatively some kind of hash that leads to caches being flushed when it changes (if I see it correctly, this functionality is not yet part of the existing related pull request but IMO it should be in order to provide the “Flow feeling”)
2. Simplify controller implementation
We discussed whether to reduce the MVC controller to callable(ServerRequestInterface): ResonseInterface
Flow would still provide the extensible ActionController
that converts the incoming request to an ActionRequest and invokes the corresponding action, but we would allow for much simpler implementations.
For example one that @wbehncke mentioned:
#[SomeCustomAttribute(path: '/api/hello-world')]
final class HttpHandler {
public function __invoke(ServerRequest $request): ResponseInterface {
return method_exists($this, strtolower($request->getMethod()) ? strtolower($request->getMethod()($request) : new Response(405);
}
public function get(ServerRequestInterface $request): ResponseInterface {
return new Response(200, [], 'hello world');
}
public function post(ServerRequestInterface $request): ResponseInterface {
// create some resource
return new Response(201);
}
}
3. Restructure Route
DTO
I don’t think it’s feasible to change the Routes.yaml
syntax (apart from deprecating/removing the notion of defaults.@subpackage
).
But I would suggest to change the Route
DTO so that a configuration of
-
name: 'Some optional name'
uriPattern: 'foo/bar/{dynamic}'
defaults:
'@package': 'Some.Package'
'@controller': 'Foo'
'@action': 'detail'
is represented as:
{"name": "Some optional name", "uriPattern": "foo/bar/{dynamic}", "handler": "Some\Package\Controller\FooController->detail"}
Accordingly, the custom routes provider for the HttpHandler
example above could produce routes like:
[
{"uriPattern": "/api/hello-world", "httpMethods": ["GET", "POST"], "handler": "Some\\Package\\HttpHandler"}
]