URL Routing
Routing is a two-way conversion between URL and presenters' actions. Two-way means that we can both determine what presenter URL links to, but also vice versa: generating URL for given action. This article contains:
- how to define routes and create links
- a few notes about SEO redirection
- how are routes debugged
- how to create your own router
Thanks to bidirectional routing you don't have to hardcode URLs into templates, but simply link to presenters' actions and framework generates the url:
<a n:href="Product:detail $productId">product detail</a>
Learn more about creating links.
Routing is a separate application layer. You may even create URLs the tiem a whole application is complete. It's also simple to change routes anytime, while keeping preserving original addresses and automatically redirecting to new variants. So hey, who's got that? :-)
SimpleRouter
Desired URL format is set by so called router, which is usually
defined in bootstrap.php
file. The most plain implementation of router is SimpleRouter.
It is to be used when there's no need for a specific url format, or our server
does not support mod_rewrite.
Addresses look like this:
http://example.com/index.php?presenter=product&action=detail&id=123
The first argument of SimpleRouter constructor is a default presenter action,
ie. action to be executed if we open for example
http://example.com/ without additional params. The declaration
itself in a bootstrap.php file might might look as follows:
($container is a system DI
container)
use Nette\Application\Routers\SimpleRouter;
// defaults to presenter Homepage and action default
$container->router = new SimpleRouter('Homepage:default');
Route: for prettier URLs
More human URLs (though also more cool, prettier and user-friendly) are easier to remember and do help SEO (search engine optimalization). Nette Framework keeps current trends in mind and fully meets developers' desires.
Apache mode mod_rewrite is required and all request
must be handled by index.php file, so the routes definition stays
in one place. See how to enable
mod_rewrite.
Class Route
can create addresses in pretty much any format one can though of. Let's start
with an example generating a pretty URL for action Product:default
with id = 123:
http://example.com/product/detail/123
The following snippet creates a Route object, passing path mask
as a first argument and specifying default action as an array or a string with
second argument.
// action defaults to presenter Homepage and action default
$route = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
This route is usable by for all presenters and actions. Accepts a path such
as /article/edit/10, as well as /catalog/list, because
the id part is wrapped in square brackets, which marks it
optional.
Because other parameters (presenter and action) do
have default value (Homepage and default), they are
optional too. If their value is same as the default one, they are skipped while
URL is generated. Link to Product:default generates only
http://example.com/product and Homepage:default links
to http://example.com/.
Mask can not only describe relative path to web root, but can as well tak absolute path (as long as it beging with a slash) or even absolute URL (if starts with double-slash):
$route = new Route('//<subdomain>.example.com/<presenter>/<action>', '...');
Mask can contain traditional get arguments (query after a question mark). Neither regular expressions nor more complex structures are supported in this part of URL, but be can set what key belongs to which variable:
$route = new Route('<presenter>/<action> ? id=<productId> & cat=<categoryId>',
'Homepage:default');
Route collection
Because we usually define more than one route, we wrap them into a RouteList.
The following code snippet shows an bootstrap.php
example: ($container is a system
DI container)
use Nette\Application\Routers\RouteList,
Nette\Application\Routers\Route;
$router = new RouteList;
$router[] = new Route('article/<id>', 'Article:view');
$router[] = new Route('rss.xml', 'Feed:rss');
$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
$container->addService('router', $router);
// or shorter: $container->router = $router;
Unlike other frameworks, Nette does not require routes to be named.
It's importnat in which order routes are defined as they are tried from top to bottom. The rule of thumb here is that routes are declared from the most specific at the top to the most vague at the bottom. Keep in mind that huge amount of routes can negatively effect application speed, mostly when generating links. It's worth it to keep routes as simple as possible.
If no route is found, an BadRequestException is thrown, which is shown as 404 Not Found to the user.
Default values
Each parameter may have defined default value in the mask:
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
Or utilizing an array:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
));
Validation expressions
Regular expression conditions may be set for route parameters. For example
let's set id to be only numerical, using
[0-9]+ regex:
$route = new Route('<presenter>/<action>[/<id [0-9]+>]',
'Homepage:default');
Default validation expression is [^/]+, that is all
characters but a slash. If parameter is supposed to match those as well, we can
set condition to .+.
Optional sequences
Square brackets denote optional parts of mask. Any part of mask may be set optional, even with parameters:
$route = new Route('[<lang [a-z]{2}>/]<name>', 'Article:view');
// Accepts:
// /en/download => lang => en, name => download
// /download => lang => NULL, name => download
Obviously, if parameter is inside an optional sequence, it's optional too
and defaults to NULL. Sequence should define it's surroundings, in
this case a slash which must follow an parameter, if set. The technique may be
used with optional language subdomains:
$route = new Route('//[<lang=en>.]example.com/<presenter>/<action>',
'Homepage:default');
Sequences may be freely nested and combined:
$route = new Route('[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
'Homepage:default');
// Accepts:
// /cs/hello
// /en-us/hello
// /hello
// /hello/page-12
URL generator tries to keep the URL as short as possible (while unique), so
what can be omitted is not used. That's why index[.html] route
generates /index. This behavior can be inverted by writing an
exclamation mark after the leftmost square bracket that denotes the respective
optional sequence:
// accepts both /hello and /hello.html, generates /hello
$route = new Route('<name>[.html]');
// accepts both /hello and /hello.html, generates /hello.html
$route = new Route('<name>[!.html]');
Optional parameters (ie. parameters having default value) without square brackets do behave as if wrapped like this:
$route = new Route('<presenter=Homepage>/<action=default>/<id=>');
// equals to:
$route = new Route('[<presenter=Homepage>/[<action=default>/[<id>]]]');
If we would like to change how the rightmost slashes are generated, that is
instead of /homepage/ get a /homepage, we can adjust
the route:
$route = new Route('[<presenter=Homepage>[/<action=default>[/<id>]]]');
Foo parameters
Foo parameters are similar to optional sequences, but they are used to match
a regular expression. The following route matches /index,
/index.html, /index.htm and
/index.php:
$route = new Route('index<? \.html?|\.php|>', 'Homepage:default');
It's also possible to explicitly define a string to be used while an address is generated (similar to setting default value to real parameters).
ONE_WAY flag
One way routes are usually used to preserve old URLs functionality, when an
application is rewritten. Flag ONE_WAY marks routes, which are not
used for url generation.
// old URL /product-info?id=123
$router[] = new Route('product-info', 'Product:detail', Route::ONE_WAY);
// new URL /product/123
$router[] = new Route('product/<id>', 'Product:detail');
Additionally the user is redirected to new URL, so search engines won't index your pages twice (see SEO a kanonizace).
HTTPS
Routes can be forced to secured HTTP by SECURED flag.
That's useful for authentication pages, as well as administration etc. HTTPS
must be supported by your hosting/server.
$route = new Route('admin/<presenter>/<action>',
'Admin:default', Route::SECURED);
Transformation and Translation
It's a good practice to write source code in english, but what if you need your application to run in a different environment? Simple routes such as:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => 'Homepage',
'action' => 'default',
'id' => NULL,
);
will generate English URLs, /product/123, /cart, or
/catalog/view etc. If we would like to translate those URLs, we can
use a dictionary. We'd extend the route so:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => array(
Route::VALUE => 'Homepage',
Route::FILTER_TABLE => array(
// translated string in URL => presenter
'produkt' => 'Product',
'kosik' => 'Cart',
'katalog' => 'Catalog',
),
),
'action' => 'default',
'id' => NULL,
);
Multiple keys may link to same presenter. That's how aliases are created. The last value is the canonical one (used for link generation).
Dictionaries may be applied to any parameter. Although it does not work as a filter, if a translation is not found, default value is used.
Besides setting dictionaries as arrays, it's possible to set a callback or a filter:
$route = new Route('<presenter>/<action>/<id>', array(
'presenter' => array(
Route::VALUE => 'Homepage',
Route::FILTER_IN => 'filterInFunc',
Route::FILTER_OUT => 'filterOutFunc',
),
'action' => 'default',
'id' => NULL,
);
Where filterInFunc and filterOutFunc are functions
or methods that converts values between URL and what presenter gets. Each
handles one direction.
Implicit in-filter is function rawurldecode and out-filter is function rawurlencode, which escapes special characters (slashes, spaces, …) for use in the URL.
In certain situations this behavior may be changed, for example let us have a
parameter path, that can also contain slashes, so they're not
converted to %2F:
// accepts http://files.example.com/path/to/my/file
$route = new Route('//files.example.com/<path .+>', array(
'presenter' => 'File',
'action' => 'default',
'path' => array(
Route::VALUE => NULL,
Route::FILTER_IN => NULL,
Route::FILTER_OUT => NULL,
),
));
Routing Debugger
We should keep it no secret that routes may seem to be magical for a while. But you will always appreciate value of Routing Debugger. It's a Debugger bar panel, which gives you a list of all parameters a router got and a list of all defined routers. It's turn on automatically, as long as the application runs in debug mode. It also shows on which presenter and action you are currently on.

SEO and canonization
Framework increases SEO (search engine optimalization) as it prevents
multiple URLs to link to different content (without a proper redirect). If more
than one addresses link to same target (/index and
/index.html), framework choses the first (makes it canonical) and
redirect other to it with HTTP code 301. Thanks to that your page won't have
duplicities on search engines and their rank won't be split.
This whole process is the so called canonication. Default (canonical) url is
the one router generaets, that is first route in collection without
ONE_WAY flag.
Canonization is done by Presenter
and it's switched on by default. You may disable it by setting
$presenter->autoCanonicalize = FALSE.
Ajax and POST requests are not redirected as user would suffer either a data loss, or it would yell no additional SEO value.
Modularity
If we want to add separate modules into our application that have their own
routes, for example a discussion forum, we may define routes elsewhere, possibly
in createRoutes() method:
class Forum
{
static function createRoutes($router, $prefix)
{
$router[] = new Route($prefix . 'index.php', 'Forum:Homepage:default');
$router[] = new Route($prefix . 'admin.php', 'Forum:Admin:default');
...
}
}
“Routing-in” the forum into existing application can be as easy as
calling this method: (bootstrap.php)
$container->addService('router', function() {
$router = new RouteList;
// our routes
...
// adding forum module
Forum::createRoutes($router, '//forum.example.com/');
return $router;
});
Custom router
If these offered routes do not fit your needs, you may create your own router and add it to your router collection. Router is nothing more but an implementation of IRouter with it's two methods:
class MyRouter implements Nette\Application\IRouter
{
function match(Nette\Http\IRequest $httpRequest){ }
function constructUrl(Nette\Application\Request $appRequest, Nette\Http\Url $refUrl) { }
}
Method match does process current $httpRequest (which
offers more than just the URL) into internal Nette\Application\Request
which contains presenter name and it's parameters. If original request could
not be processed, return NULL.
Method constructUrl generates absolute URL from internal
request, possibly utilizing information from $refUrl argument.
Possibilities of custom routers are unlimited, for example it's possible to implement a router based on database records.

Login to submit a comment