Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Success (Flash) Messages
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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.
Show Lines
|
// ... lines 1 - 14 |
class ArticleAdminController extends AbstractController | |
{ | |
Show Lines
|
// ... lines 17 - 54 |
/** | |
* @Route("/admin/article", name="admin_article_list") | |
*/ | |
public function list(ArticleRepository $articleRepo) | |
Show Lines
|
// ... 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
Show Lines
|
// ... lines 1 - 20 |
public function new(EntityManagerInterface $em, Request $request) | |
{ | |
Show Lines
|
// ... lines 23 - 25 |
if ($form->isSubmitted() && $form->isValid()) { | |
Show Lines
|
// ... lines 27 - 35 |
$this->addFlash('success', 'Article Created! Knowledge is power!'); | |
Show Lines
|
// ... lines 37 - 38 |
} | |
Show Lines
|
// ... lines 40 - 43 |
} | |
Show Lines
|
// ... 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
.
Show Lines
|
// ... lines 1 - 15 |
<body> | |
Show Lines
|
// ... lines 17 - 66 |
{% for message in app.flashes('success') %} | |
<div class="alert alert-success"> | |
{{ message }} | |
</div> | |
{% endfor %} | |
Show Lines
|
// ... lines 72 - 89 |
</body> | |
Show Lines
|
// ... 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.
Show Lines
|
// ... lines 1 - 23 |
<nav class="navbar navbar-expand-lg navbar-dark navbar-bg {{ app.session.flashbag.peek('success')|length > 0 ? '' : 'mb-5' }}"> | |
Show Lines
|
// ... 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.
27 Comments
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 Symfonycasts colleagues,
I would like to know how we can change the below query in order to see the list of records only If the current user has created those records or has admin role. ?
This is the query I made so far ?
public function findAllProjectsWithBusinCase(bool $hasbusinesscase ): array
{
$queryBuilder = $this->createQueryBuilder('proj')
->orderBy('proj.name', 'ASC');
if($hasbusinesscase) {
$queryBuilder->andWhere('proj.has_business_case = :hasbusinesscase and proj.creator = :user' )
->setParameter('hasbusinesscase',$hasbusinesscase);
}
return $queryBuilder
->getQuery()
->getResult();
;
}
Best regards.
Benoit
Hey @Benoit-L
You have half of the logic already done (checking if the user created the record). You're only missing checking for the admin role. You can inject the Security service into your class Symfony\Component\Security\Core\Security
and call isGranted('ROLE_ADMIN');
I'd do something like this:
$qb = $this->createQueryBuilder('p');
if (!$this->security->isGranted('ROLE_ADMIN')) {
$qb->andWhere('p.creator = :user')
->setParameter('user', $user);
}
...
Cheers!
I added this in the controller
in the constructor
public function __construct(
private Security $security,
){
}
in the function where I want to display the records depending on the role of the current user
$user = $this->security->getUser();
$hasAdminAccess = $this->isGranted('ROLE_ADMIN');
if ($hasAdminAccess) {
$allprojects = $projectsrepository->findAll();
}
else {
$allprojects = $projectsrepository->findBy(['creator' => $user]);
}
// $allprojects = $projectsrepository->findAll();
return $this->render('projects/listofprojects.html.twig',['allprojects' => $allprojects]);
Cool, it should work. Only one detail, be careful when calling findAll()
, if it's a big table you may exhaust the memory and get an error
Cheers!
Thanks, then I will add the paginator functionality that should help.
Best regards.
Benoit
Yes, you can use a paginator, or Doctriner's iterator https://www.doctrine-project.org/projects/doctrine-orm/en/3.1/reference/batch-processing.html
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 Mae,
Thank you for the nice message :)
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!
Hi, Where does the app.flashes comes from ?
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!
Hi, it tells me flashes doesn't exist that's why I was wondering
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!
"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
"doctrine/annotations": "^1.0", // 1.10.2
"doctrine/doctrine-bundle": "^1.6.10", // 1.10.2
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
"doctrine/orm": "^2.5.11", // v2.7.2
"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
"phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.0
"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/cache": "^3.3|^4.0", // v4.1.6
"symfony/console": "^4.0", // v4.1.6
"symfony/flex": "^1.0", // v1.21.6
"symfony/form": "^4.0", // v4.1.6
"symfony/framework-bundle": "^4.0", // v4.1.6
"symfony/property-access": "^3.3|^4.0", // v4.1.6
"symfony/property-info": "^3.3|^4.0", // v4.1.6
"symfony/security-bundle": "^4.0", // v4.1.6
"symfony/serializer": "^3.3|^4.0", // v4.1.6
"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/stopwatch": "^3.3|^4.0", // v4.1.6
"symfony/var-dumper": "^3.3|^4.0", // v4.1.6
"symfony/web-profiler-bundle": "^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?