We all know that our FrontendNodeRoutePartHandler needs some love, but I want to go one step further and refactor it in a way that makes routing to nodes much more flexible.
I have the following ideas:
Separate the uri resolving out to a separate class with interface. This would basically have two public methods, one for getting a node from a uriPath + Context and one to get an uriPath from a Node. This allows people to plug in any special way to get a node uri they need. They could have rules for special node types etc. etc. Also makes this part easily testable
Separate the resolving of context parameters out, same as above basically just for the context parameters
Allow the NodeConverter (in Neos) to convert from an array with __uriPath and __contextInformation.
With this the routing can really deliver scalar values and the actual resolving happens later on. The benefit would be that we can separate context values and uriPath in the route (I imagine something like {node.context.dimensions}/{node.uriPath}.html ) Which would allow people to append the language to the end of the url for example or even mix sources of dimension values, which I guess will be requested and needed anyway.
separate route for backend. Right now this is a bit of a mess and I think it would be beneficial if we had a separate route for the backend. With splitted responsibilities as described above that should be easily possible, additionally the backend could then even use identifiers if we wanted to.
Those are the high level changes, I have some smaller even more technical details but that is for later then.
I think it would benefit the flexibility for integrators and foster the saying of @dimaip to be a
dev friendly tool, with which you can build user-friendly solutions
Right now, if you want anything custom in routing, you need to write custom FrontendNodeRoutePartHandler. If you do, you’ll be cursed with keeping up to changes that happen in Neos, and it would easily just break on you after some non-obvious change.
Are more modular and composable experience would be both beneficial for the integrators and for the ease of development.
Custom URLs just came up again this week and I’m trying to come up with a solution next week. I tried to wrap my head around this topic for about an hour now and there are a few things that make me worry, since I don’t want to end up with 1000 custom solutions I have to maintain or like @dimaip said break for some reason after an update.
Will try to write my custom route part handler and report what problems I ran into. I would also like to help in rewriting the core components necessary to make this more flexible, but since I need a solution rather quickly I can’t wait until the core updates are merged and start with my custom solution for now. Hopefully I can throw everything away after the big update
small update:
Today I created my own RoutePartHandler and the routing part itself works, but of course the “normal routes” aren’t affected by this. So stuff like Packages/Application/TYPO3.Neos/Classes/TYPO3/Neos/TypoScript/NodeUriImplementation.php is still using the standard URL scheme.
Any ideas how to “gracefully” overrule this behaviour? I’m thinking AOP, but I’m pretty sure that is not the best way.
You are right, AOP is not a right way to do it at all. IMO, rather than overruling things, we should break up all of our routing logic into small blocks, and be able to produce custom behaviors from composition of those blocks. However, I’m not really into this topic, so can’t help with it myself, have a limited time and a lot of other things I promised to do
Could you quickly elaborate on the requirements you had on the custom URLs?
I guess the {node1.uriPath}/{node2.uriPath}/...-part is out of question. So it will be mostly about the position/format of dimension values I suppose!?
I think it would be a good idea to iron out the current implementation and to cover the most required requirements out of the box.
I just had a phone call with Christian and I think we agreed on a general direction this could go without having to rework too much.
PS: It feels weird that the node is already resolved in the RoutePartHandler and then again in the TypeConverter. But this happens only for the first request and I think it’s required in order to partially match routes like {node}/{pluginParams}.html
the current requirement would primarily give the user more freedom when it comes to URL structure.
So the pattern would look like en/myCustomUrl instead of en/subpage/anothersubpage/myCustomUrl. The SEO department came up with that idea so they could easily add keywords to the uriPathSegment, without spamming the URL or making the final URL too long.
In my current prototype the user has an “override URL path segment” for every page. If filled out the structure will flatten to dimensionValue/overrideValue
The current position of the dimension values is not an issue (at least for the moment)
Setting up my custom route part handler was quite easy, so the matching (or route) part is working as expected. While it’s still a prototype I feel pretty much dependent on the current code structure because what I basically did was extending the existing FrontendNodeRoutePartHandler for my custom route part handler to work. Maybe I can find a better way around this once I got everything else working.
Looking at the topic again right now. My currently unresolved issues are:
- Today I prototyped a little with the resolve methods(@aertmann gave me a hint in this direction in the slack channel). For some reason the resolve methods in my custom Uri part handler are never called. I studied the resolve methods within Packages/Application/TYPO3.Neos/Classes/TYPO3/Neos/Routing/FrontendNodeRoutePartHandler.php and would like to implement my own, so Neos would prefer my short URLs in stuff like navigation and canonical links etc.
(optional) Even if my custom route “wins” against the neos standard route is still valid. So both, the long and the short URL would lead to the same site. That could be unwanted, but since we have canonical tags google would not assume it was duplicate content so hopefully nothing bad happens
While debugging I noticed that the “matching methods” in my custom route part handler are called twice. Not an issue, just curious how that came to be.
Great to see that others are interested in this topic too! Maybe we can figure something out
UPDATE: I got the resolve part mentioned above working (was a stupid mistake… combination of caching and wrong route order). So my hacky prototype (or “proof of concept” or whatever you want to call it) seems to be working. Only spend a couple of minutes testing it yet, but looking into it right now. Even things like the sitemap are working as expected.
Thanks for the detailed response.
To recap, I think those are the features that we would need in order to have a flexible routing in Neos:
More flexible dimension extraction
Examples:
en/foo/bar
This is the default behavior
foo/bar/en
english/foo/bar
foo/bar/english
This is not yet possible, but could be achieved by some options for the FrontendNodeRoutePartHandler (or possibly some setting so that this can be changed globally).
In theory dimensions could even be determined completely independently from the request path (e.g. from some HTTP header or IP-lookup).
For the latter to work a HTTP Component would be needed that maps (and possibly strips off) dimension values… And one would probably always redirect to a path that includes the dimension anyways – so this is beyond the Routing Framework
Partial matching
Currently we need a static route part to separate node and other uri segments (i.e. plugin parameters).
So while this is possible today:
uriPattern: '{node}/my-plugin/{pluginParams}'
This won’t work yet:
uriPattern: '{node}/{pluginParams}'
The reason being the FrontendNodeRoutePartHandler that is very greedy and swallows everything instead of stripping off only the segments that match.
Aliases
The example you described is currently not possible, e.g.:
baz
where the node path is foo/bar.
For this we could add a new node Property that allows for specifying an “alias” that would overrule the uriPathSegment property and would need to be looked up first… Of course this could lead to conflicts (e.g. if you had a node baz on the root level).
Thanks for the examples, I think those three pretty much cover all the URL structure needs anyone can ever have
Here’s a Gist to my custom prototype for the Alias scenario (did some testing yesterday: matching and resolving nodes work surprisingly well)
As said before currently my route part handler just extends the standard frontend route part handler.
I don’t really know where to go from here:
On the one hand the my implementation is flaky because it heavily depends on the current structure of \TYPO3\Neos\Routing\FrontendNodeRoutePartHandler.
But if I write a completely independent route part handler I would basically rewrite many things that FrontendNodeRoutePartHandler already covers and that doesn’t feel right either.
So what is the best solution for now, until we clean up FrontendNodeRoutePartHandler and make node routing more flexible?
This is something that needs to be checked in the backend when saving the node. I see what I can come up with.
That sounds very reasonable and would be the way to go I think. So we take apart the current FrontendNodeRoutePartHandler and divide the functionality into smaller parts so they can be replaced with custom solutions more easily. Or we even deliver some new routing options out of the box.
One more requirement that I have, is to be able to choose dimensions based on subdomains, e.g. psmb.ru -> Russian, en.psmb.ru -> English.
Or even based on domains: psmb.ru -> Russian, psmb.com -> English.
I believe your approach is currently the best way to do this unfortunately, extending the existing handler and hook in your functionality:
class FrontendNodeRoutePartHandler extends FrontendNodeRoutePartHandler
{
protected function matchValue($requestPath) {
// your custom logic to turn "foo" into "/sites/mysitecom/some/path@user-xyz"
else return parent::matchValue($requestPath);
}
protected function resolveValue($node) {
// your custom logic to turn "/sites/mysitecom/some/path@user-xyz" or <SomeNode> into "foo"
else return parent::resolveValue($requestPath);
}
}
Christian is currently working on centralizing the logic of the handler. Afterwards it’s hopefully only a very slim facade that won’t need any extension (thus making the FrontendNodeRoutePartHandlerInterface obsolete). Instead the part that converts nodes and request paths might get some configuration options or ways to hook-in your custom logic more easily.
Right! I forgot to mention that one, but it’s mainly what I meant by “dimensions could even be determined completely independently from the request path”.
But support for domains is a long requested feature and sometimes™ we will get around implementing it. See Flow Routes Domain/Subdomain for more on this
This is a package that I wrote last year to have a quick method for re-mapping parts of routes in Neos by looking at the generated Uris. So, incoming Uris are manipulated before they are interpreted by the internal routing - and the output of UriBuilder is manipulated with the same mechanism. The resulting Routing logic is not in tune with TYPO3CR, but allows for manipulation at a higher level.
Maybe this helps or at least gives you some inspiration