Symfony: tutorial 5: los controladores y el enrutamiento

Symfony tutorial controladores

Siguiendo los tutoriales de iniciación a Symfony, aquí extendemos uno de los tutoriales anteriores sobre el enrutamiento. Lo siguiendo un poco más, y generaremos un controlador CRUD con el maker de Symfony. Las peticiones CRUD son el Create, Retrieve, Update y Delete. Estas peticiones las podemos recibir, y controlar, para hacer lo que necesitemos. Las operaciones CRUD son las operaciones básicas que toda unidad de información necesita gestionarse, y ¡Symfony nos trae un generador de código fuente para los CRUDs!

En Symfony Flex se ha mejorado de nuevo otra de sus herramientas, el enrutamiento. Todo el enrutamiento se configura dentro de los controladores con las últimas versiones. Todo, URLs, parámetros, tipo de petición, se puede ya configurar dentro de los mismos ficheros de controladores. Esto es realmente cómodo en el día a día, configuraciones de enrutamiento y los controladores que reciben las las llamadas a la web están en el mismo sitio. Además, por otro podemos listar tanto rutas como controladores mediante la línea de comandos.

En estas últimas versiones del enrutador de Symfony, según rankings independientes a Symfony, disponemos de un enrutamiento 75 veces más rápido que en la versión anterior. Es el enrutador PHP más rápido que hay, de nuevo, otro referente en este mundo.

Si quieres puedes bajar el código para seguir el post desde:
https://github.com/jaimenj/symfony-starter-tutorials

Un poco de teoría

Symfony controladores y el enrutamiento

Para éste tutorial, sino lo sabías quizá ya lo has intuido leyendo lo anterior, pero lo único que hay que saber es que los controladores son lo que realizan las acciones. Es decir, si el navegador pide cada página del sitio web, debe haber un controlador que reciba dicha URL, haciendo lo que tenga que hacer, y devolviendo lo que tenga que devolver.

Hay URLs que corresponden a ficheros, en las que el servidor devolverá el fichero al navegador. Pero incluso podemos simular peticiones a ficheros que no existen en el servidor, y con un controlador, ‘generarlos’ al vuelo y devolverlos al visitante. Digo ‘generarlos’ porque no hace falta ni grabar el fichero a disco, sino crearlo en el momento dentro del controlador y devolverlo como si se tratara de un fichero.

Creando el proyecto de pruebas

En las versiones anteriores a Symfony 5 podíamos hacer lo siguiente:

composer create-project symfony/skeleton symfony-tutorial-5

Ahora con el nuevo comando symfony podemos hacer:

symfony new symfony-tutorial-5

Entramos dentro del directorio nuevo, y le instalamos algunos vendors que vamos a usar en este proyecto:

cd symfony-tutorial-5/
composer require --dev maker
composer require annotations twig orm form validator security-csrf

Arrancamos el servidor local para comenzar a probar cosas con los controladores, antes de Symfony 5 hacíamos:

php bin/console server:start

A partir de symfony 5 tenemos que hacer lo siguiente:

symfony server:start

Si vamos a http://localhost:8000/ no veremos nada más que lo siguiente en nuestro navegador. Eso es que ha ido bien..

Symfony 5 hola mundo inicial

Esta es la página por defecto que nos sale cuando estamos en local. Es una página placeholder que se ve cuando todavía no hemos creado ningún controlador que reciba rutas del navegador.

Recapitulando, comenzamos a enrutar

Para esto simplemente necesitamos crear nuestro primer controlador en este proyecto:

php bin/console make:controller

..y le ponemos un nombre, por ejemplo DefaultController. Esto nos creará el fichero src/Controller/DefaultController.php que es el controlador, y el fichero templates/default/index.html.twig porque he instalado el vendor de Twig para usarlo en la vista. En el controlador podremos ver lo siguiente:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    /**
     * @Route("/default", name="default")
     */
    public function index()
    {
        return $this->render('default/index.html.twig', [
            'controller_name' => 'DefaultController',
        ]);
    }
}

Mirando el código podemos ver que ya con este controlador, tenemos enrutada la URL /default que apunta a este recién generado controlador. Y en la vista tendremos lo siguiente:

{% extends 'base.html.twig' %}

{% block title %}Hello DefaultController!{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">
    <h1>Hello {{ controller_name }}! ✅</h1>

    This friendly message is coming from:
    <ul>
        <li>Your controller at <code><a href="{{ '/home/usuario/projects-src/symfony-starter-tutorials/symfony-tutorial-5/src/Controller/DefaultController.php'|file_link(0) }}">src/Controller/DefaultController.php</a></code></li>
        <li>Your template at <code><a href="{{ '/home/usuario/projects-src/symfony-starter-tutorials/symfony-tutorial-5/templates/default/index.html.twig'|file_link(0) }}">templates/default/index.html.twig</a></code></li>
    </ul>
</div>
{% endblock %}

Sigamos y vamos a hacer cosas.

Rutas con parámetros, enrutamiento de URLs dinámicas

Para esto simplemente podemos poner como hemos visto en tutoriales anteriores los parámetros en las anotaciones. Por ejemplo, si queremos recibir en este controlador un parámetro de forma amigable con la URL /ruta-amigable/{parametro} podemos editar el controlador anterior así:

    /**
     * @Route("/ruta-amigable/{parametro}", name="default")
     */
    public function index($parametro)
    {

De esta forma ya podemos usar la variable $parametro dentro de la acción del controlador.

Los objetos Request and Session

Desde que se recibe la petición del navegador hasta que se devuelve la respuesta, en Symfony ocurren muchas cosas. Hay dos objetos muy interesantes para gestionar lo que nos llega del navegador del visitante. Por ejemplo tenemos el objeto Request que representa la petición al completo. En esta petición tenemos información como parámetros, tanto datos GET como POST, o el tipo de petición, URL, IPs de clientes o forwarding si pasa por proxys, puertos, idioma, etcétera..

En el objeto Session tenemos otra información relacionada con la sesión que ha abierto un usuario en nuestro navegador. Es decir, cuando un usuario llega al servidor, se crea una sessión. Cuando va haciendo varias peticiones, podemos seguirle, sabiendo si es anónimo, si se ha logueado sabemos quién es, podemos almacenar variables de sesión, mensajes, etcétera..

Todo esto entonces lo tenemos modificando el ejemplo anterior así:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

class DefaultController extends AbstractController
{
    /**
     * @Route("/ruta-amigable/{parametro}", name="default")
     */
    public function index($parametro, Request $request, Session $session)
    {
        return $this->render('default/index.html.twig', [
            'controller_name' => 'DefaultController',
        ]);
    }
}

Simplemente, con eso, ya tenemos estos dos objetos en el controlador.

Redirigiendo visitas

Hay dos funcionalidades muy útiles en los controladores. La primera es la redirección, la otra es el forwarding. Con la redirección le devolvemos una respuesta al cliente y éste se redirigirá a otra web de destino que le indiquemos. Por ejemplo, para redirigir a otra ruta se hace así:

return $this->redirectToRoute('route_name2', array('parameter' => $value));

Con el forwarding lo que hacemos es ejecutar un controlador final cuando inicialmente hemos llamado a uno. El visitante recibirá directamente la respuesta del segundo controlador, no sabrá que se le ha hecho forwarding, con lo que su URL no cambiará. Esto se hace así:

$response = $this->forward('App\Controller\DefaultController::otherRoute', array(
    'parameter'  => $value,
));

Generando controladores CRUD automáticamente

Recapitulando, vamos ahora a generar una entidad, y con esta, generaremos el código fuente del controlador y plantillas automáticamente. Vamos entonces a generar una entidad de prueba, ejecutamos:

php bin/console make:entity Address

..y generamos una entidad para guardar una agenda de teléfonos. Podemos llamar a la entidad Address con name y phonenumber como propiedades. Así ya tenemos 2 columnas. Luego configuramos el fichero .env para que tenga base de datos como hemos visto en los tutoriales anteriores sobre bases de datos si los hemos seguido.. y ejecutamos lo siguiente para crear localmente nuestra base de datos:

php bin/console doctrine:database:create
php bin/console doctrine:schema:create

Y ahora vamos a generar el controlador y vistas de este CRUD, ejecutamos:

php bin/console make:crud Address

..y elegimos la entidad Direccion recién creada. Y ahora, ya tenemos la interfaz para gestionar las direcciones. ¡Ya está! ¡Brutal! ¿Cierto? Si esto lo hacemos para 8-10 entidades de un proyecto completo, tenemos un buen punto de partida en muy pocos minutos para una aplicación de gestión. Tendremos los controladores CRUD funcionales, podremos ir directos a maquetar las plantillas y trabajar los controladores para las funcionalidades accesorias.

Si vamos al controlador, podemos ver entonces cómo se reciben y se enrutan los tipos de peticiones:

<?php

namespace App\Controller;

use App\Entity\Address;
use App\Form\AddressType;
use App\Repository\AddressRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
* @Route("/address")
*/
class AddressController extends AbstractController
{
/**
* @Route("/", name="address_index", methods={"GET"})
*/
public function index(AddressRepository $addressRepository): Response
{
return $this->render('address/index.html.twig', [
'addresses' => $addressRepository->findAll(),
]);
}

/**
* @Route("/new", name="address_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$address = new Address();
$form = $this->createForm(AddressType::class, $address);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($address);
$entityManager->flush();

return $this->redirectToRoute('address_index');
}

return $this->render('address/new.html.twig', [
'address' => $address,
'form' => $form->createView(),
]);
}

/**
* @Route("/{id}", name="address_show", methods={"GET"})
*/
public function show(Address $address): Response
{
return $this->render('address/show.html.twig', [
'address' => $address,
]);
}

/**
* @Route("/{id}/edit", name="address_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Address $address): Response
{
$form = $this->createForm(AddressType::class, $address);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();

return $this->redirectToRoute('address_index');
}

return $this->render('address/edit.html.twig', [
'address' => $address,
'form' => $form->createView(),
]);
}

/**
* @Route("/{id}", name="address_delete", methods={"DELETE"})
*/
public function delete(Request $request, Address $address): Response
{
if ($this->isCsrfTokenValid('delete'.$address->getId(), $request->request->get('_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($address);
$entityManager->flush();
}

return $this->redirectToRoute('address_index');
}
}

Una maravilla el generador de CRUDs de Symfony. Podemos arrancar proyectos en muy poco tiempo cosa que sin estas herramientas podríamos tardar muchos días o semanas incluso en tener un prototipo funcional. Ahora si vamos a la línea de comandos podemos listar todos los controladores disponibles con la instrucción de la siguiente imagen:

Symfony controladores y el enrutamiento

Además, si le ponemos el parámetro –show-controllers podremos ir al grano a encontrar los ficheros de los controladores donde está cada cosa:

php bin/console debug:router --show-controllers

Ahora sí que para terminar sólo me queda remitirte a la documentación oficial para seguir avanzando sobre este tema:
https://symfony.com/doc/current/routing.html

Dejo los códigos fuentes de este codekata en GitHub con el curso completo:
https://github.com/jaimenj/symfony-starter-tutorials

Ahora sí, otro día más.. ¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *