Zend Framework: View Helper Priority Messenger

AttachmentSize
ZExt_View_Helper_PriorityMessenger.php4.93 KB

Zend Framework's FlashMessenger is a well meaning and generally well constructed object, but which doesn't behave quite as I'd like, so I created this PriorityMessenger. My reasons for doing so center on the following two issues.

First, because it operates from the Controller's perspective, the FlashMessenger needs to have its messages manually retrieved and passed to the View where they might be rendered on a page, perhaps through a template. On the surface, placing this logic in the Controller makes perfect sense as that is where most (if not all) of the errors in an application are likely to be generated. However, determining the appropriate time to forward errors to the view turned out to be a somewhat complicated issue in a recent project of mine. In particular, that project's CSS is dynamically generated and cached for the sake of skinning, and access to these files occurs via a specially set Route. If I should accidentally allowed the Controller to forward its messages to View when rendering a CSS file then the user would never see that message (i.e. since rendering CSS does not include outputting error messages, and CSS files are not usually viewed by the user, and retrieving the messages from FlashMessenger causes them to be deleted). It makes much more sense to me to pass errors to a Helper in the View's purview. The View will be rendering the objects eventually, and the View knows when regular page content will actually be displayed to the user.

Second, I prefer to have a "priority" set with my messages; e.g. "info", "warning", and "error". This provides for appropriate emphasis to be placed on different message types. FlashMessenger is not explicitly designed for this, although I imagine one could implement a scheme using the session namespaces, albeit cumbersome to do so.

The following implementation of PriorityMessenger overcomes these issues by placing the helper in the View's domain and by accepting an arbitrary "priority" for each message. The implementation had to work around the limited accessibility afforded View Helper objects, but I hope that the in-line function comments below describe this usage well enough. As usual, I'll start with an example usage, followed by the object code, and provide this same code in downloadable links at the end of the page.

An example call to the object might look something like this:

class AuthController extends Zend_Controller_Action {
  function loginAction() {
    . . .
    if ($this->_request->isPost()) {
      $formData = $this->_request->getPost();
      if ($this->view->form->isValid($formData)) {
        . . .
      } else {
        $this->view->priorityMessenger('Login failed.', 'error');
      }
    . . .
  }
}

An example rendering when using Zend_Layout might look something like this:

<html>
  <head>
    . . .
  </head>
  <body>
    . . .
          <?php echo $this->partial('priorityMessages.phtml', array('priorityMessages'=>$this->priorityMessenger())); ?>
    . . .
  </body>
</html>

With a priorityMessages.phtml file that looks like this:

<?php
echo '<div id="priority_messages">';
  echo '<ul>';
    foreach ($this->priorityMessages as $label=>$messages) {
      if (count($messages)) {
        echo '<li class="'.$label.'_message">';
        echo '<ul class="'.$label.'_message">';
        foreach ($messages as $message) {
          echo '<li>'.$message.'</li>';
        }
        echo '</ul></li>';
      }
    }
  echo '</ul>';
echo '</div>';

Aaaaaand the Zend_View_Helper_Abstract class itself; note that I'm using "ZExt" as the library name here (as in "Zend-Extended") which is just an arbitrary name I've selected for the sake of example:

<?php
/**
 * @category   ZExt
 * @package    ZExt_View
 * @subpackage ZExt_View_Helper
 * @author     Sean P. O. MacCath-Moran
 * @email      zendcode@emanaton.com
 * @website    http://www.emanaton.com
 * @copyright  This work is licensed under a Attribution Non-commercial Share Alike Creative Commons license
 * @license    http://creativecommons.org/licenses/by-nc-sa/3.0/us/
*/

/**
 * @see Zend_Session
 */
require_once 'Zend/Session.php';

/**
 * @see Zend_View_Helper_Abstract
 */
require_once 'Zend/View/Helper/Abstract.php';

/**
 * Priority Messenger - implement session-based messages at the view level
 *
 * @uses       Zend_View_Helper_Abstract
 * @category   ZExt
 * @package    ZExt_View
 * @subpackage ZExt_View_Helper
 * @author     Sean P. O. MacCath-Moran
 * @email      zendcode@emanaton.com
 * @website    http://www.emanaton.com
 * @copyright  This work is licensed under a Attribution Non-commercial Share Alike Creative Commons license
 * @license    http://creativecommons.org/licenses/by-nc-sa/3.0/us/
*/
class ZExt_View_Helper_PriorityMessenger extends Zend_View_Helper_Abstract {

  /**
   * $_session - Zend_Session storage object
   *
   * @var Zend_Session
  */
  static protected $_session = null;

  /**
   * Add a message to the collection of priority messages or retrieve the
   * priority messages. If $messages is left null then all the messages are
   * returned, unless $severity is set (as a string or array), causing just
   * the indicated messages to be returned; in either case, the returned
   * messages are cleared from the session cache. If a message or messages
   * are provided, then store them in the indicated severity; $messages may
   * be an array of string all to be stored in the indicated severity OR it
   * may be an associative array of severity-to-message pairs. In any case,
   * if $severity is not set but message is, then 'info' is the assumed
   * default.
   *
   * @param  string|array|null $message
   * @param  string|array|null $severity
   * @return Zend_Session_Namespace
  */
  function priorityMessenger($message = null, $severity = null) {
    $session = $this->_getSession();
    // if page messages has not been set o the session, then initialize it.
    if (!isset($session->page_messages)) {$this->_resetMessageArray();}

    if (is_null($message)) {
      // return all the messages or just those indicated by $severity
      return $this->_resetMessageArray($severity);
    } else {
      //add message to the collection

      // default severity to 'info'
      if (is_null($severity)) {$severity = 'info';}

      // if severity is an array, then assume this was done in error and use
      // only the first value of the array as the severity.
      if (is_array($severity)) {
        reset($severity);
        $severity = $severity[key($severity)];
      }

      // if this is the first message of this severity, then initialize
      // the message array for that severity.
      if (!isset($session->page_messages[$severity])) {
        $session->page_messages[$severity] = array();
      }

      // if message is an array then assume it is a group of messages to be
      // added with the given severity. However, it is possible to pass in an
      // array of arrays of severity-to-messageArray sets, going as deep as
      // one might wish, so long as the deepest level provides the correct
      // severity-to-message relationship.
      if (is_array($message)) {
        foreach ($message as $sev=>$mes) {
          $this->priorityMessenger($mes, $sev);
        }
      } else {
        $session->page_messages[$severity][] = $message;
      }
    }
  }

  /**
   * Reset the session object's collection of messages. If severity provided,
   * then return that severity and clear only that severity. If an array of
   * severities are provided, then return an array in the form of
   * $severity=>$messages.
   *
   * @param  string|array $severity
   * @return void
  */
  private function _resetMessageArray($severity = null) {
    $messages = array();
    if (is_null($severity)) {
      $messages = $this->_getSession()->page_messages;
      $this->_getSession()->page_messages = array();
    } elseif (is_string($severity) && isset($this->_getSession()->page_messages[$severity])) {
      $messages = $this->_getSession()->page_messages[$severity];
      unset($this->_getSession()->page_messages[$severity]);
    } elseif (is_array($severity)) {
      foreach($severity as $sev) {
        $messages[$sev] = $this->_resetMessageArray($sev);
      }
    }
    return $messages;
  }

  /**
   * Return the static session object, initiating it if needs be.
   *
   * @return Zend_Session_Namespace
  */
  private function _getSession() {
    if (!self::$_session instanceof Zend_Session_Namespace) {
      $className = get_class($this);
      $className = (strpos($className, '_') !== false) ? ltrim(strrchr($className, '_'), '_') : $className;
      self::$_session = new Zend_Session_Namespace($className);
    }
    return self::$_session;
  }
}

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Sweet -- much appreciated

I truly appreciate code that just works! This was one such example, nicely written too! Had it up and running in a blink of an eye... well almost.

Cheers!

AS

Awesome!

I agree with your view that all this "flash message" business belongs in the view. In fact, as I set out to use the standard Zend FlashMessenger component for the first time, I started looking for it in the Zend/View/Helpers folder. Kind of surprised to find it as an action helper.

I was not originally sold on the idea of priorities for the messages, but now I see - as you obviously understood from the get-go and was also alluded to by others - that rendering different icons/CSS for different priorities is also a benefit.

So, I dropped your gorgeous view helper into my project, changed the namespace prefix (maintaining all credits and license, of course), added in the bootstrap the required prefix/path pair to the view helper path, and it all works like a charm.

Thanks mucho for an awesome piece of code. ;-)

Cheers!

Re: Awesome!

Greetings David,

Woot! Thank you for your comment - I'm thrilled it worked out so well for you!

Sean P. O. MacCath-Moran
www.emanaton.com

Thanks!

Edited 2010-01-26 by Sean P. O. MacCath-Moran: I took the liberty of adding the code filters into this. =o)

Hi,

Thanks for this, nice work.

I haven't done any complicated applications that have used message 'type' in the business logic, but I do like to set a visual indication of the message type in my views, e.g, <p class='error'>blah</p>.

The FlashMessenger object accepts arrays, so you can simply pass in an array (of any structure), and then retrieve it at any time and use as you need.

So I just pass in a message array that has 'text' and 'CSS class'.
Its not even an associative array - just indexed (although you can have any structure).

Example
This is pseudo code...


// (in a model or action)
// eg, if($logged_out)
$flashMessenger = Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger');

// text is indexed 0, and class is indexed 1
$flashMessenger->addMessage(array('You have been logged out', 'error'));



// sometime later...



// (in a helper class (accessed via a partial or script))

$messages = Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger')->getMessages();

// Loop the messages and build the return string
$string = '';
foreach($messages as $message) {
    $message_class = (!empty($message[1])) ? $message[1] : 'info';
    $message_text = (!empty($message[0])) ? $message[0] : false;
    if($message_text) {
        $string .= "<p class='$message_class'>$message_text</p>";
    }
}
return $string;




// (then the partial or script would echo the returned string as <p> tags with a class set, or default to class='info'.
 

Thanks,
Danny

Re: Thanks!

Greetings Danny,

Thank you very much for your input on this! However, your approach demonstrates yet another issue I have with using the flashMessenger object: it encourages one to address concerns in the Controller that should be dealt with in the View. Specifically, the issue of what class name gets associated with the messages. IMHO, this decision should be made completely with the context of the View.

That being said, I rather like the notion of leveraging the message itself to contain more data if needed by setting it to an array, and am now considering retooling my approach a bit to accommodate this.

Thanks again,

Sean P. O. MacCath-Moran
www.emanaton.com

how can i use this

hi i also have problem using the flashmessenger in zend and this helper is the one i need, but how do i get zend to use this helper where should i put it?and where to initialize it in the bootstrap?? sorry im not that good in zend thanks

First, thanks! This is

Edited 2009-10-08 by Sean P. O. MacCath-Moran: I took the liberty of adding the code filters into this. =o)

First, thanks! This is exactly what I was needing.

@jigen7 - Assuming you create the ZExt folder in your library folder and put file in the correct path (ex. library/ZExt/View/Helper/PriorityMessenger.php)

Then if you put something like the following in your Bootstrap you should be able to use the examples provided without any modification.

$view = $this->getResource('view');
$view->addHelperPath('ZExt/View/Helper','ZExt_View_Helper');

Hope that helps.

I did make a slight modification to the partial helper so that it doesn't print out the priority_messages div if there are no messages to show (sorry, couldn't get this to format correctly).

<?php
if (sizeof($this->priorityMessages)) {
    echo '
'; echo '
    '; foreach ($this->priorityMessages as $label=>$messages) { if (count($messages)) { echo '
  • '; echo '
      '; foreach ($messages as $message) { echo '
    • '.$message.'
    • '; } echo '
  • '; } } echo '
'; echo '
'; }

Re: First, thanks! This is

Greetings Jonathon,

Thank you for these notes - such feedback is always welcome!

Regards,

Sean P. O. MacCath-Moran
www.emanaton.com

Re: how can i use this

Greetings jigen7,

In general, Helpers are an MVC concept that is not intuitive to implement, but VERY intuitive to use. The idea with them is that they provide code that you can get to from "anywhere" for any reason. However, Zend Framework breaks this down a little further, making helpers "spaces" that are specific to particular parts of the MVC; there are very good reasons for this, but again, this is not entirely intuitive for a new user.

The Priority Messenger I wrote is a View Helper, so to use it the View must know where to find it. There are various ways to accomplish this, but the bottom line is that the View object has to be told what path(s) to look for the helper(s) at *and* what the class prefix for the helper(s) at the path(s). This can accomplished with the $view->addHelperPath() function directly, or via configuration options passed in to to the Application object, or by a number of other means. Here are some links to get you going:

For more information on setting the view helper path:
http://framework.zend.com/manual/en/zend.view.helpers.html#zend.view.hel...

For more information on how custom view helpers work:
http://framework.zend.com/manual/en/zend.view.helpers.html#zend.view.hel...

For more information on setting up and using zend application:
http://framework.zend.com/manual/en/zend.application.html

Hope that helps!

Regards,

Sean P. O. MacCath-Moran
www.emanaton.com

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.