nette/application: Modular Router

4 years ago

ales.kafka
Member | 34
+
0
-

Reasons and current status of RFC

Nette Route is one of the best features there is and I really like it from the beginning. It's easy, powerful and clean. I've always found it good enough to handle all I wanted. But after working on some big applications and also creating some light website without full Nette Framework (just Latte, DI and Http), I'm missing bigger modularity.

First example is slug in URL. It's really hard to find suitable way, how to handle such requests with all requisites. And it's tip of the iceberg. There are other cases that I would like to wrap into reusable modules that interact with presenter as little as possible. Likes of:

  1. /article/2015/02/01/title-as-slug-in-url–12456.html
  2. URL that contains signature created from other parts
  3. wrap params into encrypted and base64 encoded string
  4. encode auto_incremented ids into form, where id cannot be easily interpreted

Maybe I could come up with others, but you know what I mean by now.

I would like to point out, that this is not final proposal, just a work-in-progress. I didn't investigate how it could be implemented, only that proposed API can be implemented without BC. I also won't have time to work on the code until summer, but I still think it's very good idea worth sharing right away.

Current behaviour

Nette Router is really simple and outlines didn't change since the beginning. Configuration is done by string that carries information about parts of URL. Whether they are optional, allowed characters, mapping into arguments, etc. Router match URL if it is matched by internal regular expression from start to finish. It's probably more difficult than this, but let's not bother with details.

Proposed behaviour

Everything stays the same, but configuration can be separated into arbitrary number of modules. In final URL, these modules are compulsory separated by slash.

It took me while to come up with API that would really enhance Nette Router and still kept it simple. Let's see example of definition of route for aforementioned case 1.

We want to choose presenter (in this case article) and then apply module that provides functionality of matching and constructing of URL such as this 2015/02/01/title-as-slug-in-url--12456.html given article entity as param. Also we would like to have default action and comment action as well.

Let's build route to cover this example.

$router[] = new Route('/<presenter article>', new RouterModule/Article, '[<action=default>]', array(...));

And that would be it. Lovely part of this API is, that thanks to PHP 5.4 array optional module stays the same, you would just wraps it into array like this: [new RouteModule/Article].

In terms of implementation, RouterModule/Article would have to implement interface Nette\Application\IRouterModule. This interface would be very similar to Nette\Application\IRouter, but with some alternations. Really sketchy draft of the interface:

namespace Nette\Application;

use Nette;


/**
 * Router module.
 */
interface IRouterModule
{

    /**
     * @param Http\IRequest
     * @param string $path Remaining part of path that is being matched by IRouter.
     * @return ModuleRequest|NULL returns instance of ModuleRequest if it matched part of URL or NULL otherwise
     */
    function match(Nette\Http\IRequest $httpRequest, $path);

    /**
     * Constructs part of URL
     * @return string|NULL
     */
    function constructUrl(Request $appRequest, Nette\Http\Url $refUrl);

}

Mentioned Nette\Application\ModuleRequest needs some serious thoughts how it would be implemented. Since it needs to signalize how big chunk of path has been matched and also provide parameters for final Nette\Application\Request in case whole URL is matched by underlying Route.

Things that could be considered in discussion and maybe improve nette application even further.

  1. Could presenter as whole require parameters? Like ArticlePresenter could be only instantiate with $article entity passed to constructor? $article entity would be provided by IRouterModule as one of the resulting parameters.
  2. Could IRouterModule handle response of application? Like #404 when it matches part of the URL, but didn't find appropriate entity?

What are your thoughts about this RFC?

4 years ago

enumag
Member | 2128
+
+1
-

Can you explain why you don't use FILTER_IN and FILTER_OUT callbacks?

4 years ago

ales.kafka
Member | 34
+
0
-

enumag wrote:

Can you explain why you don't use FILTER_IN and FILTER_OUT callbacks?

I don't like partial solutions. In this case due to configuring Route through multidimensional array. It feels so Nette 0.9.

I would like to have this modules to wrap functionality I use in every project, but that could vary project by project. For example, constructor of mentioned module RouterModule\Article could take first parameter that would specify form of the path. Like this: %Y/%m/%title--%id.html, sometimes I want this: %id/%title.html, etc.

4 years ago

enumag
Member | 2128
+
0
-

Then you just need to wrap Route in way that would create FILTER_* callbacks out of your IRouterModule instance.

Last edited by enumag (2015-02-25 10:23)

4 years ago

ales.kafka
Member | 34
+
0
-

I suppose that could do the trick. That is to implement it in easiest way possible to offer me proposed functionality. But I wanted to share this idea, because it doesn't break the existing code and it would add simple but very powerful concept to Nette.