Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Mensaje flash y modelos ricos vs anémicos

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Después de enviar un formulario con éxito, siempre redirigimos. A menudo, también querremos mostrar al usuario un mensaje de éxito para que sepa que todo ha funcionado. Symfony tiene una forma especial de manejar esto: los mensajes flash.

Para establecer un mensaje flash, antes de redirigir, llama a $this->addFlash() y pasa, en esta situación, success. Para el segundo argumento, pon el mensaje que quieres mostrar al usuario, como Vote counted!.

... lines 1 - 12
class MixController extends AbstractController
{
... lines 15 - 44
public function vote(VinylMix $mix, Request $request, EntityManagerInterface $entityManager): Response
{
... lines 47 - 53
$entityManager->flush();
$this->addFlash('success', 'Vote counted!');
... lines 56 - 59
}
}

La clave success puede ser cualquier cosa... es una especie de "categoría" para el mensaje flash... y verás cómo la utilizamos en un minuto.

Los mensajes flash tienen un nombre elegante, pero son una idea sencilla; Symfony almacena los mensajes flash en la sesión del usuario. Lo que los hace especiales es que Symfony eliminará el mensaje automáticamente en cuanto lo leamos. Son como mensajes que se autodestruyen. Bastante chulo.

Lectura de mensajes Flash

Entonces... ¿cómo los leemos? La forma en que me gusta hacerlo es abriendo mi plantilla base -base.html.twig - y leyéndolos y renderizándolos aquí. Pongámoslo justo después de la navegación pero antes de {% block body %}. Digamos {% for message in %}. Entonces, queremos leer los mensajes flash de la categoría success que podamos tener. Para ello, podemos aprovechar la única variable global Twig de Symfony: app. Ésta tiene varios métodos, como environment, request, session, el actual user, o uno llamado app.flashes. Pásale la categoría (en nuestro caso,success). Como ya he dicho, puede ser cualquier cosa. Si pusieras dinosaur como clave en un controlador, entonces leerías los mensajes de dinosaur aquí. Termina con {% endfor %}.

... lines 1 - 19
<body>
<div class="mb-5">
... lines 22 - 57
{% for message in app.flashes('success') %}
... lines 59 - 61
{% endfor %}
</div>
... lines 64 - 84
</body>
... lines 86 - 87

Normalmente, sólo tendrás un mensaje de éxito en tu flash a la vez, pero técnicamente puedes tener varios. Por eso estamos haciendo un bucle sobre ellos.

Dentro de esto, renderiza un <div> con class="alert alert-success" para que parezca un mensaje de felicidad. Luego, imprime message.

... lines 1 - 57
{% for message in app.flashes('success') %}
<div class="alert alert-success">
{{ message }}
</div>
{% endfor %}
... lines 63 - 87

Así, si esto funciona correctamente, leerá todos nuestros mensajes flash success y los renderizará. Y una vez leídos, Symfony los eliminará para que no se vuelvan a renderizar en la siguiente carga de la página. Al poner esto en la plantilla base, ahora podemos establecer mensajes flash desde cualquier parte de nuestra aplicación y se renderizarán en la página. Muy bonito.

Observa. Vuelve a nuestra página, sube la nota y... ¡guapa! Probablemente querremos eliminar este margen extra en un proyecto real, pero lo dejaremos por ahora.

Hacer más inteligente nuestra clase de entidad

Muy bien, vuelve a mirar a MixController. La lógica para hacer nuestra votación "arriba" y "abajo" es bastante sencilla... pero creo que puede ser mejor. ¡Prueba esto! AbreVinylMix... y baja hasta setVotes(). Justo después de esto, sólo para mantener las cosas organizadas, crea un nuevo public function llamado upVote() y devuelve void. Dentro, di $this->votes++. Copia eso, y crea un segundo método que llamaremos -lo has adivinado- downVote()... con $this->votes--.

... lines 1 - 9
class VinylMix
{
... lines 12 - 116
public function upVote(): void
{
$this->votes++;
}
public function downVote(): void
{
$this->votes--;
}
... lines 126 - 141
}

Gracias a estos métodos, en MixController, en lugar de tener $mix->setVotes()para $mix->getVotes() + 1, podemos decir simplemente $mix->upVote()... y $mix->downVote().

... lines 1 - 12
class MixController extends AbstractController
{
... lines 15 - 44
public function vote(VinylMix $mix, Request $request, EntityManagerInterface $entityManager): Response
{
... line 47
if ($direction === 'up') {
$mix->upVote();
} else {
$mix->downVote();
... lines 52 - 59
}
}

Eso está muy bien. Nuestro controlador se lee con mucha más claridad, y hemos encapsulado la lógica de upVote() y downVote() en nuestra entidad. Si nos dirigimos y refrescamos, sigue funcionando.

Modelos inteligentes frente a modelos anémicos

Esto pone de manifiesto un tema interesante. Ahora hemos añadido cuatro métodos personalizados a nuestra entidad: dos que ayudan a leer los datos de forma especial, y dos que ayudan a establecer los datos. Cuando ejecutamos make:entity, se crean métodos getter y setter para cada una de las propiedades. Eso está muy bien, porque hace que nuestra entidad sea infinitamente flexible: cualquiera, desde cualquier lugar, puede leer o establecer cualquier propiedad. Pero a veces, puede que no quieras o necesites eso. Por ejemplo, ¿realmente queremos un método setVotes()? ¿Hay realmente un caso de uso en nuestro código para que algo establezca el recuento de votos a cualquier número que quiera? Probablemente no. Es probable que sólo necesitemos upVote() y downVote(). Yo mantendré el método setVotes()... sin embargo, porque lo utilizamos cuando generamos nuestro objeto ficticio VinylMix.

Pero, en general, si eliminas los métodos getter y setter innecesarios en tu entidad y los sustituyes por métodos más descriptivos como upVote(), downVote(),getVoteString(), o getImageUrl() -métodos que se ajustan a tu lógica de negocio- puedes, poco a poco, dar más claridad a tus entidades. Nuestros métodos upVote() ydownVote() son súper claros y descriptivos. Alguien que los llame no necesita saber ni preocuparse de cómo funcionan internamente.

Las entidades que sólo tienen métodos getter y setter se denominan a veces "modelos anémicos". Las entidades que las eliminan y las sustituyen por métodos específicos para su lógica de negocio se denominan a veces "modelos ricos". Algunas personas llevan esto al extremo y casi no tienen métodos getter o setter. Aquí, en SymfonyCasts, tendemos a ser pragmáticos. Normalmente tenemos métodos getter y setter, pero siempre buscamos formas de ser más descriptivos, como añadir upVote() y downVote().

A continuación, vamos a instalar una impresionante biblioteca llamada DoctrineExtensions. Se trata de una biblioteca mágica llena de superpoderes, como los comportamientos automáticos de creación de marcas de tiempo y babosas.

Leave a comment!

2
Login or Register to join the conversation

First of all, I wanted to thank you for this fantastic video course and take the opportunity to ask if it's just my feeling or is there a chapter dedicated to forms and data manipulation with forms completely missing? Or maybe there is but in some other video course, thanks again for all your work.

Reply

Hey @pasquale_pellicani

Thanks for your kind words. There's a tutorial specific to Symfony Forms, but it's built on Symfony 4, however most (if not all) of the concepts are still relevant, and nothing important has changed in the Symfony Forms component
https://symfonycasts.com/screencast/symfony-forms

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.7", // v3.7.0
        "doctrine/doctrine-bundle": "^2.7", // 2.7.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.12", // 2.12.3
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.2", // v6.2.6
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
        "symfony/asset": "6.1.*", // v6.1.0
        "symfony/console": "6.1.*", // v6.1.2
        "symfony/dotenv": "6.1.*", // v6.1.0
        "symfony/flex": "^2", // v2.2.2
        "symfony/framework-bundle": "6.1.*", // v6.1.2
        "symfony/http-client": "6.1.*", // v6.1.2
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
        "symfony/runtime": "6.1.*", // v6.1.1
        "symfony/twig-bundle": "6.1.*", // v6.1.1
        "symfony/ux-turbo": "^2.0", // v2.3.0
        "symfony/webpack-encore-bundle": "^1.13", // v1.15.1
        "symfony/yaml": "6.1.*", // v6.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.1.*", // v6.1.0
        "symfony/maker-bundle": "^1.41", // v1.44.0
        "symfony/stopwatch": "6.1.*", // v6.1.0
        "symfony/web-profiler-bundle": "6.1.*", // v6.1.2
        "zenstruck/foundry": "^1.21" // v1.21.0
    }
}
userVoice