Nette Documentation Preview

syntax
Enrutamiento
************

<div class=perex>

El router se ocupa de todo lo relacionado con las direcciones URL, de modo que usted no tenga que pensar en ellas. Le mostraremos:

- cómo configurar el router para que las URL tengan el aspecto que desea
- hablaremos del SEO y de las redirecciones
- y le enseñaremos cómo escribir un router propio

</div>


Las URL más amables para las personas (también llamadas cool o pretty URLs) son más utilizables, se recuerdan mejor y contribuyen positivamente al SEO. Nette lo tiene en cuenta y satisface plenamente las necesidades de los desarrolladores. Puede diseñar para su aplicación exactamente la estructura de URL que quiera. Incluso puede diseñarla cuando la aplicación ya está terminada, porque no requiere ningún cambio en el código ni en las plantillas. Se define de forma elegante en [un único lugar |#Integración], el router, en vez de andar dispersa como anotaciones por todos los presenters.

El router en Nette es excepcional porque es **bidireccional.** Sabe tanto descodificar las URL de las peticiones HTTP como crear enlaces. Por eso desempeña un papel clave en [Nette Application |how-it-works#Nette Application], ya que no solo decide qué presenter y qué acción ejecutarán la petición actual, sino que se usa también para [generar las URL |creating-links] en las plantillas, etc.

El router no se limita, sin embargo, a este uso; puede utilizarlo en aplicaciones donde no se usan presenters en absoluto, para API REST, etc. Más detalles en la sección [#Uso independiente].


Colección de rutas
==================

La forma más agradable de definir la estructura de las direcciones URL de una aplicación la ofrece la clase [api:Nette\Application\Routers\RouteList]. La definición consiste en una lista de las llamadas rutas, es decir, máscaras de direcciones URL y los presenters y acciones asociados, mediante una API sencilla. No hace falta nombrar las rutas de ninguna manera.

```php
$router = new Nette\Application\Routers\RouteList;
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('article/<id>', 'Article:view');
// ...
```

El ejemplo muestra que, si abrimos en el navegador `https://domain.com/rss.xml`, se mostrará el presenter `Feed` con la acción `rss`. Si abrimos `https://domain.com/article/12`, se mostrará el presenter `Article` con la acción `view`, etc. Si no se encuentra ninguna ruta adecuada, Nette Application reacciona lanzando la excepción [BadRequestException |api:Nette\Application\BadRequestException], que se muestra al usuario como una página de error 404 Not Found.


Orden de las rutas
------------------

El **orden** en el que se indican las distintas rutas es absolutamente **crucial**, porque se evalúan sucesivamente de arriba abajo. La regla es que declaramos las rutas **de las específicas a las generales**:

```php
// MAL: 'rss.xml' lo captura la primera ruta y entiende esa cadena como <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// BIEN
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');
```

Las rutas se evalúan de arriba abajo también al generar los enlaces:

```php
// MAL: el enlace a 'Feed:rss' se genera como 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// BIEN
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
```

No le vamos a ocultar que montar las rutas correctamente requiere cierta destreza. Hasta que le coja el truco, el [panel de enrutamiento |#Depuración del router] le será de gran ayuda.


Máscara y parámetros
--------------------

La máscara describe la ruta relativa desde el directorio raíz del sitio web. La máscara más sencilla es una URL estática:

```php
$router->addRoute('products', 'Products:default');
```

A menudo las máscaras contienen los llamados **parámetros**. Se escriben entre corchetes angulares (p. ej. `<year>`) y se pasan al presenter de destino, por ejemplo al método `renderShow(int $year)` o al parámetro persistente `$year`:

```php
$router->addRoute('chronicle/<year>', 'History:show');
```

El ejemplo muestra que, si abrimos en el navegador `https://example.com/chronicle/2020`, se mostrará el presenter `History` con la acción `show` y el parámetro `year: 2020`.

A los parámetros les podemos indicar un valor por defecto directamente en la máscara, con lo que se vuelven opcionales:

```php
$router->addRoute('chronicle/<year=2020>', 'History:show');
```

La ruta aceptará ahora también la URL `https://example.com/chronicle/`, que mostrará de nuevo `History:show` con el parámetro `year: 2020`.

Naturalmente, el nombre del presenter y de la acción también pueden ser parámetros. Por ejemplo:

```php
$router->addRoute('<presenter>/<action>', 'Home:default');
```

La ruta indicada acepta, por ejemplo, URL con la forma `/article/edit` o `/catalog/list` y las entiende como los presenters y acciones `Article:edit` y `Catalog:list`, respectivamente.

Al mismo tiempo da a los parámetros `presenter` y `action` los valores por defecto `Home` y `default`, con lo que también son opcionales. Así, la ruta acepta también una URL como `/article` y la entiende como `Article:default`. O al revés: un enlace a `Product:default` genera la ruta `/product` y un enlace al predeterminado `Home:default` genera la ruta `/`.

La máscara puede describir no solo la ruta relativa desde el directorio raíz del sitio web, sino también una ruta absoluta si empieza por una barra, o incluso la URL absoluta entera si empieza por dos barras:

```php
// relativa al document root
$router->addRoute('<presenter>/<action>', /* ... */);

// ruta absoluta (relativa al dominio)
$router->addRoute('/<presenter>/<action>', /* ... */);

// URL absoluta incluido el dominio (relativa al esquema)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// URL absoluta incluido el esquema
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);
```


Expresiones de validación
-------------------------

A cada parámetro se le puede indicar una condición de validación mediante una [expresión regular|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por ejemplo, al parámetro `id` le indicamos con la expresión regular `\d+` que solo puede contener cifras:

```php
$router->addRoute('<presenter>/<action>[/<id \d+>]', /* ... */);
```

La expresión regular predeterminada para todos los parámetros es `[^/]+`, es decir, todo menos la barra. Si un parámetro debe aceptar también barras, indicamos la expresión `.+`:

```php
// acepta https://example.com/a/b/c, path será 'a/b/c'
$router->addRoute('<path .+>', /* ... */);
```


Secuencias opcionales
---------------------

En la máscara se pueden marcar partes opcionales con corchetes. Cualquier parte de la máscara puede ser opcional y puede contener parámetros:

```php
$router->addRoute('[<lang [a-z]{2}>/]<name>', /* ... */);

// Acepta las rutas:
//    /en/download  => lang => en, name => download
//    /download     => lang => null, name => download
```

Cuando un parámetro forma parte de una secuencia opcional, se vuelve naturalmente opcional también. Si no tiene indicado un valor por defecto, será null.

Las partes opcionales pueden estar también en el dominio:

```php
$router->addRoute('//[<lang=en>.]example.com/<presenter>/<action>', /* ... */);
```

Las secuencias se pueden anidar y combinar libremente:

```php
$router->addRoute(
	'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
	'Home:default',
);

// Acepta las rutas:
// 	/en/hello
// 	/en-us/hello
// 	/hello
// 	/hello/page-12
```

Al generar las URL se busca la variante más corta, así que todo lo que se pueda omitir se omite. Por eso, por ejemplo, la ruta `index[.html]` genera la ruta `/index`. Este comportamiento se puede invertir escribiendo un signo de exclamación después del corchete izquierdo:

```php
// acepta /hello y /hello.html, genera /hello
$router->addRoute('<name>[.html]', /* ... */);

// acepta /hello y /hello.html, genera /hello.html
$router->addRoute('<name>[!.html]', /* ... */);
```

Los parámetros opcionales (es decir, los que tienen un valor por defecto) sin corchetes se comportan en esencia como si estuvieran encerrados de la siguiente manera:

```php
$router->addRoute('<presenter=Home>/<action=default>/<id=>', /* ... */);

// corresponde a esto:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);
```

Si queremos influir en el comportamiento de la barra final, para que se genere por ejemplo `/home` en lugar de `/home/`, se puede conseguir así:

```php
$router->addRoute('[<presenter=Home>[/<action=default>[/<id>]]]', /* ... */);
```


Comodines
---------

En la máscara de una URL absoluta podemos usar los siguientes comodines para no tener que escribir en la máscara, por ejemplo, el dominio, que puede diferir entre el entorno de desarrollo y el de producción:

- `%tld%` = dominio de primer nivel, p. ej. `com` u `org`
- `%sld%` = dominio de segundo nivel, p. ej. `example`
- `%domain%` = dominio sin subdominios, p. ej. `example.com`
- `%host%` = host entero, p. ej. `www.example.com`
- `%basePath%` = ruta al directorio raíz

```php
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action>', /* ... */);
```


Notación avanzada
-----------------

El destino de la ruta, que se escribe normalmente con el formato `Presenter:action`, se puede escribir también mediante un array que define los distintos parámetros y sus valores por defecto:

```php
$router->addRoute('<presenter>/<action>[/<id \d+>]', [
	'presenter' => 'Home',
	'action' => 'default',
]);
```

Para una especificación más detallada se puede usar una forma aún más extendida, en la que, además de los valores por defecto, podemos establecer otras propiedades de los parámetros, como una expresión regular de validación (véase el parámetro `id`):

```php
use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>[/<id>]', [
	'presenter' => [
		Route::Value => 'Home',
	],
	'action' => [
		Route::Value => 'default',
	],
	'id' => [
		Route::Pattern => '\d+',
	],
]);
```

Es importante señalar que, si los parámetros definidos en el array no aparecen en la máscara de la ruta, sus valores no se pueden cambiar, ni siquiera con los parámetros de consulta indicados tras el signo de interrogación en la URL.

Esto es útil para los **parámetros fijos**: dar a una página concreta una URL corta y fácil de recordar. Por ejemplo, para que `/tos` abra siempre `Article:view` con `id: 123`:

```php
$router->addRoute('tos', [
	'presenter' => 'Article',
	'action' => 'view',
	'id' => 123,
]);
```


Filtros y traducciones
----------------------

El código fuente de la aplicación lo escribimos en inglés, pero si el sitio web debe tener las URL en checo, un enrutamiento sencillo como:

```php
$router->addRoute('<presenter>/<action>', 'Home:default');
```

generará URL en inglés, como `/product/123` o `/cart`. Si queremos que los presenters y las acciones en la URL estén representados por palabras checas (p. ej. `/produkt/123` o `/kosik`), podemos usar un diccionario de traducción. Para escribirlo necesitamos ya la variante "más locuaz" del segundo parámetro:

```php
use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// cadena en la URL => presenter
			'produkt' => 'Product',
			'kosik' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'seznam' => 'list',
		],
	],
]);
```

Varias claves del diccionario de traducción pueden llevar al mismo presenter. Así se le crean distintos alias. La última clave se considera la variante canónica (es decir, la que aparecerá en la URL generada).

La tabla de traducción se puede usar de esta manera para cualquier parámetro. Si la traducción no existe, se toma el valor original. Podemos cambiar este comportamiento añadiendo `Route::FilterStrict => true` y la ruta rechazará entonces la URL si el valor no está en el diccionario.

Además del diccionario de traducción en forma de array, se pueden emplear funciones de traducción propias.

```php
use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>/<id>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterIn => function (string $s): string { /* ... */ },
		Route::FilterOut => function (string $s): string { /* ... */ },
	],
	'action' => 'default',
	'id' => null,
]);
```

La función `Route::FilterIn` convierte entre el parámetro de la URL y la cadena que se pasa después al presenter; la función `FilterOut` se encarga de la conversión en sentido contrario.

Los parámetros `presenter`, `action` y `module` ya tienen filtros predefinidos que convierten entre el estilo PascalCase o camelCase y el kebab-case usado en la URL. El valor por defecto de los parámetros se escribe ya en la forma en la que se pasa a la aplicación (PascalCase para presenter y module, camelCase para action), así que, por ejemplo, en el caso del presenter escribimos `<presenter=ProductEdit>`, no `<presenter=product-edit>`.


Filtros generales
-----------------

Además de los filtros destinados a parámetros concretos, podemos definir también filtros generales, que reciben un array asociativo de todos los parámetros, que pueden modificar de cualquier manera y devolver después. Los filtros generales se definen bajo la clave vacía.

```php
use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => 'Home',
	'action' => 'default',
	'' => [
		Route::FilterIn => function (array $params): array { /* ... */ },
		Route::FilterOut => function (array $params): array { /* ... */ },
	],
]);
```

Los filtros generales ofrecen la posibilidad de modificar el comportamiento de la ruta de absolutamente cualquier manera. Podemos usarlos, por ejemplo, para modificar unos parámetros a partir de otros. Por ejemplo, para traducir `<presenter>` y `<action>` según el valor actual del parámetro `<lang>`.

Si un parámetro tiene definido su propio filtro y existe además un filtro general, el `FilterIn` propio se ejecuta antes que el general y, al revés, el `FilterOut` general se ejecuta antes que el propio. Así, dentro del filtro general los valores de los parámetros `presenter` y `action` están escritos en estilo PascalCase o camelCase, respectivamente.

Véase [URLs amigables con slugs |best-practices:pretty-urls] para un uso práctico de estos filtros: generar URL amigables para el SEO como `/article/123-how-to-bake-bread` sin modificar ninguna plantilla.


Bandera OneWay
--------------

Las rutas de un solo sentido se usan para mantener la funcionalidad de URL antiguas que la aplicación ya no genera, pero que sigue aceptando. Las marcamos con la bandera `OneWay`:

```php
// URL antigua /product-info?id=123
$router->addRoute('product-info', 'Product:detail', oneWay: true);
// URL nueva /product/123
$router->addRoute('product/<id>', 'Product:detail');
```

Al acceder a la URL antigua, el presenter redirige automáticamente a la nueva, de modo que los buscadores no indexen estas páginas dos veces (véase [#SEO y canonización]).


Enrutamiento dinámico con callbacks
-----------------------------------

El enrutamiento dinámico con callbacks le permite asignar directamente a las rutas funciones (callbacks) que se ejecutan al visitar la ruta dada. Esta funcionalidad flexible le permite crear rápida y eficazmente distintos endpoints para su aplicación:

```php
$router->addRoute('test', function () {
	echo 'You are at the /test address';
});
```

En la máscara puede definir también parámetros, que se pasan automáticamente a su callback:

```php
$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Welcome to the Czech version of our website!',
		'en' => 'Welcome to the English version of our website!',
	};
});
```

Además de los parámetros de la máscara, el callback puede recibir también servicios del contenedor DI. Se pasan según el tipo del parámetro. Además, el parámetro `$presenter` recibe una instancia de [MicroPresenter |api:NetteModule\MicroPresenter], que procesa la ruta:

```php
$router->addRoute('<lang cs|en>', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) {
	// ...
});
```


Módulos
-------

Si tenemos varias rutas que pertenecen a un [módulo |directory-structure#Presenters y plantillas] común, usamos `withModule()`. El módulo indicado se antepone automáticamente al presenter de cada ruta del grupo y desaparece por completo de la URL:

```php
$router = new RouteList;
$router->withModule('Forum') // las rutas siguientes forman parte del módulo Forum
	->addRoute('rss', 'Feed:rss') // el presenter será Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // las rutas siguientes forman parte del módulo Forum:Admin
		->addRoute('sign:in', 'Sign:in');
```

Una alternativa es el parámetro `module`, que igualmente fija un módulo y lo mantiene fuera de la URL:

```php
// la URL manage/dashboard/default se mapea al presenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);
```

El nombre de un presenter está completo solo junto con su módulo, p. ej. `Front:Admin:ProductList`. Siempre que ese nombre completo acaba en un parámetro de la URL, el router lo codifica con dos reglas sencillas: cada dos puntos `:` (el separador de módulos) se convierte en un **punto** y cada límite entre palabras de un nombre en PascalCase se convierte en un **guion**. Así, `Front:Admin:ProductList` se escribe en la URL como `front.admin.product-list` y de la misma manera se descodifica de vuelta. Precisamente por eso una aplicación modular sin ninguna de las herramientas anteriores genera URL llenas de puntos.

Tanto `withModule()` como el parámetro `module` evitan esto justamente porque recortan el prefijo de módulo conocido del nombre del presenter antes de que llegue a la URL: como el módulo es una constante, no hace falta codificarlo de ninguna manera.

A veces queremos que el módulo mismo varíe y aparezca en la URL, así que recurrimos a `<module>` directamente en la máscara. Cuidado, sin embargo, con un detalle esencial: **`<module>` absorbe toda la ruta de módulos**, todo hasta los últimos dos puntos del nombre del presenter. En el presenter `Shop:Admin:Product` eso es el módulo `Shop:Admin` y el presenter `Product`, y como los dos puntos se convierten en puntos, obtenemos:


Subdominios
-----------

Las colecciones de rutas se pueden dividir según los subdominios:

```php
$router = new RouteList;
$router->withDomain('example.com')
	->addRoute('rss', 'Feed:rss')
	->addRoute('<presenter>/<action>');
```

En el nombre del dominio se pueden usar también [#Comodines]:

```php
$router = new RouteList;
$router->withDomain('example.%tld%')
	// ...
```


Prefijo de ruta
---------------

Las colecciones de rutas se pueden dividir según la ruta de la URL:

```php
$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // coincide con la URL /eshop/rss
	->addRoute('<presenter>/<action>'); // coincide con la URL /eshop/<presenter>/<action>
```


Combinaciones
-------------

Las agrupaciones anteriores se pueden combinar entre sí:

```php
$router = (new RouteList)
	->withDomain('admin.example.com')
		->withModule('Admin')
			->addRoute(/* ... */)
			->addRoute(/* ... */)
		->end()
		->withModule('Images')
			->addRoute(/* ... */)
		->end()
	->end()
	->withDomain('example.com')
		->withPath('export')
			->addRoute(/* ... */)
			// ...
```


Parámetros de consulta
----------------------

Las máscaras pueden contener también parámetros de consulta (los parámetros que van tras el signo de interrogación en la URL). Para ellos no se puede definir una expresión de validación, pero sí se puede cambiar el nombre con el que se pasan al presenter:

```php
// queremos usar el parámetro de consulta 'cat' con el nombre 'categoryId' en la aplicación
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);
```


Parámetros foo
--------------

Ahora vamos a profundizar. Los parámetros foo son, en esencia, parámetros sin nombre que permiten hacer coincidir una expresión regular. Un ejemplo es una ruta que acepta `/index`, `/index.html`, `/index.htm` e `/index.php`:

```php
$router->addRoute('index<? \.html?|\.php|>', /* ... */);
```

También es posible definir explícitamente la cadena que se usará al generar la URL. La cadena debe colocarse justo después del signo de interrogación. La siguiente ruta es parecida a la anterior, pero genera `/index.html` en lugar de `/index`, porque la cadena `.html` está establecida como valor de generación:

```php
$router->addRoute('index<?.html \.html?|\.php|>', /* ... */);
```


Integración
===========

Para integrar el router creado en la aplicación tenemos que informar de él al contenedor DI. La forma más sencilla es preparar una factory que cree el objeto del router e indicar al contenedor en la configuración que la use. Digamos que escribimos para ello el método `App\Core\RouterFactory::createRouter()`:

```php
namespace App\Core;

use Nette\Application\Routers\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute(/* ... */);
		return $router;
	}
}
```

Después escribimos en la [configuración |dependency-injection:services]:

```neon
services:
	- App\Core\RouterFactory::createRouter
```

Las eventuales dependencias, por ejemplo de la base de datos, se pasan al método factory como sus parámetros mediante el [autowiring|dependency-injection:autowiring]:

```php
public static function createRouter(Nette\Database\Connection $db): RouteList
{
	// ...
}
```


SimpleRouter
============

Un router mucho más sencillo que la colección de rutas es [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo usamos cuando no tenemos requisitos especiales para el formato de las URL, cuando `mod_rewrite` (o sus alternativas) no está disponible o cuando todavía no queremos ocuparnos de las URL bonitas.

Genera direcciones más o menos con esta forma:

```
http://example.com/?presenter=Product&action=detail&id=123
```

El parámetro del constructor de `SimpleRouter` es el presenter y la acción predeterminados, es decir, la acción que se ejecutará si abrimos, por ejemplo, `http://example.com/` sin parámetros adicionales.

```php
// el presenter predeterminado será 'Home' y la acción 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');
```

Recomendamos definir SimpleRouter directamente en la [configuración |dependency-injection:services]:

```neon
services:
	- Nette\Application\Routers\SimpleRouter('Home:default')
```


SEO y canonización
==================

El framework contribuye al SEO (Search Engine Optimization) impidiendo la duplicidad de contenido en URL distintas. Si a un destino concreto llevan varias direcciones, p. ej. `/index` e `/index.html`, el framework designa la primera como principal (canónica) y redirige las demás a ella con el código HTTP 301. Gracias a eso, los buscadores no indexan las páginas dos veces ni diluyen su page rank.

Este proceso se llama canonización. La URL canónica es la que genera el router, es decir, la primera ruta coincidente de la colección sin la bandera OneWay. Por eso indicamos en la colección **primero las rutas principales**.

De la canonización se encarga el presenter, más en el capítulo [canonización |presenters#Canonización].


HTTPS
=====

Para usar el protocolo HTTPS es necesario activarlo en el hosting y configurar el servidor correctamente.

La redirección de todo el sitio web a HTTPS hay que establecerla a nivel del servidor, por ejemplo mediante el archivo `.htaccess` en el directorio raíz de nuestra aplicación, con el código HTTP 301. La configuración puede variar según el hosting y tener más o menos este aspecto:

```
<IfModule mod_rewrite.c>
	RewriteEngine On
	...
	RewriteCond %{HTTPS} off
	RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
	...
</IfModule>
```

El router genera las URL con el mismo protocolo con el que se cargó la página, así que no hace falta configurar nada más.

Sin embargo, si excepcionalmente necesitamos que distintas rutas funcionen bajo protocolos distintos, lo indicamos en la máscara de la ruta:

```php
// Generará una dirección HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Generará una dirección HTTPS
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);
```


Depuración del router
=====================

El panel de enrutamiento que se muestra en la [Tracy Bar |tracy:] es un ayudante útil que muestra la lista de rutas y también los parámetros que el router obtuvo de la URL.

La barra verde con el símbolo ✓ representa la ruta que procesó la URL actual; el color azul y el símbolo ≈ señalan las rutas que también habrían procesado la URL si la verde no se les hubiera adelantado. Más abajo vemos el presenter y la acción actuales.

[* routing-debugger.webp *]

Al mismo tiempo, si se produce una redirección inesperada a causa de la [canonización |#SEO y canonización], conviene mirar en el panel la barra *redirect*, donde averiguará cómo entendió el router la URL originalmente y por qué redirigió.

.[note]
Al depurar el router recomendamos abrir las herramientas para desarrolladores del navegador (Ctrl+Shift+I o Cmd+Option+I) y desactivar la caché en el panel Network, para que las redirecciones no se guarden en ella.


Rendimiento
===========

El número de rutas influye en la velocidad del router. Su número no debería superar en ningún caso unas pocas decenas. Si su sitio web tiene una estructura de URL demasiado complicada, puede escribir un [#Router propio] propio.

Si el router no tiene dependencias, por ejemplo de la base de datos, y su factory no acepta argumentos, podemos serializar su forma compilada directamente en el contenedor DI y acelerar así ligeramente la aplicación.

```neon
routing:
	cache: true
```


Router propio
=============

Las siguientes líneas están destinadas a usuarios muy avanzados. Puede crear su propio router e integrarlo con toda naturalidad en la colección de rutas. El router es una implementación de la interfaz [api:Nette\Routing\Router] con dos métodos:

```php
use Nette\Http\IRequest as HttpRequest;
use Nette\Http\UrlScript;

class MyRouter implements Nette\Routing\Router
{
	public function match(HttpRequest $httpRequest): ?array
	{
		// ...
	}

	public function constructUrl(array $params, UrlScript $refUrl): ?string
	{
		// ...
	}
}
```

El método `match` procesa la petición actual [$httpRequest |http:request], de la que se puede obtener no solo la URL sino también las cabeceras, etc., y la convierte en un array que contiene el nombre del presenter y sus parámetros. Si no puede procesar la petición, devuelve null. Al procesar la petición debemos devolver al menos el presenter; la acción es opcional y, si no se indica, es `default`. El nombre del presenter es completo e incluye los eventuales módulos:

```php
[
	'presenter' => 'Front:Home',
	'action' => 'default',
]
```

El método `constructUrl`, por el contrario, construye la URL absoluta resultante a partir del array de parámetros. Puede usar la información del parámetro [`$refUrl`|api:Nette\Http\UrlScript], que es la URL actual.

Lo añadimos a la colección de rutas con `add()`:

```php
$router = new Nette\Application\Routers\RouteList;
$router->add($myRouter);
$router->addRoute(/* ... */);
// ...
```


Uso independiente
=================

Por uso independiente entendemos el aprovechamiento de las capacidades del router en una aplicación que no usa Nette Application ni presenters. Vale para él casi todo lo que hemos mostrado en este capítulo, con estas diferencias:

- para las colecciones de rutas usamos la clase [api:Nette\Routing\RouteList]
- como router sencillo, la clase [api:Nette\Routing\SimpleRouter]
- como no existe el par `Presenter:action`, usamos la [#Notación avanzada]

Así que creamos de nuevo un método que nos monte el router, p. ej.:

```php
namespace App\Core;

use Nette\Routing\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute('rss.xml', [
			'controller' => 'RssFeedController',
		]);
		$router->addRoute('article/<id \d+>', [
			'controller' => 'ArticleController',
		]);
		// ...
		return $router;
	}
}
```

Si usa un contenedor DI, cosa que recomendamos, añada de nuevo el método a la configuración y obtenga después del contenedor el router junto con la petición HTTP:

```php
$router = $container->getByType(Nette\Routing\Router::class);
$httpRequest = $container->getByType(Nette\Http\IRequest::class);
```

O cree los objetos directamente:

```php
$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
```

Ahora ya solo queda dejar que el router haga su trabajo:

```php
$params = $router->match($httpRequest);
if ($params === null) {
	// no se encontró ninguna ruta coincidente, se envía un error 404
	exit;
}

// procesa los parámetros obtenidos
$controller = $params['controller'];
// ...
```

Y al revés, use el router para construir un enlace:

```php
$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());
```


{{composer: nette/routing}}

Enrutamiento

El router se ocupa de todo lo relacionado con las direcciones URL, de modo que usted no tenga que pensar en ellas. Le mostraremos:

  • cómo configurar el router para que las URL tengan el aspecto que desea
  • hablaremos del SEO y de las redirecciones
  • y le enseñaremos cómo escribir un router propio

Las URL más amables para las personas (también llamadas cool o pretty URLs) son más utilizables, se recuerdan mejor y contribuyen positivamente al SEO. Nette lo tiene en cuenta y satisface plenamente las necesidades de los desarrolladores. Puede diseñar para su aplicación exactamente la estructura de URL que quiera. Incluso puede diseñarla cuando la aplicación ya está terminada, porque no requiere ningún cambio en el código ni en las plantillas. Se define de forma elegante en un único lugar, el router, en vez de andar dispersa como anotaciones por todos los presenters.

El router en Nette es excepcional porque es bidireccional. Sabe tanto descodificar las URL de las peticiones HTTP como crear enlaces. Por eso desempeña un papel clave en Nette Application, ya que no solo decide qué presenter y qué acción ejecutarán la petición actual, sino que se usa también para generar las URL en las plantillas, etc.

El router no se limita, sin embargo, a este uso; puede utilizarlo en aplicaciones donde no se usan presenters en absoluto, para API REST, etc. Más detalles en la sección Uso independiente.

Colección de rutas

La forma más agradable de definir la estructura de las direcciones URL de una aplicación la ofrece la clase Nette\Application\Routers\RouteList. La definición consiste en una lista de las llamadas rutas, es decir, máscaras de direcciones URL y los presenters y acciones asociados, mediante una API sencilla. No hace falta nombrar las rutas de ninguna manera.

$router = new Nette\Application\Routers\RouteList;
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('article/<id>', 'Article:view');
// ...

El ejemplo muestra que, si abrimos en el navegador https://domain.com/rss.xml, se mostrará el presenter Feed con la acción rss. Si abrimos https://domain.com/article/12, se mostrará el presenter Article con la acción view, etc. Si no se encuentra ninguna ruta adecuada, Nette Application reacciona lanzando la excepción BadRequestException, que se muestra al usuario como una página de error 404 Not Found.

Orden de las rutas

El orden en el que se indican las distintas rutas es absolutamente crucial, porque se evalúan sucesivamente de arriba abajo. La regla es que declaramos las rutas de las específicas a las generales:

// MAL: 'rss.xml' lo captura la primera ruta y entiende esa cadena como <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// BIEN
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');

Las rutas se evalúan de arriba abajo también al generar los enlaces:

// MAL: el enlace a 'Feed:rss' se genera como 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// BIEN
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');

No le vamos a ocultar que montar las rutas correctamente requiere cierta destreza. Hasta que le coja el truco, el panel de enrutamiento le será de gran ayuda.

Máscara y parámetros

La máscara describe la ruta relativa desde el directorio raíz del sitio web. La máscara más sencilla es una URL estática:

$router->addRoute('products', 'Products:default');

A menudo las máscaras contienen los llamados parámetros. Se escriben entre corchetes angulares (p. ej. <year>) y se pasan al presenter de destino, por ejemplo al método renderShow(int $year) o al parámetro persistente $year:

$router->addRoute('chronicle/<year>', 'History:show');

El ejemplo muestra que, si abrimos en el navegador https://example.com/chronicle/2020, se mostrará el presenter History con la acción show y el parámetro year: 2020.

A los parámetros les podemos indicar un valor por defecto directamente en la máscara, con lo que se vuelven opcionales:

$router->addRoute('chronicle/<year=2020>', 'History:show');

La ruta aceptará ahora también la URL https://example.com/chronicle/, que mostrará de nuevo History:show con el parámetro year: 2020.

Naturalmente, el nombre del presenter y de la acción también pueden ser parámetros. Por ejemplo:

$router->addRoute('<presenter>/<action>', 'Home:default');

La ruta indicada acepta, por ejemplo, URL con la forma /article/edit o /catalog/list y las entiende como los presenters y acciones Article:edit y Catalog:list, respectivamente.

Al mismo tiempo da a los parámetros presenter y action los valores por defecto Home y default, con lo que también son opcionales. Así, la ruta acepta también una URL como /article y la entiende como Article:default. O al revés: un enlace a Product:default genera la ruta /product y un enlace al predeterminado Home:default genera la ruta /.

La máscara puede describir no solo la ruta relativa desde el directorio raíz del sitio web, sino también una ruta absoluta si empieza por una barra, o incluso la URL absoluta entera si empieza por dos barras:

// relativa al document root
$router->addRoute('<presenter>/<action>', /* ... */);

// ruta absoluta (relativa al dominio)
$router->addRoute('/<presenter>/<action>', /* ... */);

// URL absoluta incluido el dominio (relativa al esquema)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// URL absoluta incluido el esquema
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);

Expresiones de validación

A cada parámetro se le puede indicar una condición de validación mediante una expresión regular. Por ejemplo, al parámetro id le indicamos con la expresión regular \d+ que solo puede contener cifras:

$router->addRoute('<presenter>/<action>[/<id \d+>]', /* ... */);

La expresión regular predeterminada para todos los parámetros es [^/]+, es decir, todo menos la barra. Si un parámetro debe aceptar también barras, indicamos la expresión .+:

// acepta https://example.com/a/b/c, path será 'a/b/c'
$router->addRoute('<path .+>', /* ... */);

Secuencias opcionales

En la máscara se pueden marcar partes opcionales con corchetes. Cualquier parte de la máscara puede ser opcional y puede contener parámetros:

$router->addRoute('[<lang [a-z]{2}>/]<name>', /* ... */);

// Acepta las rutas:
//    /en/download  => lang => en, name => download
//    /download     => lang => null, name => download

Cuando un parámetro forma parte de una secuencia opcional, se vuelve naturalmente opcional también. Si no tiene indicado un valor por defecto, será null.

Las partes opcionales pueden estar también en el dominio:

$router->addRoute('//[<lang=en>.]example.com/<presenter>/<action>', /* ... */);

Las secuencias se pueden anidar y combinar libremente:

$router->addRoute(
	'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
	'Home:default',
);

// Acepta las rutas:
// 	/en/hello
// 	/en-us/hello
// 	/hello
// 	/hello/page-12

Al generar las URL se busca la variante más corta, así que todo lo que se pueda omitir se omite. Por eso, por ejemplo, la ruta index[.html] genera la ruta /index. Este comportamiento se puede invertir escribiendo un signo de exclamación después del corchete izquierdo:

// acepta /hello y /hello.html, genera /hello
$router->addRoute('<name>[.html]', /* ... */);

// acepta /hello y /hello.html, genera /hello.html
$router->addRoute('<name>[!.html]', /* ... */);

Los parámetros opcionales (es decir, los que tienen un valor por defecto) sin corchetes se comportan en esencia como si estuvieran encerrados de la siguiente manera:

$router->addRoute('<presenter=Home>/<action=default>/<id=>', /* ... */);

// corresponde a esto:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);

Si queremos influir en el comportamiento de la barra final, para que se genere por ejemplo /home en lugar de /home/, se puede conseguir así:

$router->addRoute('[<presenter=Home>[/<action=default>[/<id>]]]', /* ... */);

Comodines

En la máscara de una URL absoluta podemos usar los siguientes comodines para no tener que escribir en la máscara, por ejemplo, el dominio, que puede diferir entre el entorno de desarrollo y el de producción:

  • %tld% = dominio de primer nivel, p. ej. comorg
  • %sld% = dominio de segundo nivel, p. ej. example
  • %domain% = dominio sin subdominios, p. ej. example.com
  • %host% = host entero, p. ej. www.example.com
  • %basePath% = ruta al directorio raíz
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action>', /* ... */);

Notación avanzada

El destino de la ruta, que se escribe normalmente con el formato Presenter:action, se puede escribir también mediante un array que define los distintos parámetros y sus valores por defecto:

$router->addRoute('<presenter>/<action>[/<id \d+>]', [
	'presenter' => 'Home',
	'action' => 'default',
]);

Para una especificación más detallada se puede usar una forma aún más extendida, en la que, además de los valores por defecto, podemos establecer otras propiedades de los parámetros, como una expresión regular de validación (véase el parámetro id):

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>[/<id>]', [
	'presenter' => [
		Route::Value => 'Home',
	],
	'action' => [
		Route::Value => 'default',
	],
	'id' => [
		Route::Pattern => '\d+',
	],
]);

Es importante señalar que, si los parámetros definidos en el array no aparecen en la máscara de la ruta, sus valores no se pueden cambiar, ni siquiera con los parámetros de consulta indicados tras el signo de interrogación en la URL.

Esto es útil para los parámetros fijos: dar a una página concreta una URL corta y fácil de recordar. Por ejemplo, para que /tos abra siempre Article:view con id: 123:

$router->addRoute('tos', [
	'presenter' => 'Article',
	'action' => 'view',
	'id' => 123,
]);

Filtros y traducciones

El código fuente de la aplicación lo escribimos en inglés, pero si el sitio web debe tener las URL en checo, un enrutamiento sencillo como:

$router->addRoute('<presenter>/<action>', 'Home:default');

generará URL en inglés, como /product/123 o /cart. Si queremos que los presenters y las acciones en la URL estén representados por palabras checas (p. ej. /produkt/123 o /kosik), podemos usar un diccionario de traducción. Para escribirlo necesitamos ya la variante „más locuaz“ del segundo parámetro:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// cadena en la URL => presenter
			'produkt' => 'Product',
			'kosik' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'seznam' => 'list',
		],
	],
]);

Varias claves del diccionario de traducción pueden llevar al mismo presenter. Así se le crean distintos alias. La última clave se considera la variante canónica (es decir, la que aparecerá en la URL generada).

La tabla de traducción se puede usar de esta manera para cualquier parámetro. Si la traducción no existe, se toma el valor original. Podemos cambiar este comportamiento añadiendo Route::FilterStrict => true y la ruta rechazará entonces la URL si el valor no está en el diccionario.

Además del diccionario de traducción en forma de array, se pueden emplear funciones de traducción propias.

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>/<id>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterIn => function (string $s): string { /* ... */ },
		Route::FilterOut => function (string $s): string { /* ... */ },
	],
	'action' => 'default',
	'id' => null,
]);

La función Route::FilterIn convierte entre el parámetro de la URL y la cadena que se pasa después al presenter; la función FilterOut se encarga de la conversión en sentido contrario.

Los parámetros presenter, action y module ya tienen filtros predefinidos que convierten entre el estilo PascalCase o camelCase y el kebab-case usado en la URL. El valor por defecto de los parámetros se escribe ya en la forma en la que se pasa a la aplicación (PascalCase para presenter y module, camelCase para action), así que, por ejemplo, en el caso del presenter escribimos <presenter=ProductEdit>, no <presenter=product-edit>.

Filtros generales

Además de los filtros destinados a parámetros concretos, podemos definir también filtros generales, que reciben un array asociativo de todos los parámetros, que pueden modificar de cualquier manera y devolver después. Los filtros generales se definen bajo la clave vacía.

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => 'Home',
	'action' => 'default',
	'' => [
		Route::FilterIn => function (array $params): array { /* ... */ },
		Route::FilterOut => function (array $params): array { /* ... */ },
	],
]);

Los filtros generales ofrecen la posibilidad de modificar el comportamiento de la ruta de absolutamente cualquier manera. Podemos usarlos, por ejemplo, para modificar unos parámetros a partir de otros. Por ejemplo, para traducir <presenter> y <action> según el valor actual del parámetro <lang>.

Si un parámetro tiene definido su propio filtro y existe además un filtro general, el FilterIn propio se ejecuta antes que el general y, al revés, el FilterOut general se ejecuta antes que el propio. Así, dentro del filtro general los valores de los parámetros presenter y action están escritos en estilo PascalCase o camelCase, respectivamente.

Véase URLs amigables con slugs para un uso práctico de estos filtros: generar URL amigables para el SEO como /article/123-how-to-bake-bread sin modificar ninguna plantilla.

Bandera OneWay

Las rutas de un solo sentido se usan para mantener la funcionalidad de URL antiguas que la aplicación ya no genera, pero que sigue aceptando. Las marcamos con la bandera OneWay:

// URL antigua /product-info?id=123
$router->addRoute('product-info', 'Product:detail', oneWay: true);
// URL nueva /product/123
$router->addRoute('product/<id>', 'Product:detail');

Al acceder a la URL antigua, el presenter redirige automáticamente a la nueva, de modo que los buscadores no indexen estas páginas dos veces (véase SEO y canonización).

Enrutamiento dinámico con callbacks

El enrutamiento dinámico con callbacks le permite asignar directamente a las rutas funciones (callbacks) que se ejecutan al visitar la ruta dada. Esta funcionalidad flexible le permite crear rápida y eficazmente distintos endpoints para su aplicación:

$router->addRoute('test', function () {
	echo 'You are at the /test address';
});

En la máscara puede definir también parámetros, que se pasan automáticamente a su callback:

$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Welcome to the Czech version of our website!',
		'en' => 'Welcome to the English version of our website!',
	};
});

Además de los parámetros de la máscara, el callback puede recibir también servicios del contenedor DI. Se pasan según el tipo del parámetro. Además, el parámetro $presenter recibe una instancia de MicroPresenter, que procesa la ruta:

$router->addRoute('<lang cs|en>', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) {
	// ...
});

Módulos

Si tenemos varias rutas que pertenecen a un módulo común, usamos withModule(). El módulo indicado se antepone automáticamente al presenter de cada ruta del grupo y desaparece por completo de la URL:

$router = new RouteList;
$router->withModule('Forum') // las rutas siguientes forman parte del módulo Forum
	->addRoute('rss', 'Feed:rss') // el presenter será Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // las rutas siguientes forman parte del módulo Forum:Admin
		->addRoute('sign:in', 'Sign:in');

Una alternativa es el parámetro module, que igualmente fija un módulo y lo mantiene fuera de la URL:

// la URL manage/dashboard/default se mapea al presenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

El nombre de un presenter está completo solo junto con su módulo, p. ej. Front:Admin:ProductList. Siempre que ese nombre completo acaba en un parámetro de la URL, el router lo codifica con dos reglas sencillas: cada dos puntos : (el separador de módulos) se convierte en un punto y cada límite entre palabras de un nombre en PascalCase se convierte en un guion. Así, Front:Admin:ProductList se escribe en la URL como front.admin.product-list y de la misma manera se descodifica de vuelta. Precisamente por eso una aplicación modular sin ninguna de las herramientas anteriores genera URL llenas de puntos.

Tanto withModule() como el parámetro module evitan esto justamente porque recortan el prefijo de módulo conocido del nombre del presenter antes de que llegue a la URL: como el módulo es una constante, no hace falta codificarlo de ninguna manera.

A veces queremos que el módulo mismo varíe y aparezca en la URL, así que recurrimos a <module> directamente en la máscara. Cuidado, sin embargo, con un detalle esencial: <module> absorbe toda la ruta de módulos, todo hasta los últimos dos puntos del nombre del presenter. En el presenter Shop:Admin:Product eso es el módulo Shop:Admin y el presenter Product, y como los dos puntos se convierten en puntos, obtenemos:

Subdominios

Las colecciones de rutas se pueden dividir según los subdominios:

$router = new RouteList;
$router->withDomain('example.com')
	->addRoute('rss', 'Feed:rss')
	->addRoute('<presenter>/<action>');

En el nombre del dominio se pueden usar también Comodines:

$router = new RouteList;
$router->withDomain('example.%tld%')
	// ...

Prefijo de ruta

Las colecciones de rutas se pueden dividir según la ruta de la URL:

$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // coincide con la URL /eshop/rss
	->addRoute('<presenter>/<action>'); // coincide con la URL /eshop/<presenter>/<action>

Combinaciones

Las agrupaciones anteriores se pueden combinar entre sí:

$router = (new RouteList)
	->withDomain('admin.example.com')
		->withModule('Admin')
			->addRoute(/* ... */)
			->addRoute(/* ... */)
		->end()
		->withModule('Images')
			->addRoute(/* ... */)
		->end()
	->end()
	->withDomain('example.com')
		->withPath('export')
			->addRoute(/* ... */)
			// ...

Parámetros de consulta

Las máscaras pueden contener también parámetros de consulta (los parámetros que van tras el signo de interrogación en la URL). Para ellos no se puede definir una expresión de validación, pero sí se puede cambiar el nombre con el que se pasan al presenter:

// queremos usar el parámetro de consulta 'cat' con el nombre 'categoryId' en la aplicación
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Parámetros foo

Ahora vamos a profundizar. Los parámetros foo son, en esencia, parámetros sin nombre que permiten hacer coincidir una expresión regular. Un ejemplo es una ruta que acepta /index, /index.html, /index.htm e /index.php:

$router->addRoute('index<? \.html?|\.php|>', /* ... */);

También es posible definir explícitamente la cadena que se usará al generar la URL. La cadena debe colocarse justo después del signo de interrogación. La siguiente ruta es parecida a la anterior, pero genera /index.html en lugar de /index, porque la cadena .html está establecida como valor de generación:

$router->addRoute('index<?.html \.html?|\.php|>', /* ... */);

Integración

Para integrar el router creado en la aplicación tenemos que informar de él al contenedor DI. La forma más sencilla es preparar una factory que cree el objeto del router e indicar al contenedor en la configuración que la use. Digamos que escribimos para ello el método App\Core\RouterFactory::createRouter():

namespace App\Core;

use Nette\Application\Routers\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute(/* ... */);
		return $router;
	}
}

Después escribimos en la configuración:

services:
	- App\Core\RouterFactory::createRouter

Las eventuales dependencias, por ejemplo de la base de datos, se pasan al método factory como sus parámetros mediante el autowiring:

public static function createRouter(Nette\Database\Connection $db): RouteList
{
	// ...
}

SimpleRouter

Un router mucho más sencillo que la colección de rutas es SimpleRouter. Lo usamos cuando no tenemos requisitos especiales para el formato de las URL, cuando mod_rewrite (o sus alternativas) no está disponible o cuando todavía no queremos ocuparnos de las URL bonitas.

Genera direcciones más o menos con esta forma:

http://example.com/?presenter=Product&action=detail&id=123

El parámetro del constructor de SimpleRouter es el presenter y la acción predeterminados, es decir, la acción que se ejecutará si abrimos, por ejemplo, http://example.com/ sin parámetros adicionales.

// el presenter predeterminado será 'Home' y la acción 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Recomendamos definir SimpleRouter directamente en la configuración:

services:
	- Nette\Application\Routers\SimpleRouter('Home:default')

SEO y canonización

El framework contribuye al SEO (Search Engine Optimization) impidiendo la duplicidad de contenido en URL distintas. Si a un destino concreto llevan varias direcciones, p. ej. /index e /index.html, el framework designa la primera como principal (canónica) y redirige las demás a ella con el código HTTP 301. Gracias a eso, los buscadores no indexan las páginas dos veces ni diluyen su page rank.

Este proceso se llama canonización. La URL canónica es la que genera el router, es decir, la primera ruta coincidente de la colección sin la bandera OneWay. Por eso indicamos en la colección primero las rutas principales.

De la canonización se encarga el presenter, más en el capítulo canonización.

HTTPS

Para usar el protocolo HTTPS es necesario activarlo en el hosting y configurar el servidor correctamente.

La redirección de todo el sitio web a HTTPS hay que establecerla a nivel del servidor, por ejemplo mediante el archivo .htaccess en el directorio raíz de nuestra aplicación, con el código HTTP 301. La configuración puede variar según el hosting y tener más o menos este aspecto:

<IfModule mod_rewrite.c>
	RewriteEngine On
	...
	RewriteCond %{HTTPS} off
	RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
	...
</IfModule>

El router genera las URL con el mismo protocolo con el que se cargó la página, así que no hace falta configurar nada más.

Sin embargo, si excepcionalmente necesitamos que distintas rutas funcionen bajo protocolos distintos, lo indicamos en la máscara de la ruta:

// Generará una dirección HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Generará una dirección HTTPS
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Depuración del router

El panel de enrutamiento que se muestra en la Tracy Bar es un ayudante útil que muestra la lista de rutas y también los parámetros que el router obtuvo de la URL.

La barra verde con el símbolo ✓ representa la ruta que procesó la URL actual; el color azul y el símbolo ≈ señalan las rutas que también habrían procesado la URL si la verde no se les hubiera adelantado. Más abajo vemos el presenter y la acción actuales.

Al mismo tiempo, si se produce una redirección inesperada a causa de la canonización, conviene mirar en el panel la barra redirect, donde averiguará cómo entendió el router la URL originalmente y por qué redirigió.

Al depurar el router recomendamos abrir las herramientas para desarrolladores del navegador (Ctrl+Shift+I o Cmd+Option+I) y desactivar la caché en el panel Network, para que las redirecciones no se guarden en ella.

Rendimiento

El número de rutas influye en la velocidad del router. Su número no debería superar en ningún caso unas pocas decenas. Si su sitio web tiene una estructura de URL demasiado complicada, puede escribir un Router propio propio.

Si el router no tiene dependencias, por ejemplo de la base de datos, y su factory no acepta argumentos, podemos serializar su forma compilada directamente en el contenedor DI y acelerar así ligeramente la aplicación.

routing:
	cache: true

Router propio

Las siguientes líneas están destinadas a usuarios muy avanzados. Puede crear su propio router e integrarlo con toda naturalidad en la colección de rutas. El router es una implementación de la interfaz Nette\Routing\Router con dos métodos:

use Nette\Http\IRequest as HttpRequest;
use Nette\Http\UrlScript;

class MyRouter implements Nette\Routing\Router
{
	public function match(HttpRequest $httpRequest): ?array
	{
		// ...
	}

	public function constructUrl(array $params, UrlScript $refUrl): ?string
	{
		// ...
	}
}

El método match procesa la petición actual $httpRequest, de la que se puede obtener no solo la URL sino también las cabeceras, etc., y la convierte en un array que contiene el nombre del presenter y sus parámetros. Si no puede procesar la petición, devuelve null. Al procesar la petición debemos devolver al menos el presenter; la acción es opcional y, si no se indica, es default. El nombre del presenter es completo e incluye los eventuales módulos:

[
	'presenter' => 'Front:Home',
	'action' => 'default',
]

El método constructUrl, por el contrario, construye la URL absoluta resultante a partir del array de parámetros. Puede usar la información del parámetro $refUrl, que es la URL actual.

Lo añadimos a la colección de rutas con add():

$router = new Nette\Application\Routers\RouteList;
$router->add($myRouter);
$router->addRoute(/* ... */);
// ...

Uso independiente

Por uso independiente entendemos el aprovechamiento de las capacidades del router en una aplicación que no usa Nette Application ni presenters. Vale para él casi todo lo que hemos mostrado en este capítulo, con estas diferencias:

Así que creamos de nuevo un método que nos monte el router, p. ej.:

namespace App\Core;

use Nette\Routing\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute('rss.xml', [
			'controller' => 'RssFeedController',
		]);
		$router->addRoute('article/<id \d+>', [
			'controller' => 'ArticleController',
		]);
		// ...
		return $router;
	}
}

Si usa un contenedor DI, cosa que recomendamos, añada de nuevo el método a la configuración y obtenga después del contenedor el router junto con la petición HTTP:

$router = $container->getByType(Nette\Routing\Router::class);
$httpRequest = $container->getByType(Nette\Http\IRequest::class);

O cree los objetos directamente:

$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();

Ahora ya solo queda dejar que el router haga su trabajo:

$params = $router->match($httpRequest);
if ($params === null) {
	// no se encontró ninguna ruta coincidente, se envía un error 404
	exit;
}

// procesa los parámetros obtenidos
$controller = $params['controller'];
// ...

Y al revés, use el router para construir un enlace:

$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());