Enrutador (Router)

El enrutamiento se puede utilizar además del mecanismo por defecto que asocia controller/action/{parameters} con una url.

Rutas dinámicas

Las rutas dinámicas se definen en tiempo de ejecución.
Es posible definir estas rutas en el archivo app/config/services.php.

Importante

Las rutas dinámicas sólo deben utilizarse si la situación lo requiere:

  • en el caso de una microaplicación

  • si una ruta debe definirse dinámicamente

En todos los demás casos, es aconsejable declarar las rutas con anotaciones, para beneficiarse del almacenamiento en caché.

Rutas de devolución de llamadas (callback)

Las rutas más básicas de Ubiquity aceptan un Closure.
En el contexto de las microaplicaciones, este método evita tener que crear un controlador.

app/config/services.php
1     use Ubiquity\controllers\Router;
2
3     Router::get("foo", function(){
4             echo 'Hello world!';
5     });

Se pueden definir rutas de devolución de llamada para todos los métodos http con:

  • Router::post

  • Router::put

  • Router::delete

  • Router::patch

  • Router::options

Rutas de controlador

Las rutas también pueden asociarse de forma más convencional a una acción de un controlador:

app/config/services.php
1     use Ubiquity\controllers\Router;
2
3     Router::addRoute('bar', \controllers\FooController::class,'index');

El método FooController::index() será accesible a través de la url /bar.

En este caso, el FooController debe ser una clase que herede de Ubiquitycontrollers\Controller o una de sus subclases, y debe tener un método index:

app/controllers/FooController.php
1     namespace controllers;
2
3     class FooController extends ControllerBase{
4
5             public function index(){
6                     echo 'Hello from foo';
7             }
8     }

Ruta por defecto

La ruta por defecto coincide con la ruta /.
Puede definirse utilizando la ruta reservada _default.

app/config/services.php
1     use Ubiquity\controllers\Router;
2
3     Router::addRoute("_default", \controllers\FooController::class,'bar');

Rutas estáticas

Las rutas estáticas se definen mediante anotaciones o con atributos nativos de php desde Ubiquity 2.4.0.

Nota

Estas anotaciones o atributos nunca se leen en tiempo de ejecución.
Es necesario reiniciar la caché del enrutador para tener en cuenta los cambios realizados en las rutas.

Creación

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6
 7    #[Route('products')]
 8    public function index(){}
 9
10}

El método Products::index() será accesible a través de la url /products.

Nota

La barra inicial o terminal se ignora en la ruta. Por lo tanto, las siguientes rutas son equivalentes:
  • #[Route('products')]

  • #[Route('/products')]

  • #[Route('/products/')]

Parámetros de ruta

Una ruta puede tener parámetros:

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6...
 7     #[Route('products/{value}')]
 8     public function search($value){
 9         // $value will equal the dynamic part of the URL
10         // e.g. at /products/brocolis, then $value='brocolis'
11         // ...
12     }
13}

Parámetros opcionales de ruta

Una ruta puede definir parámetros opcionales, si el método asociado tiene argumentos opcionales:

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6   ...
 7   #[Route('products/all/{pageNum}/{countPerPage}')]
 8   public function list($pageNum,$countPerPage=50){
 9      // ...
10   }
11}

Requisitos de ruta

Es posible añadir especificaciones sobre las variables pasadas en la url mediante el atributo requirements.

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6   ...
 7   #[Route('products/all/{pageNum}/{countPerPage}',requirements: ["pageNum"=>"\d+","countPerPage"=>"\d?"])]
 8   public function list($pageNum,$countPerPage=50){
 9      // ...
10   }
11}
La ruta definida coincide con estas urls:
  • products/all/1/20

  • products/all/5/

pero no con ese:
  • products/all/test

Tipificación de parámetros

La declaración de ruta tiene en cuenta los tipos de datos pasados a la acción, lo que evita añadir requisitos para tipos simples (int, bool, float).

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6   ...
 7   #[Route('products/{productNumber}')]
 8   public function one(int $productNumber){
 9      // ...
10   }
11}
La ruta definida coincide con estas urls:
  • products/1

  • products/20

pero no con ese:
  • products/test

Valores correctos por tipo de datos:
  • int: 1

  • bool: 0 or 1

  • float: 1 1.0

Métodos http de enrutado

Es posible especificar el método o métodos http asociados a una ruta:

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6
 7   #[Route('products',methods: ['get','post'])]
 8   public function index(){}
 9
10}

El atributo methods puede aceptar varios métodos:
@route("testMethods", "methods"=>["get", "post", "delete"])
#[Route('testMethods', methods: ['get','post','delete'])]

La anotación @route o atributo Route se aplica por defecto a todos los métodos HTTP.
Existe una anotación específica para cada uno de los métodos HTTP existentes:

  • @get => Get

  • @post => Post

  • @put => Put

  • @patch => Patch

  • @delete => Delete

  • @head => Head

  • @options => Options

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Get;
 4
 5class ProductsController extends ControllerBase{
 6
 7   #[Get('products')]
 8   public function index(){}
 9
10}

Nombre de ruta

Es posible especificar el name de una ruta, este nombre facilita entonces el acceso a la url asociada.
Si no se especifica el atributo name, cada ruta tiene un nombre por defecto, basado en el patrón controllerName_methodName.

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6
 7   #[Route('products',name: 'products.index')]
 8   public function index(){}
 9
10}

Generación de URL o rutas

Los nombres de ruta pueden utilizarse para generar URL o rutas.

Enlaces a páginas en Twig

<a href="{{ path('products.index') }}">Products</a>

Ruta global

La anotación @route puede utilizarse en una clase de controlador :

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5#[Route('products')]
 6class ProductsController extends ControllerBase{
 7   ...
 8   #[Route('/all')]
 9   public function display(){}
10
11}

En este caso, la ruta definida en el controlador se utiliza como prefijo para todas las rutas del controlador :
La ruta generada para la acción display es /product/all.

rutas automatizadas

Si se define una ruta global, es posible añadir todas las acciones del controlador como rutas (utilizando el prefijo global), estableciendo el parámetro automated :

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5#[Route('/products',automated: true)]
 6class ProductsController extends ControllerBase{
 7
 8   public function index(){}
 9
10   public function generate(){}
11
12   public function display($id){}
13
14}
El atributo automated define las 3 rutas contenidas en ProductsController:
  • /product/(index/)?

  • /product/generate

  • /product/display/{id}

rutas heredadas

Con el atributo inherited, también es posible generar las rutas declaradas en las clases base, o generar rutas asociadas a acciones de clases base si el atributo automated se establece a true al mismo tiempo.

La clase base:

app/controllers/ProductsBase.php
 1 namespace controllers;
 2
 3 use Ubiquity\attributes\items\router\Route;
 4
 5 abstract class ProductsBase extends ControllerBase{
 6
 7     #[Route('(index/)?')]
 8     public function index(){}
 9
10     #[Route('sort/{name}')]
11     public function sortBy($name){}
12
13 }

La clase derivada que utiliza miembros heredados:

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5#[Route('/product',inherited: true)]
 6class ProductsController extends ProductsBase{
 7
 8   public function display(){}
 9
10}
El atributo inherited define las 2 rutas definidas en ProductsBase:
  • /products/(index/)?

  • /products/sort/{name}

Si se combinan los atributos automated y inherited, las acciones de la clase base también se añaden a las rutas.

Parámetros de ruta globales

La parte global de una ruta puede definir parámetros, que serán pasados en todas las rutas generadas.
Estos parámetros se pueden recuperar a través de un miembro de datos público:

app/controllers/FooController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5#[Route('/foo/{bar}',automated: true)]
 6class FooController {
 7
 8   public string $bar;
 9
10   public function display(){
11       echo $this->bar;
12   }
13
14}

Accediendo a la url /foo/bar/display se muestra el contenido del miembro bar.

Ruta sin prefijo global

Si la ruta global se define en un controlador, todas las rutas generadas en este controlador irán precedidas del prefijo.
Es posible introducir explícitamente excepciones en algunas rutas, utilizando el prefijo #/.

app/controllers/FooController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5#[Route('/foo',automated: true)]
 6class FooController {
 7
 8   #[Route('#/noRoot')]
 9   public function noRoot(){}
10
11}

El controlador define la url /noRoot, que no está prefijada con la parte /foo.

Prioridad de ruta

El parámetro prority de una ruta permite resolver esta ruta en un orden de prioridad.

Cuanto mayor sea el parámetro de prioridad, más se definirá la ruta al principio de la pila de rutas en la caché.

En el ejemplo siguiente, la ruta products/all se definirá antes que la ruta products.

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6
 7   #[Route('products', priority: 1)]
 8   public function index(){}
 9
10   #[Route('products/all', priority: 10)]
11   public function all(){}
12
13}

El valor de prioridad por defecto es 0.

Caché de respuesta de rutas

Es posible almacenar en caché la respuesta producida por una ruta:

En este caso, la respuesta se almacena en caché y deja de ser dinámica.

#[Route('products/all', cache: true)]
public function all(){}

Duración de la caché

La duración se expresa en segundos, si se omite, la duración de la caché es infinita.

#[Route('products/all', cache: true, duration: 3600)]
public function all(){}

Expiración de la caché

Es posible forzar la recarga de la respuesta borrando la caché asociada.

Router::setExpired("products/all");

Cacheo dinámico de rutas

Las rutas dinámicas también pueden almacenarse en caché.

Importante

Esta posibilidad sólo es útil si esta caché no se realiza en producción, sino en el momento de inicializar la caché.

Router::get("foo", function(){
   echo 'Hello world!';
});

Router::addRoute("string", \controllers\Main::class,"index");
CacheManager::storeDynamicRoutes(false);

Comprobación de rutas con devtools :

Ubiquity info:routes
../_images/info-routes.png

Gestión de errores (errores 404 y 500)

Sistema de enrutamiento por defecto

Con el sistema de enrutamiento por defecto (la pareja controlador+acción definiendo una ruta), el manejador de errores puede ser redefinido para personalizar la gestión de errores.

En el fichero de configuración app/config/config.php, añade la clave onError, asociada a un devolucion de llamada (callback) que defina los mensajes de error:

"onError"=>function ($code, $message = null,$controller=null){
   switch($code){
      case 404:
         $init=($controller==null);
         \Ubiquity\controllers\Startup::forward('IndexController/p404',$init,$init);
         break;
   }
}

Implementar la acción solicitada p404 en el IndexController:

app/controllers/IndexController.php
...

public function p404(){
   echo "<div class='ui error message'><div class='header'>404</div>The page you are looking for doesn't exist!</div>";
}

Enrutado con anotaciones

Basta en este caso con añadir una última ruta deshabilitando el sistema de enrutamiento por defecto, y correspondiente a la gestión del error 404:

app/controllers/IndexController.php
...

#[Route('{url}', priority: -1000)]
public function p404($url){
   echo "<div class='ui error message'><div class='header'>404</div>The page `$url` you are looking for doesn't exist!</div>";
}