El sistema de routing de un framework, es una de las ventajas más competitivas a la hora de tomar la decisión de elegir un framework u otro.

Symfony provee un sistema de rutas o routing que facilita mucho el desarrollo de aplicaciones, aportando velocidad en el desarrollo, limpieza en el código y una estética muy amigable de cara al usuario y transmisión de variables de información a través de la URL.

Si tuviese que definir que es un sistema de routing en Symfony, diría que:

Es el sistema que relaciona la información de la URL con el código que la resuelve.

En un desarrollo Symfony, la ruta se ve en la URL. Todo el contenido de la URL que aparece después de la URL base, es la ruta definida por el desarrollador para direccionar hacia el código que va a procesar el contenido.

Una de las particularidades de las rutas en Symfony, es que podemos realizar la transmisión de variables a través de la propia ruta, al margen de los parámetros GET tradicionales que hemos visto tantas veces.

De esta manera, lo explicaremos más adelante, podemos ver URLs como las siguientes, donde por ejemplo, cardenalherrera es una variable y la acción edit también:

  • https://www.midominio.com/intranet/usuarios/
  • https://www.midominio.com/intranet/usuario/cardenalherrera
  • https://www.midominio.com/intranet/usuario/cardenalherrera/?edit=true
  • https://www.midominio.com/intranet/usuario/cardenalherrera/edit/

El sistema de routing

Symfony carga todas las rutas desde el archivo de configuración de enrutamiento global situado en /app/config/routing.yml que tiene un aspecto similar a este:

app:
    resource: "@AppBundle/Controller/"
    type:     annotation

Es posible delegar las rutas a partir de este archivo en otros archivos con la finalidad de organizar mejor el código a través de los Bundle que estemos desarrollando. Más información aquí. Para ello debemos de realizar un link desde el archivo global de rutas, hacia el archivo donde vamos a definir las rutas, quedando un archivo de routing global similar a este:

app:
    resource: "@AppBundle/Controller/"
    type:     annotation
app_route_file:
    resource: "@AppBundle/Resources/config/routing.yml"
    prefix:   /

Este archivo global de routing, lo que está indicando es que las rutas del sistema pueden ser definidas mediante dos metodos:

  • Annotation
  • Prefix

Definición de rutas por anotación (annotation)

La definición de rutas por anotación, consiste en escribir la ruta vía comentario, por delante del método de la clase del controller que va a resolver esa ruta.

Esto se realiza mediante la sintaxis @Route y quedaría de la siguiente forma:

    /**
     * @Route("/intranet/usuarios", name="intranetUsuarios")
     */

Esto debe de ir justo antes del método de la clase que va a resolver el código, quedando algo así:

namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class PruebasController extends Controller
{
    /**
     * @Route("/intranet/usuarios", name="intranetUsuarios")
     */
    public function intranetUsuariosAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "Bienvenido a la intranet de usuarios",
        ]);
    }
}

Definición de rutas por archivo yml (prefix)

Si hemos delegado dentro del archivo global de rutas, una parte de las mismas a otro archivo de routing externo, tal como hemos visto, el fichero routing donde hemos delegado, contendrá una información similar a la siguiente:

home:
    path: /
    defaults: { _controller: AppBundle:Default:index }
pruebas:
    path: /pruebas
    defaults: { _controller: AppBundle:Pruebas:index }
demo:
    path: /demo
    defaults: { _controller: AppBundle:Demo:index }
intranetUsuarios:
    path: /intranet/usuarios
    defaults: { _controller: AppBundle:Intranet:usuarios }

El parámetro path, define la ruta y el parámetro defaults, define el método de la clase controller que va a resolver esa ruta.

Ahora la pregunta es:

¿Pueden convivir dos formas diferentes de definición de rutas dentro de la misma aplicación?

La respuesta es: SI. Ahora veremos mediante un poco de laboratorio, quien predomina y como se comporta el framework.

Escenario de Laboratorio.

Vamos a crear un pequeño escenario de laboratorio para observar cual es el comportamiento general del Framwork ante diferentes pruebas que le puedan crear conflicto.

Ficheros de routing

Se generan los siguientes archivos de routing:

  • /app/config/routing.yml
  • /src/AppBundle/Resources/config/routing.yml

Con el siguiente contenido respectivamente:

/app/config/routing.yml

app:
    resource: "@AppBundle/Controller/"
    type:     annotation
app_route_file:
    resource: "@AppBundle/Resources/config/routing.yml"
    prefix:   /

/src/AppBundle/Resources/config/routing.yml

home:
    path: /
    defaults: { _controller: AppBundle:Default:index }
pruebas:
    path: /pruebas
    defaults: { _controller: AppBundle:Pruebas:index }
demo:
    path: /demo
    defaults: { _controller: AppBundle:Demo:index }
valar:
    path: /valar
    defaults: { _controller: AppBundle:Pruebas:valar }
valar:
    path: /valar
    defaults: { _controller: AppBundle:Default:index }

Ficheros Controller

Se crean los siguientes controller:

  • /src/AppBundle/Controller/DefaultController.php
  • /src/AppBundle/Controller/DemoController.php
  • /src/AppBundle/Controller/PruebasController.php

Con el siguiente contenido respectivamente:

/src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
    public function indexAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('default/index.html.twig', [
            'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..'),
        ]);
    }
}

/src/AppBundle/Controller/DemoController.php

namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DemoController extends Controller
{
    public function indexAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "Contenido enviado desde el front controller (Demo)",
        ]);
    }
}

/src/AppBundle/Controller/PruebasController.php

namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class PruebasController extends Controller
{
    /**
     * @Route("/pruebecilla", name="pruebecilla")
     */
    public function indexAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "Contenido enviado desde el front controller (Pruebas)",
        ]);
    }
    public function valarAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "VALAR MORGULIS",
        ]);
    }
}

Laboratorio 1

Enunciado: Los dos métodos de definición de rutas, pueden convivir correctamente.

Se generan las siguientes URL que resuelven en el mismo método del mismo controller.

  • http://proyectodemo/web/app_dev.php/pruebas (Vía prefix)
  • http://proyectodemo/web/app_dev.php/pruebecilla (Vía annotation)

Resultado: Las dos URL resuelven correctamente al mismo contenido por lo que ambos métodos de definición de rutas conviven correctamente. Esto crea alguna debilidad a nivel de SEO. Se corre el riesgo de generar contenido duplicado. Es necesario organizar y tener un criterio muy claro de definición de rutas.

Laboratorio 2

Enunciado: Determinar el conflicto de dos rutas denominadas igual por métodos diferentes, es decir, vía Annotation o vía Prefix.

Se modifica la ruta annontation «pruebecillas» por «pruebas«, quedando de la siguiente forma:

/src/AppBundle/Controller/PruebasController.php

...
    class PruebasController extends Controller
{
    /**
     * @Route("/pruebas", name="pruebas")
     */
    public function indexAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "Contenido enviado desde el front controller (Pruebas)",
        ]);
    }
    public function valarAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "VALAR MORGULIS",
        ]);
    }
}
...

Se genera el conflicto con la ruta vía Prefix modificandola a :

pruebas:
    path: /pruebas
    defaults: { _controller: AppBundle:Pruebas:valar }

Resultado: Al llamar a la URL http://proyectodemo/web/app_dev.php/pruebas el resultado en pantalla ha sido «VALAR MORGULIS» por lo que la ruta predominante ha sido la definida vía Prefix.

Laboratorio 3

Enunciado. Determinar el conflicto de la definición de dos rutas iguales vía prefix hacia métodos de resolución diferentes.

Se eliminan todas las rutas definidas como Annotation

...
    class PruebasController extends Controller
{
    public function indexAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "Contenido enviado desde el front controller (Pruebas)",
        ]);
    }
    public function valarAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('AppBundle:pruebas:index.html.twig', [
            'texto' => "VALAR MORGULIS",
        ]);
    }
}
...

Se modifica el archivo de routing duplicando la definición de la ruta:

pruebas:
    path: /pruebas
    defaults: { _controller: AppBundle:Pruebas:index }
valar:
    path: /pruebas
    defaults: { _controller: AppBundle:Pruebas:valar }

Resultado: Resuelve la primera ruta, la segunda no ha afectado. El resultado por pantalla es «Contenido enviado desde el front controller (Pruebas) «