gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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 | |
} |
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.
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.
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.
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!
hello all!
I have a question: why flasbag didn't disapear after redirect? what i'm doing wrong?
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!
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
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!
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
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!
How can you overwrite flash messages from FOSUserBundle? For example the flash message that you get after you register your account?
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!
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.
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!
Because I didn't see you talk about it previously and when I tried implementing it, it says not existant or under construction
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!
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
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!
// 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
}
}
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?