Thursday, June 17, 2010

Action Helpers, Part 1: FlashMessenger

If you've done any reading on the Zend Forums, you may have noticed that extending the base controller class is frowned upon. The recommended solution is to use Action Helpers. If you haven't read Mathew Weier O'Phinney's definitive article on how action helpers work, you may want to do that first. I was still pretty confused after reading that article, but I'm starting to get it now.

This three part series will first cover the built-in FlashMessenger action helper, the recommended tool for passing messages to the user after page redirects. Next we'll set up application logging using Zend_Log through an action helper. Finally, I'll share a History action helper which stores the last 10 actions in a session variable and add a Back button to the site template which points to the calling page. Who knows, there may be more parts to this series later, because Action Helpers are just so incredibly useful.

Registered Action Helpers run preDispatch() after the controller init but before any action and postDispatch() after the controller action. Kevin Schroeder published a good simplified diagram of the Zend dispatch loop, if you need a visual reference. They can be set up application-wide by adding them to the bootstrap or specifically added to any controller in its init function. However you can also use action helpers on an as-needed basis, easily calling them from anywhere in the application.

I picked the FlashMessenger to start with because it is something I wish I'd known about when I started developing ZF applications. There is already an excellent article about the FlashMessenger by Noumenal over at A Fading Tan and I recommend you read it, but this is for people who like more complete examples. One time the FlashMessenger is very helpful is when a user enters data into a form and you want to redirect back to another page but indicate that the data was received. So let's set up a very simple example contoller with an index page and a comment page where the comment page contains a form. When the user submits the form, she is redirected back to the index page. If the comment field is empty, she will stay on the form page and get an error message both through the validator and the FlashMessenger. Here is some code:

/application/controllers/ExampleController.php:
<?php

class ExampleController extends Zend_Controller_Action {

public function indexAction() {}

public function commentAction() {
$form = new Zend_Form();

$comment = new Zend_Form_Element_Textarea('comment');

$form->addElement('textarea', 'comment');
$form->addElement('submit', 'submit');

if ($this->_request->isPost()) {
if ($comment->isValid($this->_request->comment)) {
$this->_redirect('/example/index');
}
}
$this->view->form = $form;
$this->view->form = $form;
}

}
/application/views/scripts/example/index.php:
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc mauris justo, faucibus vitae sagittis sed, laoreet rhoncus sapien. Suspendisse ac nunc vitae arcu tincidunt dictum. Mauris interdum, nisi ac convallis consequat, velit odio porttitor quam, at iaculis risus orci quis tellus. Nullam rutrum tempor mauris, id imperdiet velit semper sit amet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus eleifend mollis cursus. Quisque et velit dolor. Sed nunc leo, euismod quis ultricies nec, gravida vitae purus. Nullam aliquet purus vitae urna tristique sed ultricies turpis condimentum. Fusce malesuada ipsum at ligula tristique et blandit nisi feugiat.</p>

<a href="http://www.blogger.com/example/comment">Submit a comment</a>
/application/views/scripts/example/comment.php:
<?php
echo $this->form;
?>
So now let's use the FlashMessenger to add a message in the commentAction and retrieve it in index.phtml. First we'll need to register the action helper, let's do this in Bootstrap.php to make it application-wide:
protected function _initMessenger()
{
require_once('Zend/Controller/Action/HelperBroker.php');
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_FlashMessenger());
}
Now we'll add our messages to the ExampleController commentAction():
public function commentAction() {

(some code)

if ($this->_request->isPost()) {
$flashMessenger = $this->_helper->FlashMessenger;
if (!$comment->isValid($this->_request->comment)) {
$flashMessenger->addMessage('Please enter a comment.');
} else {
$flashMessenger->addMessage('Thank you for your comment.');
$this->_redirect('/example/index');
}
}
$this->view->form = $form;
}
As Noumanal points out, you do not really want to get the messages until you need them, which is in the view. We can retrieve the FlashMessenger Action Helper through the ActionBroker with the getExistingHelper() method. Notice that there are two methods that we call to get all the messages; getMessages gets the messages added before any redirect and getCurrentMessages gets any messages added without redirecting. We also need to call clearCurrentMessages() or else they will show up on the next request (the other messages are cleared automatically). For immediate gratification, add the following lines to the tops of your views, above the content:
<p><?php      
$flashMessenger = Zend_Controller_Action_HelperBroker::getExistingHelper('FlashMessenger');
$messages = $flashMessenger->getMessages()getMessages()
+ $flashMessenger->getCurrentMessages();
$flashMessenger->clearCurrentMessages();
echo '<p>' . join('<br>', $messages) . '</p>';
?></p>

(the page content)
Now of course you realize that this should really be in your layout.phtml file, right? See Layout and Navigation, Part 1 if you don't know how to set that up. Since that was so easy, let's do a brief example of how we would move this piece into a View Helper. Create a file libraries/My/View/Helper/FlashMessenger.php and basically just move the display code to here:
<?php

class My_View_Helper_FlashMessenger extends Zend_View_Helper_Abstract {
public function flashMessenger() {
$flashMessenger = Zend_Controller_Action_HelperBroker::getExistingHelper('FlashMessenger');
$messages = $flashMessenger->getMessages()
+ $flashMessenger->getCurrentMessages();
$flashMessenger->clearCurrentMessages();
echo '<p>' . join('<br>', $messages) . '</p>';
}
}
Add the path to your new View Helper to your Bootstrap.php:
protected function _initMessenger()
{
require_once('Zend/Controller/Action/HelperBroker.php');
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_FlashMessenger());

// initialize the view helper
require_once('My/View/Helper/FlashMessenger.php');
$view = Zend_Layout::getMvcInstance()->getView();
$view->addHelperPath('My_View_Helper');
}
Now you can use this View Helper in your view scripts or layouts to echo your application messages:

<?php
echo $this->flashMessenger();
?>
I haven't looked at Noumenal's FlashMessenger View Helper, although it sounds pretty neat. One thing he mentions is that it uses key/value pairs for the messages which indicate a level such as notice, warning, etc, which seems like a good idea. This simple version should get you started, but keep in mind that you can use your View Helper to add many formatting enhancements that will then ensure a standard message format throughout your application.

No comments:

Post a Comment