Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Success (Flash) Messages

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Our form submits and saves! But... it's not all that obvious that it works... because we redirect to the homepage... and there's not even a success message to tell us it worked! We can do better!

In ArticleAdminController, give the list endpoint route a name="admin_article_list". After a successful submit, we can redirect there. That makes more sense.

... lines 1 - 14
class ArticleAdminController extends AbstractController
{
... lines 17 - 54
/**
* @Route("/admin/article", name="admin_article_list")
*/
public function list(ArticleRepository $articleRepo)
... lines 59 - 65
}

Adding a Flash Message

With that done, I next want to add a "success" message. Like, after I submit, there's a giant, happy-looking green bar on top that says "Article created! You're a modern-day Shakespeare!".

And... great news! Symfony has a feature that's made for this. It's called a flash message. Oooooo. After a successful form submit, say $this->addFlash(). Pass this the key success - we'll talk about that in a moment - and then an inspirational message!

Article Created! Knowledge is power

... lines 1 - 20
public function new(EntityManagerInterface $em, Request $request)
{
... lines 23 - 25
if ($form->isSubmitted() && $form->isValid()) {
... lines 27 - 35
$this->addFlash('success', 'Article Created! Knowledge is power!');
... lines 37 - 38
}
... lines 40 - 43
}
... lines 45 - 67

That's all we need in the controller. The addFlash() method is a shortcut to set a message in the session. But, flash messages are special: they only live in the session until they are read for the first time. As soon as we read a flash message, poof! In a... flash, it disappears. It's the perfect place to store temporary messages.

Rendering the Flash Message

Oh, and the success key? I just made that up. That's sort of a "category" or "type", and we'll use it to read the message and render it. And... where should we read and render the message? The best place is in your base.html.twig layout. Why? Because no matter what page you redirect to after a form submit, your flash message will then be rendered.

Scroll down a little bit and find the block body. Right before this - so that it's not overridden by our child templates, add {% for message in app.flashes() %} and pass this our type: success. Remember: Symfony adds one global variable to Twig called app, which comes in handy here.

Inside the for, add a div with class="alert alert-success" and, inside, print message.

... lines 1 - 15
<body>
... lines 17 - 66
{% for message in app.flashes('success') %}
<div class="alert alert-success">
{{ message }}
</div>
{% endfor %}
... lines 72 - 89
</body>
... lines 91 - 92

Done! Oh, but, why do we need a for loop here to read the message? Well, it's not too common, but you can technically put as many messages onto your success flash type as you want. So, in theory, there could be 5 success messages that we need to read and print... but you'll usually have just one.

Anyways, let's try this crazy thang! Move back so we can create another important article:

Ursa Minor: Major Construction Planned

Hit enter and... hello nice message! I don't like that weird margin issue - but we'll fix that in a minute. When you refresh, yep! The message disappears in a flash... because it was removed from the session when we read it the first time.

Peeking at the Flash Messages

Ok, let's fix that ugly margin issue... it's actually interesting. Inspect element on the page and find the navbar. Ah, it has some bottom margin thanks to this mb-5 class. Hmm. To make this look right, we don't want to render that mb-5 class when there is a flash message. How can we do that?

Back in base.html.twig, scroll up a bit to find the navbar. Ok: we could count the number of success flash messages, and if there are more than 0, do not print the mb-5 class. That's pretty simple, except for one huge problem! If we read the flash messages here to count them, that would also remove them! Our loop below would never do anything!

How can we work around that? By peeking at the flash messages. Copy the class. Then, say app.session.flashbag.peek('success'). Pipe that to the length filter and if this is greater than zero, print nothing. Otherwise, print the mb-5 class.

... lines 1 - 23
<nav class="navbar navbar-expand-lg navbar-dark navbar-bg {{ app.session.flashbag.peek('success')|length > 0 ? '' : 'mb-5' }}">
... lines 25 - 93

This... deserves some explanation. First, the global app variable is actually an object called, conveniently, AppVariable! Press Shift+Shift and search for this so we can see exactly what it looks like.

Before, we used the getFlashes() method, which handles all the details of working with the Session object. But, if we need to "peek", we need to work with the Session directly via the getSession() shortcut. It turns out, the "flash messages" are stored on a sub-object called the "flash bag". This new longer code fetches the Session, gets that "FlashBag" and calls peek() on it.

Ok, let's see if that fixed things! Move back over and click to author another amazing article:

Mars: God of War? Or Misunderstood?

Hit enter to submit and... got it! Flash message and no extra margin.

Next, let's learn how we can do... less work! By bossing around the form system and forcing it to create and populate our Article object so we don't have to.

Leave a comment!

21
Login or Register to join the conversation
Default user avatar
Default user avatar Adrian Max | posted 4 years ago

Why not simply write " {{ app.session.flashBag.peek('success') ? 'yes' : 'no' }} ", instead of using the " |length " method?
A simple "true" or "false" will suffice for this purpose.

Also, is there any quick method to take the "flashBag.peek" after the flashBag was already accessed and cleared?
Or, for this you need to define a variable with the value before the flashBag is accessed, and then query that variable instead of peek?

1 Reply

Hey Adrian Max!

Your solution would work perfectly fine :). I think it's just a matter of preference - the |length adds some extra code, but also might add some clarity. Honestly, even better might be app.session.flashBag.peek('success') is not empty 'yes' : 'no'.

Or, for this you need to define a variable with the value before the flashBag is accessed

Yep, this is what you'll need to do. Once the flashbag is accessed, the values are fully deleted from the session/flashbag - so there's no record of them anywhere anymore. You'll need to track them on your own.

Cheers!

1 Reply
Default user avatar
Default user avatar Gherman Adrian | posted 2 years ago

hello all!

I have a question: why flasbag didn't disapear after redirect? what i'm doing wrong?

Reply

Hey Gherman,

How do you read values from the flash bag? It has a few methods: peekAll() allows you to get the value but not clear it. And all() allow you to get the value and clear the flash bag, i.e. the next time you will load the page - flash message will disappear :)

Cheers!

Reply
Default user avatar
Default user avatar Gherman Adrian | Victor | posted 2 years ago

thank you I was thinking that it disappears from the template automatically in some time (without refreshing page), but i created a JS script for that :) , I'm sorry for disturb

Reply

Hey Gherman,

Ah, I see :) Nope, you need to do some extra work to make it to work this way, like to add a close button on that flash message, or a timeout callback with custom JS code that will close the flash message automatically after some time. By default, it will disappear only after page refresh.

Cheers!

Reply
Default user avatar
Default user avatar Mae Casio | posted 3 years ago

Nice messenge

Reply

Hey Mae,

Thank you for the nice message :)

Cheers!

Reply
Default user avatar
Default user avatar Mae Casio | posted 3 years ago

Nice

Reply
Pascal Avatar

Hey Guys, i have a problem with the flashes , if i dump the {{ dump(app.session.flashBag.all) }} i get an empty array every time. But on the screen the messages is render perfaectly . What is wrong .

Greets Pascal

Reply

Hey Pascal!

That's just the normal behavior of flash messages, they get cleared from the session after being rendered. There is another method under the FlashBag object, IIRC, it's name is peekAll(), what it does is to fetch all messages but without clearing the session

Cheers!

Reply
Laura M. Avatar
Laura M. Avatar Laura M. | posted 4 years ago

How can you overwrite flash messages from FOSUserBundle? For example the flash message that you get after you register your account?

Reply

Hey Laura M.

By using translations. You can watch this episode to get a better idea of how to do it: https://symfonycasts.com/sc...

Cheers!

Reply
Laura M. Avatar
Laura M. Avatar Laura M. | posted 4 years ago

How would you go in order to create dynamic css classes for those messages?
You exemplified only the success message. What if I have also danger/warning types of messages?
I see the flash() function receives only a string as argument.

Reply

Hey Laura M.

You can replicate what Ryan did but for every of your message keys, or, if you want a more generic way you could do something like this


{% for flashKey in app.session.flashBag.keys %}
    <div class="alert alert-block flash-container alert-{{ flashKey }}">
        {% for flash in app.session.flashBag.get(flashKey) %}
            <pp class="text-center">
                {{ flash|trans|raw }}
            </pp>
        {% endfor %}
    </div>
{% endfor %}

(Had to rename the P tag to "PP" because Disqus was trying to render it as HTML)

Cheers!

Reply
Mdr L. Avatar

Hi, Where does the app.flashes comes from ?

Reply
Mdr L. Avatar

Because I didn't see you talk about it previously and when I tried implementing it, it says not existant or under construction

Reply

Hey Max!

Yep, this is the first time we've talked about that. Symfony always adds an "app" variable automatically to every template, which is an object called AppVariable. This has a getFlashes() method on it, which is why you can say app.flashes.

What error do you get exactly when you try to use it?

Cheers!

Reply
Mdr L. Avatar

Hi, it tells me flashes doesn't exist that's why I was wondering

Reply
Mdr L. Avatar

I dumped the messages list and it gave me all of my flashes from the start.
Now when I load my page I have an empty green banner on top, my flashes appear but the green banner wont go away

Reply

Hey @Max!

Can you post your template code? By the way, if you dumped the messages at the start (a smart thing to do), that will actually *read* them from the flash and cause them to be *removed* (because once a flash message is read, it's gone). That would cause it not to be printed below. However, unless there's a problem with your template, the green bar should NOT be shown if there are no flash messages to render.

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.6
        "symfony/console": "^4.0", // v4.1.6
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.1.6
        "symfony/framework-bundle": "^4.0", // v4.1.6
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.6
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.6
        "symfony/validator": "^4.0", // v4.1.6
        "symfony/web-server-bundle": "^4.0", // v4.1.6
        "symfony/yaml": "^4.0", // v4.1.6
        "twig/extensions": "^1.5" // v1.5.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
        "symfony/dotenv": "^4.0", // v4.1.6
        "symfony/maker-bundle": "^1.0", // v1.8.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.6
    }
}
userVoice