Friday, August 14, 2009

Using Zend Framework's FlashMessenger Action Helper

We often want to provide users feedback from their actions. Zend Framework includes the FlashMessenger Action Helper for this purpose.
A gentle trickle of questions appear on the Zend Framework mailing lists concerning the exact usage of the FlashMessenger, so I'm going to go over the details here. Hopefully we'll get all those Zend Framework FlashMessenger questions answered.
We will also introduce the Noumenal PHP Library's FlashMessenger View Helper. This encapsulates retrieving flash messages and adds the option to send different levels of message (e.g. "notice", "warning", "error").
The Noumenal PHP Library is available on GitHub.

Overview of the FlashMessenger

Zend Framework's FlashMessenger Action Helper is meant to allow immediate feedback on the status of a user's actions: "Your profile was updated", "Your message was sent", "Your junk mail has been deleted".
Here's an example of the sort of thing from Google's Gmail:
Example of a FlashMessenger
style message from Google's Gmail
Basic usage of the FlashMessenger couldn't be simpler. In your action controller, you set messages via the addMessage() method:
$flashMessenger = $this->_helper->FlashMessenger;
$flashMessenger->addMessage('We did something in the last request');
or via its proxy, the FlashMessenger's direct():
$this->_helper->FlashMessenger('Message stored until needed');
( Action helpers can each define their own direct() as a short cut to their core functionality.)
Under normal circumstances you'd collect any messages when you want to display them:
//Collect array of messages for display.
$aMessages = $this->_helper->FlashMessenger->getMessages();
And that is all there is to it. Your user performs some action, e.g. sending their message; you redirect them somewhere sensible and let them know it was successful.
Note, however, the "Under normal circumstances". There are a couple of wiggles that can catch you out. We'll go over these as way of introduction to the Noumenal PHP Library's FlashMessenger View Helper, which handles these.

Messages are stored until the FlashMessenger is next instantiated

The FlashMessenger stores its messages in a Zend_Session namespace. The data in that namespace is collected each time the FlashMessenger is instantiated (i.e. in the constructor). The data in the namespace is also cleared (via unset()) at the same time. This can cause problems for people when they wish to add an extra action in between setting the messages and retrieving them. If on the extra request they accidently instantiate the FlashMessenger whilst not wanting to collect the messages then they find that they are not there when they do want them later.
The rule here is simple. Contrary to the bad example in the documentation, which assigns the FlashMessenger to an instance variable in the controller's init(), do not get a reference to the FlashMessenger until you actually need it.

There's also a getCurrentMessages() Method

Sometimes we want to send users a message not for the next request but for the current one. The obvious example of this is a failed form validation. Here we output the form with error messages and ask the user to resubmit. Zend_Form has Decorators specifically for displaying form and element level errors, but beyond this it's good on the consistency front to send a message via the normal messaging channel to let the users know that there was a problem.
Calling the regular getMessages() method here won't work. This only returns messages which were stored in the appropriate ZendSession namespace when the FlashMessenger was instantiated. Since any messages added this request were not in the ZendSession namespace at that time (because the FlashMessenger was instantiated in order to add the messages) they won't be returned by getMessages().
For just this use-case, the FlashMessenger also provides a getCurrentMessages() method (and a related family of current methods) which returns those messages set on the current request.

The Noumenal PHP Library's FlashMessenger View Helper

The FlashMessenger View Helper encapsulates the message retrieval side of our interactions with the Zend Framework's FlashMessenger Action Helper. It checks for messages from both previous requests and the current request, so it satisfies our use-case from above. It also interprets key-value pairs, rather than a simple strings, passed to the FlashMessenger as specifying a message-level.
Setting up the View Helper
The first thing to do is to add the call to echo the FlashMessenger View Helper to your layout script, say above the main content:
<html>
<head></head>
<body>
    <div id="hd">
        <!-- Header -->
    </div>

    <div id="bd">
        <div>
            <!-- Main Content -->
            <?php echo $this->flashMessenger(); ?>
            <?php echo $this->layout()->content; ?>
        </div>
        <div>
            <!-- Sidebar -->        
        </div>
    </div>

    <div id="ft">
        <!-- Footer -->
    </div>
</body>
</html>
Since the layout script is rendered so late in the dispatch process, putting the call the the FlashMessenger View Helper here eliminates most of the risk that you'll accidently instantiate the FlashMessenger Action Helper when you don't mean to. (Of course this is still possible but if you're going out of your way to instantiate it then we can assume that you'll know to handle any messages that still need to be displayed.)
Adding Messages (with levels) to the FlashMessenger
Adding messages to the FlashMessenger proceeds via the HelperBroker unchanged. Add simple string messages as you normally would:
$this->_helper->FlashMessenger('Your message was sent.');
If you want to specify a message-level pass a key-value pair instead:
$this->_helper->FlashMessenger(
    array('error' => 'There was a problem with your form submission.') 
);
By default, the FlashMessenger View Helper will render the message within a p tag with a class attribute of the message-level key.
Advanced Options to the FlashMessenger View Helper
You can specify the default message-level by passing a string value as the first parameter to flashMessenger(). The default message-level is 'warning'. To change this to 'notice', in your layout do:
<?php echo $this->flashMessenger('notice'); ?>
This applies to simple string messages only. If key-value pairs are passed to the FlashMessenger, any message-level key will override the default value set here.
Finally, you can override the default message template by passing a format specifier string (suitable for the printf family of functions) as the second parameter. The default template is <p class="%s">%s</p> (where the message-level and message fill the respective placeholders).
As an example, if you regularly expected multiple flash messages, you could render messages as an unordered list by doing this in your layout:
<ul>
    <?php echo $this->flashMessenger('notice', '<li class="%s">%s</li>');?>
</ul>
Here we have also set the default message-level to 'notice'.
The FlashMessenger View Helper is part of the Noumenal PHP Library and is available on GitHub.

17 comments:

Erik said...

Great article, made it a lot cleared to me what the flashmessenger is supposed to do.

Carlton Gibson said...

Glad it was of help Erik! Let me know what you make of the View Helper.

Guy said...

Excellent class - just in time, too! I am building one of my first ZF applications and I was surprised that there is no View Helper to go along with the Action Helper - so now there is :) Thank you very much!

Carlton Gibson said...

Hey Guy,

Glad you found it useful!

(Stay tuned as there is an updated version in the pipeline for after Christmas.)

Menno said...

It should be a standard part of the framework I think! when I first read about FlashMessenger I was excited, but got disappointed when I saw the examples telling how to pull the data into the view. This is the missing piece :)

What about your updated version?

Carlton Gibson said...

Hey Menno,

Glad you liked it.

1. Updated version is coming -- work is taking priority right now (hence all quiet on the blog front) but stay tuned.

2. I've offered to contribute the view helper to the framework but don't have the time to push it myself and haven't received anything in the way of encouragement from the ZF team. (In fairness to them, they're short-staffed and have much to do.)

dave morris said...

I'm trying to use the View Helper you described in this blog post, but I'm getting an error that I cannot seem to figure out:

Fatal error: Uncaught exception 'Zend_Loader_PluginLoader_Exception' with message 'Plugin by name 'FlashMessenger' was not found in the registry; used paths: Zend_View_Helper_Navigation_: Zend/View/Helper/Navigation/ Zend_View_Helper_: Zend/View/Helper/;C:/zend/Apache2/htdocs/portal/application/views\helpers/' in C:\zend\ZendServer\share\ZendFramework\library\Zend\Loader\PluginLoader.php:406 Stack trace: #0 C:\zend\ZendServer\share\ZendFramework\library\Zend\View\Abstract.php(1118): Zend_Loader_PluginLoader->load('FlashMessenger') #1 C:\zend\ZendServer\share\ZendFramework\library\Zend\View\Abstract.php(569): Zend_View_Abstract->_getPlugin('helper', 'flashMessenger') #2 C:\zend\ZendServer\share\ZendFramework\library\Zend\View\Abstract.php(336): Zend_View_Abstract->getHelper('flashMessenger') #3 [internal function]: Zend_View_Abstract->__call('flashMessenger', Array) #4 C:\zend\Apache2\htdocs\portal\application\layouts\scripts\layout.phtml(40): Zend_View->flashMessenger() #5 C:\zend\ZendServer\share\ZendFramework\library\Ze in C:\zend\ZendServer\share\ZendFramework\library\Zend\Loader\PluginLoader.php on line 406


Let me know if you have seen something like this before. Thanks for your help,

Dave

dave morris said...

It looks like it was just a typo. I'm sorry to bother you here. This is a fantastic library, I'm loving it already.

Carlton Gibson said...

Hi Dave,

The problem is that you've not registered the Noumenal__View_Helper_ prefix path with your view.

Add:

$view->addHelperPath('Noumenal/View/Helper','Noumenal_View_Helper');

Wherever you're configuring your view in bootstrap (or indeed anywhere prior to rendering your layout). Check the README in the download package for a little more detail.

Hope that helps.
Regards,
Carlton

Carlton Gibson said...

Hey Dave,

no problem! I wouldn't have written about it if it was a bother. :-)

Glad you're liking the library. Stay tuned, there is much more to come, I promise.

(Big product launch in the next two months, so after that I'd guess...)

Simon said...

Hi! Thank you so much for this. I've used my own implementation of FlashMessenger in the past, but I suspect the way I went about it was not ideal. I downloaded your code, plugged it in, and it did EXACTLY what I wanted. Thank you! This is also much easier to follow than the ZF documentation.

Carlton Gibson said...

Hey Simon. Glad you liked it!

Samuele said...

Hi,

Look that you did a very good job but I'm not able to let it work...

I put your folder: Noumenal in the folder where the are the Zend library... ( in the same folder of Mail, Acl, Layout... )


In my bootstrap I have:

registerNamespace('Noumenal_');

$view = new Zend_View;
$view->addHelperPath('Noumenal/View/Helper','Noumenal_View_Helper');
}
}



And in my layout.phtml I have:



flashMessenger(); ?>

layout()->content; ?>



What's wrong?

It give those errors:
Fatal error: Uncaught exception 'Zend_Loader_PluginLoader_Exception' with message 'Plugin by name 'FlashMessenger' in C:\xampp\php\Zend\Controller\Plugin\Broker.php on line 336
( ! ) Zend_Loader_PluginLoader_Exception: Plugin by name 'FlashMessenger' was not found in the registry; used paths: Zend_View_Helper_: Zend/View/Helper/;C:/xampp/htdocs/dev.sospreventivi.it/application/views\helpers/ in C:\xampp\php\Zend\Loader\PluginLoader.php on line 412


Thanks again...
Samuele

Carlton Gibson said...

Hi Samuele,

You need to put the Noumenal folder alongside the Zend folder (not inside it).

So...

lib/
Noumenal/
Zend/

Hope that helps.

Rowinson Gallego said...

It was so useful.. Thank you so much!

red said...

I've looked at source code and saw that you can use the session namespace. So an array structure is not needed.

From controller:

$this->_helper->flashMessenger->setNamespace('notice')->addMessage('This is a notice');

From view/viewhelper:

$this->_getFlashMessenger()->setNamespace('notice')->getMessages();

Carlton Gibson said...

@red, yes, as always, there's more than one way to do it.

The trouble with your way is that you'd need to check each message-type namespace for a possible message.

That would get messy quite quickly.

Up to you though. I hope you like the helper.