secture & code

Symfony Live Components. Por qué miles de frontends están perdiendo su trabajo

Las cosas han cambiado mucho desde que yo empecé a trabajar en serio como programador de tecnologías web. 25 años han pasado, y vaya si han cambiado las cosas. Empecé trabajando con un simple fichero PHP que tenía todo el HTML necesario para mostrárselo al navegador. Oh, perdón, que ahora eso se llama Server Side Rendering.

Pero… ¿Por qué tiene ese nombre? La respuesta es porque el desarrollo web se ha bifurcado en dos grandes vertientes: El programador frontend y el programador backend, dos perfiles independientes e irreconciliables para mostrar HTML en la web.

image

Visto en https://www.reddit.com/r/ProgrammerHumor/comments/18r9fu0/theworldwouldbebetterwithplainhtml/

Ahora (y desde ya hace un rato) tenemos Frameworks de Javascript que nos ayudan a mostrar la info que nos da el backend de un modo fácil y rápido. Simplemente tenemos que elegir uno de los 83 frameworks que existen (y contando…) y a trabajar!

Vale, ya basta de sorna ¡danos lo que hemos venido a buscar!

Presentando: Symfony Live Components

Symfony Live Component fue lanzado en febrero de 2022, como un modo de crear interfaces web dinámicas y reactivas con un mínimo de javascript, todo ello empleando el viejo y bien conocido PHP junto con Twig.

Pero antes de hablar directamente de Symfony Live Components, vamos a hablar un poco de los Symfony Twig Components.

Symfony Twig Components

Los Symfony Twig Components son elementos de la interfaz de usuario autocontenidos, hechos con PHP y Twig de un modo modular de tal forma que se puedan reutilizar y mantener facilmente.

Vamos a crear un componente sencillo. Verás qué rápido te haces a la idea:

Para crear un componente normalmente necesitaremos dos cosas: Un fichero PHP…

// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Alert
{
    public string $type = 'success';
    public string $message;
}

… y un fichero Twig

{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ this.type }}">
    {{ this.message }}
</div>

Como eres una persona observadora, te habrás fijado en la similitud entre ambos. El PHP define dos atributos de clase públicos type y message y esos mismos campos están definidos en el fichero Twig. ¡Ah! Y además la clase Alert está marcada con un atributo de PHP AsTwigComponent, que es obligatorio para que la clase se comporte como (¡oh sorpresa!) un componente de Symfony.

Mantén esto en mente porque ahora necesitamos incluir nuestro componente en una página de Twig, su página padre:

{# templates/index.html.twig #}
{#
  Por aquí hay más código... el código de la página vamos
#}
<twig:Alert
    type="error"
    message="This is an error message"
/>
{#
  Y por aquí también
#}

¡Listo! Si te fijas bien, llamamos a nuestro componente como <twig:NombreDelComponenteDelPHP> (hay otra forma de llamar los componentes pero esa me gusta menos, pero sí estás interesado, no tienes más que entrar a la documentación de Symfony Twig Components). Así pues, cuando llamemos al componente, sabremos que la variable type tendrá el valor «error» y la variable message tendrá «This is an error message«

Bueno, esto fue fácil ¿no es así? Pero vamos ahora con un ejemplo un poco más interesante.

Supongamos que tenemos un componente que nos liste los últimos 5 mensajes de un usuario. Vamos a crear ese componente

// src/Twig/Components/LastMessages.php
namespace App\Twig\Components;

use App\Repository\MessageRepository;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class LastMessages {
    public function __construct(private MessageRepository $messageRepository) {}

    public function getMessages(): array {
        return $this->messageRepository->findLast(5);
    }
}

Realmente aquí no hay nada muy raro. Inyectamos el repositorio de los mensajes, ya que Symfony automágicamente se encargará de encontrar la implementación adecuada a esa interfaz. Tenemos también un método que nos devolverá los 5 últimos mensajes.

Y ahora nuestro fichero Twig:

{# templates/components/AllMessages.html.twig #}
<div>
    <h3>All messages</h3>

    {% for message in this.messages %}
        {# doing something fun with the messages! #}
    {% endfor %}
</div>

Toda la magia está en la línea 5: recorremos la variable messages… ¿la variable messages? Recuerda que Twig tiene la capacidad de tratar como variables aquellos métodos que empiecen por get.

Entonces, para resumir, las ventajas de usar Symfony Twig Components serían:

  • Son independientes entre ellos y totalmente modulares
  • La lógica vive en una clase de PHP… y su presentación es el gestor de plantillas Twig
  • El sistema hace optimizaciones de código y cachea resultados, para que no tengamos que machacar nuestra fuente de datos cada vez que los pidamos

Y seguro que te estarás preguntado «¿Pero dónde está la explicación de los Symfony Live Components? ¡A eso he venido!»

Tranquilo, ahora mismo vamos a hablar de los…

Symfony Live Components

Nos permiten crear elementos interactivos como formularios modales o diálogos con datos en tiempo real con PHP y Twig usando peticiones AJAX con un mínimo de programación en Javascript

Para explicar un poco más en detalle lo que son estos componentes, te propongo que hagamos un ejemplo, el más utilizado para explicar la reactividad: Un contador de clics.

Par empezar tenemos dos ficheros:

namespace App\Twig\Components\Counter;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
class Counter
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
}

Este fichero tiene un atributo público $count (que llevará el conteo de los clics) y un método que incrementará ese contador.

El fichero Twig es más pequeño, claro:

<div>
    <button>
        Clicked times: {{ this.count }}
    </button>
</div>

Como veis, muestra un botón y el valor actual del contador.

button click animation rounded

Pues eso, que solo muestra un cero a pesar de que le hacemos clic. Está claro: no hemos puesto nada de código para la lógica.

Seguimos…

Vamos a convertir ese componente de Twig a uno Live…

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class Counter
{
    use DefaultActionTrait;

    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
}

Ya está, hemos cambiado solo dos cosas:

  • Ahora el fichero usa AsLiveComponent (y no AsTwigComponent)
  • Hemos añadido un trait, DefaultActionTrait. No te preocupes mucho por esto; pero básicamente como los Symfony Live Components se comportan como controladores, el componente necesita que se le defina una acción por defecto. Hablaremos un poco más acerca de acciones más adelante.

El fichero Twig también tiene unos pequeños cambios:

<div {{ attributes }}>
    <button
        data-action="live#$render"
    >
        Clicked times: {{ this.count }}
    </button>
</div>

Dos, básicamente:

  • Hemos mostrado el contenido de la variable attributes. Esta variable se añade automáticamente y nos sirve para decirle al HTML que éste será un componente Live.
  • Al botón le hemos dicho que use una acción, la de «re-renderizado» cada vez que se presione.

No te preocupes. Re-renderizar o redibujar el componente no es más que decirle que lo pinte nuevamente.

Pero ahora, si hacemos clic

button click animation rounded

¡Sigue sin cambiar nada! Pero oye, algo sí que ha cambiado. Si Inspeccionas la página, yendo a la pestaña de «Red» verás que se hace una llamada al servidor. El Symfony Live Component está haciendo algo.

El caso es que nuestro componente necesita una llamada principal para que cambie, las llamadas «action».

Vamos a definir nuestro método principal como una action de Live Component a ver qué tal nos va.

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class Counter3
{
    use DefaultActionTrait;

    public int $count = 0;

    #[LiveAction]
    public function increment(): void {
        $this->count++;
    }
}

Ya está, hemos definido que nuestra action será la que tiene el decorador #[LiveAction]

Y ahora debemos llamar a esa action desde el Twig. De este modo:

<div {{ attributes }}>
    <button
        data-action="live#action"
        data-live-action-param="increment"
    >
        Clicked times: {{ this.count }}
    </button>
</div>

Le decimos que estamos definiendo una action con el data-action, y con el data-live-action-param le decimos el método a llamar.

Y, al ejecutar el código, tenemos:

button first click only

Genial… ya casi estamos. ¿Qué sucede? ¿Por qué al primer clic sí que actualiza el texto del botón, pero no sigue incrementando?

El caso es que los componentes, así, de primeras, no preservan el estado. Así que la primera vez vale 0, cuando clicamos vale uno… pero de vuelta a empezar, el valor inicial vuelve a 0 y es cuando le volvemos a hacer clic que cambia a 1… hasta el infinito. Debemos «marcar» la variable $count como que está viva.

Para eso, tenemos el decorador: #[LivePro]

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class Counter
{
    use DefaultActionTrait;

    #[LiveProp]
    public int $count = 0;

    #[LiveAction]
    public function increment(): void {
        $this->count++;
    }
}

El Twig se mantiene igual:

<div {{ attributes }}>
    <button
        data-action="live#action"
        data-live-action-param="increment"
    >
        Clicked times: {{ this.count }}
    </button>
</div>

Y… ¡ahora todo parece que marcha correctamente!

button count 0 to 4

Creo que, para este abrebocas de los Symfony Live Components, está bien llegar hasta aquí. Puedes encontrar más info en la documentación de Symfony que te dejo en los enlaces un poco más abajo.

Espero que hayas disfrutado este artículo tanto como yo he disfrutado al escribirlo. Anda, empieza con los Live Components y no pierdas tu trabajo como programador 😉😉😉

Components de Twig, documentación en la web de Symfony

Componentes Live, documentación en la web de Symfony

Full-Stack

Picture of Gonzalo Payo

Gonzalo Payo

Entusiasta Programador Fullstack que, a la par, le gusta escribir
Picture of Gonzalo Payo

Gonzalo Payo

Entusiasta Programador Fullstack que, a la par, le gusta escribir

We are HIRING!

What Can We Do