Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Flash Message & Rich vs Anemic Models

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

After we submit a form successfully, we always redirect. Often times, we'll also want to show the user a success message so they know everything worked. Symfony has a special way to handle this: flash messages.

To set a flash message, before redirecting, call $this->addFlash() and pass, in this situation, success. For the second argument, put the message that you want to show to the user, like 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
}
}

The success key could be anything... it's kind of like a "category" for the flash message... and you'll see how we use that in a minute.

Flash messages have a fancy name, but they're a simple idea; Symfony stores flash messages in the user's session. What makes them special is that Symfony will remove the message automatically as soon as we read it. They're like self-destructing messages. Pretty cool.

Reading Flash Messages

So... how do we read them? The way I like to do it is by opening up my base template - base.html.twig - and reading and rendering them here. Let's put it right after the navigation but before the {% block body %}. Say {% for message in %}. Then, we want to read out any success category flash messages we might have. To do this, we can leverage the one global Twig variable in Symfony: app. This has several methods on it, like environment, request, session, the current user, or one called app.flashes. Pass this the category (in our case,success). As I mentioned, this could be anything. If you put dinosaur as the key in a controller, then you'd read the dinosaur messages out here. Finish with {% 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

Typically, you'll only have one success message in your flash at a time, but technically you can have multiple. That's why we're looping over them.

Inside of this, render a <div> with class="alert alert-success" so it looks like a happy message. Then, print out message.

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

So if this works correctly, it will read all of our success flash messages and render them. And once they've been read, Symfony will remove them so that they won't render again on the next page load. By putting this in the base template, we can now set flash messages from anywhere in our app and they'll be rendered on the page. Pretty cool.

Watch. Head back to our page, upvote and... beautiful! We'll probably want to remove this extra margin in a real project, but we'll leave it for now.

Making our Entity Class Smarter

All right, look back at MixController. The logic for doing our "up" and "down" voting is pretty simple... but I think it can be better. Try this! Open up VinylMix... and scroll down to setVotes(). Right after this, just to keep things organized, create a new public function called upVote() and return void. Inside, say $this->votes++. Copy that, and create a second method which we'll call - you guessed it - downVote()... with $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
}

Thanks to these methods, in MixController, instead of having $mix->setVotes() set to $mix->getVotes() + 1, we can just say $mix->upVote()... and $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
}
}

Now that's nice. Our controller reads much more clearly, and we've encapsulated the upVote() and downVote() logic into our entity. If we head over and refresh, it still works.

Smart vs Anemic Models

This highlights an interesting topic. We've now added four custom methods to our entity: two that help read the data in a special way, and two that help set data. When we run make:entity, it creates getter and setter methods for every single property. That's nice, because it makes our entity infinitely flexible. Anyone from anywhere can read or set any property. But sometimes, you might not want or need that. For example, do we really want a setVotes() method? Is there really a use case in our code for something to set the vote count to any number it wants? Probably not. We'll likely only need upVote() and downVote(). I will keep the setVotes() method... though, because we use it when we generate our dummy VinylMix object.

But, in general, by removing unnecessary getter and setter methods in your entity and replacing them with more descriptive methods like upVote(), downVote(), getVoteString(), or getImageUrl() - methods that fit your business logic - you can, little by little, give your entities more clarity. Our upVote() and downVote() methods are super clear and descriptive. Someone calling these doesn't even need to know or care how they work internally.

Entities that only have getter and setter methods are sometimes called "anemic models". Entities that remove these and replace them with specific methods for your business logic are sometimes called "rich models". Some people take this to an extreme and have almost no getter or setter methods. Here at SymfonyCasts, we tend to be pragmatic. We usually do have getter and setter methods, but we always look for ways to be more descriptive, like by adding upVote() and downVote().

Next, let's install an awesome library called DoctrineExtensions. This is a magic library full of superpowers, like automatic timestampable, and slug creation behaviors.

Leave a comment!

0
Login or Register to join the conversation
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
    }
}