Parte 4: Controladores, templates y estilos con Tailwind CSS en Symfony 6
En el tutorial de esta semana veremos como crear nuestro primer controlador, aprenderemos a realizar búsquedas en nuestra base de datos con Doctrine para obtener todos los posts y enviaremos esos datos a una plantilla de Twig para visualizarlos en la web. Además, veremos como instalar Tailwind CSS en Symfony 6 el cual es un conjunto de clases de CSS para facilitarnos el trabajo a la hora de maquetar nuestra web.
👋 ¡Hola! Hasta ahora solo hemos estado picando código y no hemos visto nuestra web en el navegador ya en funcionamiento, pero eso cambia ya en este tutorial. Ahora que tenemos datos y ya he explicado unas cuantas cosas, es hora de meternos de lleno en esas partes que habíamos dejado de lado hasta ahora.
Antes de empezar 🛑. Si te has perdido alguno de los tutoriales de esta serie, aquí te dejo todos los tutoriales escritos hasta ahora:
- Parte 1: Cómo crear una webapp con Symfony 6
- Parte 2: Cómo crear entidades, migraciones y conectarnos a una base de datos con Symfony 6
- Parte 3: Cómo crear datos falsos en una base de datos con Symfony 6
- Parte 4: Controladores, templates y estilos con Tailwind CSS en Symfony 6
- Parte 5: Optimización de queries con Doctrine, ajustes y página de post en Symfony 6
- Parte 6: Autenticación y registro con Symfony 6
Ahora mismo, sí levantamos nuestra web (symfony serve) y vamos al navegador, visualizaremos la página por defecto del Symfony. Pues bien, la idea es cambiar eso y que en la página principal de la web podamos ver el listado con nuestros posts.
Crear un controlador en Symfony 6
El primer paso será crear un controlador. Los controladores son un componente que se encarga de manejar solicitudes HTTP y retornar una respuesta. Un controlador en Symfony 6 es una clase PHP en la cual podemos manejar las rutas, qué métodos son permitidos (GET, POST, etc.), realizar cualquier lógica necesaria, como recuperar datos de una base de datos, procesar formularios y renderizar plantillas para generar una respuesta HTML.
Para crear un controlador, vamos a la terminal y lanzamos el siguiente comando:
php bin/console make:controller
Como en ocasiones anteriores, nos aparecerá el asistente y nos preguntará como queremos llamarlo. Yo lo he nombrado BlogController.
Choose a name for your controller class (e.g. AgreeableKangarooController):
> BlogController
created: src/Controller/BlogController.php
created: templates/blog/index.html.twig
Success!
Next: Open your new controller class and add some pages!
Al finalizar el proceso nos habrá creado dos archivos. El primero es el controlador y el segundo es una plantilla de Twig la cual usaremos más adelante y explicaré como funciona.
El siguiente paso es modificar BlogController para recuperar los posts de nuestra base de datos y mostrarlos en la plantilla. Abrimos el archivo y realizamos las siguientes modificaciones:
<?php
...
use App\Repository\PostRepository;
class BlogController extends AbstractController
{
#[Route('/', name: 'get_posts')]
public function index(PostRepository $postRepository): Response
{
return $this->render('blog/index.html.twig', [
'posts' => $postRepository->findAll()
]);
}
}
En esta clase he realizado varios cambios que son:
Primero he importado use App\Repository\PostRepository; ya que lo necesitaremos para recuperar los datos de los posts de la base de datos.
He modificado la línea #[Route..., la cual se encarga de manejar el enrutamiento. Como primer parámetro he dejado solo el slash lo cual indica que para acceder a este controlador deberemos ir a la página principal de nuestra web. También, he modificado su nombre. El uso de estos nombres los veremos más adelante.
El método index ahora recibe el objeto $postRepository que es una instancia de PostRepository. Esto es lo que se llama inyección de dependencias y cuando lo añadimos como parámetro, Symfony se encarga automáticamente de inicializar el objeto, el cual nos servirá para recuperar los posts de la base de datos.
Por último, se renderiza la plantilla y se retorna por respuesta. El método render, recibe como segundo parámetro un array con información. Aquí podemos enviar la información que queramos y luego utilizarla en la plantilla. Pues bien, aquí he eliminado la clave que existía y he creado una nueva llamada posts.
Esta clave tiene como valor el listado con todos los posts. Como puedes observar, estoy utilizando $postRepository y el método findAll() que retorna todos los elementos de una tabla.
Si quieres más información acerca de los distintos métodos que podemos utilizar para extraer información de la base de datos, te dejo el siguiente enlace:
https://symfony.com/doc/current/doctrine.html#fetching-objects-from-the-database
Ahora que ya tenemos listo el controlador, vamos a editar la plantilla. Antes de seguir, voy a darte una breve explicación de que son las plantillas Twig.
Qué es Twig en Symfony 6 🤔
Twig es un motor de plantillas que se utiliza en Symfony 6 para convertir nuestros archivos HTML en archivos dinámicos. Twig permite escribir HTML y además podemos usar sintaxis especial y variables que se reemplazan en tiempo de ejecución con los datos proporcionados por el controlador.
También, Twig ofrece una gran cantidad de funcionalidades para realizar tareas como iterar sobre colecciones de datos, aplicar filtros y realizar operaciones matemáticas como veremos más adelante.
Si quieres más info sobre Twig, te dejo un enlace a la documentación:
https://symfony.com/doc/current/templates.html
Ahora que ya entendemos un poco mejor Twig, vamos a editar nuestra plantilla. Para ello abrimos el archivo templates/blog/index.html.twig y modificamos el contenido por el siguiente:
{% extends 'base.html.twig' %}
{% block title %}Últimos posts{% endblock %}
{% block body %}
{% for post in posts %}
<h2>
<a href="#">
{{ post.title }}
</a>
</h2>
<p>
{{ post.content }}
</p>
<hr>
{% endfor %}
{% endblock %}
Como puedes observar, esta plantilla extiende de la plantilla base.html.twig. Esta la podemos utilizar de base para las demás plantillas o podemos crear una nueva. En nuestro caso será la que utilizaremos de base para las demás plantillas. En base.html.twig tenemos la estructura de una página con HTML y además se definen bloques que podremos sustituir en las plantillas hijas.
En este caso, sustituimos el bloque title por defecto por uno para esta plantilla en particular.
En el bloque body que será el cuerpo de nuestra página web, recorremos la variable posts que enviamos en el controlador. La podemos recorrer gracias a la sintaxis especial de Twig y vamos generando los distintos posts dinámicamente.
De momento no me he complicado mucho y solo estamos pintando el título y el contenido del post. Si recargamos nuestra página ya podremos ver el listado de posts aunque de forma bastante rudimentaria. Vamos a arreglar eso ahora mismo con Webpack y Tailwind CSS.
Instalar Webpack y Tailwind CSS ⚒️
Antes de nada para el que no lo conozca, voy a explicar que es Webpack y Tailwind CSS y posteriormente lo instalaremos en el proyecto.
Qué es Webpack
Webpack es un empaquetador de módulos de JavaScript para aplicaciones web. Permite a los desarrolladores empaquetar todos los archivos necesarios para una aplicación web, incluyendo JavaScript, HTML, CSS y otros recursos, en un solo archivo o paquete.
Esto hace que sea más fácil y rápido para un navegador cargar y ejecutar la aplicación, ya que solo tiene que descargar un solo archivo en lugar de varios.
Además, Webpack permite la optimización de los archivos empaquetados, como la minimización y compresión de código, para mejorar la velocidad de carga y el rendimiento de la aplicación.
Qué es Tailwind CSS
Tailwind CSS es un framework de estilos de diseño para aplicaciones web. Este framework proporciona una gran cantidad de clases predefinidas para dar estilo a elementos HTML, lo que nos permite aplicar estos estilos en nuestras webs de forma más rápida.
Una vez explicadas estas herramientas, vamos a instalarlas. Para instalar Webpack lanzaremos el siguiente comando:
composer require symfony/webpack-encore-bundle
Con este comando integraremos de forma muy sencilla Webpack en nuestro proyecto. Generará la carpeta asserts que será la carpeta donde guardaremos nuestros archivos CSS y JavaScript y también creará el archivo webpack.config.js con una configuración por defecto para Symfony 6.
El siguiente paso es instalar Tailwind CSS en nuestro proyecto. Para ello he seguido esta guía de la propia documentación de Tailwind, pero también lo explicaré aquí paso a paso:
https://tailwindcss.com/docs/guides/symfony
Para esta parte necesitaremos tener instalado npm, el cual es el sistema de gestión de paquetes por defecto para Node.js. Si no lo tienes instalado, pincha aquí.
Con npm en nuestra máquina, vamos a instalar Tailwind. Para ello lanzamos los siguientes comandos:
npm install -D tailwindcss postcss autoprefixer postcss-loader
npx tailwindcss init -p
El primero instalará Tailwind y otros paquetes necesarios y el segundo generará un archivo de configuración para Tailwind llamado tailwind.config.js en la raíz del proyecto.
Ahora abrimos el archivo webpack.config.js en la raíz del proyecto y añadimos la siguiente línea dentro de Encore junto a las ya existentes:
Encore
// ...
.enablePostCssLoader()
;
El siguiente paso es abrir el archivo tailwind.config.js y modificar su contenido por el siguiente:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./assets/**/*.js",
"./templates/**/*.html.twig",
],
theme: {
extend: {},
},
plugins: [],
}
Una vez tenemos los archivos de configuración listos, vamos a importar Tailwind a nuestros CSS. Abrimos el archivo asserts/styles/app.css en el cual a partir de ahora deberá ir todo nuestro CSS y sustituimos el bloque existente por el siguiente:
@tailwind base;
@tailwind components;
@tailwind utilities;
De esta forma podremos utilizar todos los componentes de Tailwind en nuestra web.
Por último, abrimos una terminal y lanzamos el siguiente comando:
npm run watch
Con este comando, cada vez que realicemos un cambio en las plantillas o el CSS, actualizará el archivo del que bebe nuestra web, el cual se encuentra en public\build\app.css.
Si vamos al navegador y actualizamos la página principal, veremos que la letra y el formato de los títulos ha cambiado porque ya estamos usando Tailwind CSS 💪.
Ahora vamos a utilizar las clases de Tailwind en nuestro proyecto. Para no complicarnos mucho con explicaciones, voy a utilizar una plantilla de ejemplo de un tutorial que escribí sobre como maquetar un blog con Tailwind CSS. Si quieres profundizar más sobre este framework, te dejo en enlace a continuación:
https://cosasdedevs.com/posts/maquetar-blog-tailwindcss/
Maquetar la página principal con Tailwind CSS 🖼️
El primer paso que haremos será añadir unas cuantas configuraciones generales para todas las plantillas, así que abrimos el archivo assets\styles\app.css y añadimos las siguientes líneas junto a lo ya existente:
header > nav > ul > li > a:hover {
@apply text-blue-300;
}
.tags {
@apply inline-block p-2 mx-1 bg-orange-300 text-white uppercase text-xs font-medium;
}
.popular-posts > div > ul > li {
@apply p-1;
}
.popular-posts > div > ul > li > a {
@apply text-blue-400 underline text-lg;
}
.popular-posts > div > ul > li > a:hover {
@apply text-blue-500;
}
footer > ul > li {
@apply py-1 px-4;
}
footer > ul > li > a:hover {
@apply underline;
}
Ahora vamos al archivo templates\base.html.twig y reemplazamos el código existente por el siguiente:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Mi primer blog con Symfony 6{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog==" crossorigin="anonymous" />
</head>
<body>
<header class="bg-gray-900 shadow-md">
<nav class="flex justify-between items-center text-gray-200 px-4 shadow-md sm:py-4 sm:justify-around">
<div class="sm:hidden text-2xl">≡</div>
<ul class="hidden sm:flex justify-between w-64">
<li>
<a href="http://">Inicio</a>
</li>
<li>
<a href="http://">Etiquetas</a>
</li>
<li>
<a href="http://">Acerca de mi</a>
</li>
</ul>
<ul class="flex justify-between w-20">
<li>
<a href="http://">
<i class="fab fa-twitter"></i>
</a>
</li>
<li>
<a href="http://">
<i class="fab fa-facebook-f"></i>
</a>
</li>
<li>
<a href="http://">
<i class="fas fa-rss"></i>
</a>
</li>
</ul>
</nav>
<div class="bg-white text-center">
<div class="text-6xl font-bold">
Mi blog
</div>
<h1 class="text-gray-700 text-xl pb-4">con Symfony</h1>
</div>
</header>
{% block body %}{% endblock %}
<footer class="text-center text-white bg-teal-900 py-4">
<ul class="md:flex justify-center">
<li><a href="http://">Hola</a></li>
<li><a href="http://">Privacidad</a></li>
<li><a href="http://">Términos y condiciones</a></li>
<li>® {{'now'|date('Y')}} Mi blog con Symfony 6 y TailwindCSS 🧑💻</li>
</ul>
</footer>
</body>
</html>
Con estos cambios hemos añadido la cabecera y la barra de navegación. También importamos font awesome la cual es una librería de iconos. Si recargamos la página ya podrás ver que va tomando mejor forma 💪.
Si te fijas dentro de la etiqueta head, tenemos la importación del CSS que generemos con Webpack:
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
Para finalizar, editaremos la plantilla que utilizamos para renderizar nuestros posts. Abrimos el archivo templates\blog\index.html.twig y reemplazamos el código existente por el siguiente:
{% extends 'base.html.twig' %}
{% block title %}Mi blog con Symfony{% endblock %}
{% block body %}
<div class="mt-10 md:flex justify-center">
<div class="max-w-full flex-row justify-center mb-8 md:mr-2">
{% for post in posts %}
<article class="max-w-xl text-center bg-white shadow-md mb-4 p-2">
<a class="block mb-4" href="http://">
<h2 class="text-2xl font-bold">{{post.title}}</h2>
<time class="text-gray-500" datetime="{{post.publicationDate|date("Y-m-d")}}">
{{post.publicationDate|date("Y-m-d H:i:s")}}
</time>
</a>
<p class="text-justify">{{post.content}}</p>
<div class="my-2">
{% for tag in post.tags %}
<div class="tags">#{{tag.name}}</div>
{% endfor %}
</div>
<div class="inline-block w-full align-middle my-2">
<i class="fas fa-comments"></i> <span>{{post.comments|length}} Comentarios</span>
</div>
</article>
{% endfor %}
</div>
<section>
<div class="popular-posts text-center bg-white border-gray-300 mb-4 pb-4 md:w-64 shadow-md">
<div>
<h3 class="font-bold text-2xl text-gray-900 border-b-2 border-gray-100 p-2 mb-2">Recomendados</h3>
</div>
<div>
<ul>
<li>
<a href="http://">Título del post 1</a>
</li>
<li>
<a href="http://">Título del post 2</a>
</li>
<li>
<a href="http://">Título del post 3</a>
</li>
</ul>
</div>
</div>
</section>
</div>
{% endblock %}
Si recargas la página, ahora se verá todo mucho mejor. Además he añadido más información como la fecha de publicación, el contenido, los tags y el número de comentarios por post.
También añadí una sección donde podríamos añadir posts recomendados para que la web parezca más completa.
Y con esto podemos dar por finalizado este tutorial. En el siguiente seguiremos trabajando en la parte visual de la web, crearemos la página para ver un post en particular, añadiremos paginación y optimizaremos queries.
Como siempre os dejo el enlace al repo https://github.com/albertorc87/blog-symfony-tutorial/tree/controladores-templates-estilos-tailwindcss-symfony-6
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 👋.