MichaelHenriksen.dk Random bits and bytes…

8Nov/090

Handling messages in web applications – Part 1

Messages are a very important part of an application. Messages inform users about what is happening in the system and what the result of their actions is, so therefore it is critical to have a good and consistent way to display messages in an application.

In this post I will show how a message handling system can be implemented in a Zend Framework project.

Building the component

The class structure for the component looks as follows:

  • App_MessageHandler: Main class and interface to component
  • App_MessageHandler_Exception: Exception for message handler related errors
    • App_MessageHandler_Message_Exception: Exception for message related errors
  • App_MessageHandler_Message_Abstract: Abstract class to represent a message
    • App_MessageHandler_Message: Concrete implementation of abstract message class
    • App_MessageHandler_Success: Class to represent a success message
    • App_MessageHandler_Error: Class to represent an error message
    • App_MessageHandler_Notice: Class to represent a notice message
    • App_MessageHandler_Info: Class to represent an information message

App_MessageHandler

The App_MessageHandler class is the main class and interface for our little component. This class is responsible for collecting and saving the messages it gets from the application and it also takes care of rendering them.

The code:

final class App_MessageHandler implements Iterator, Countable
{
    /**
     * @var App_MessageHandler
     */
    protected static $_instance;
 
    /**
     * @var array
     */
    protected $_messages = array();
 
    /**
     * Iterator pointer
     * 
     * @var int
     */
    protected $_position = 0;
 
    /**
     * Protected constructor to enforce singleton
     */
    protected function __construct()
    {
    }
 
    /**
     * Magic clone method
     *
     * Throws exception if class is being cloned, to enfore singleton
     */
    public function __clone()
    {
        require_once "App/MessageHandler/Exception.php";
        throw new App_MessageHandler_Exception(
            "Cloning of singleton class is not allowed.");
    }
 
    /**
     * Get class instance
     * 
     * @return App_MessageHandler
     */
    public static function getInstance()
    {
      if(is_null(self::$_instance)) {
        self::$_instance = new self();
      }
 
      return self::$_instance;
    }
 
    /**
     * Add a message
     *
     * @param App_MessageHandler_Message_Abstract $message
     */
    public function addMessage(App_MessageHandler_Message_Abstract $message)
    {
        $this->_messages[] = $message;
    }
 
    /**
     * Flush all stored messages
     */
    public function flushMessages()
    {
        $this->_messages = array();
        $this->_position = 0;
    }
 
    /**
     * Render the stored messages
     *
     * @return string HTML rendering of stored messages
     */
    public function render()
    {
        $output = "";
 
        foreach ($this->_messages as $message) {
            $output .= $message->render();
        }
 
        return $output;
    }
 
    /**
     * Count stored messages
     *
     * Required by interface Countable.
     *
     * @return int
     */
    public function count()
    {
        return count($this->_messages);
    }
 
    /**
     * Rewind internal iterator pointer
     *
     * Required by interface Iterator.
     */
    public function rewind()
    {
        $this->_position = 0;
    }
 
    /**
     * Get stored message at current position
     *
     * Required by interface Iterator.
     *
     * @return App_MessageHandler_Message_Abstract
     */
    public function current()
    {
        return $this->_messages[$this->_position];
    }
 
    /**
     * Get the current iterator position
     *
     * Required by interface Iterator.
     *
     * @return int
     */
    public function key()
    {
        return $this->_position;
    }
 
    /**
     * Increment the internal iterator pointer
     *
     * Required by interface Iterator.
     */
    public function next()
    {
        $this->_position++;
    }
 
    /**
     * Check if current iterator position is valid
     *
     * Required by interface Iterator.
     *
     * @return bool TRUE|FALSE
     */
    public function valid()
    {
        return isset($this->_messages[$this->_position]);
    }
 
    /**
     * Add a success message
     *
     * @param string $message
     */
    public static function addSuccess($message)
    {
        require_once "App/MessageHandler/Message/Success.php";
 
        $message = new App_MessageHandler_Message_Success($message);
        self::getInstance()->addMessage($message);
    }
 
    /**
     * Add an error message
     *
     * @param string $message
     */
    public static function addError($message)
    {
        require_once "App/MessageHandler/Message/Error.php";
 
        $message = new App_MessageHandler_Message_Error($message);
        self::getInstance()->addMessage($message);
    }
 
    /**
     * Add a notice message
     *
     * @param string $message
     */
    public static function addNotice($message)
    {
        require_once "App/MessageHandler/Message/Notice.php";
 
        $message = new App_MessageHandler_Message_Notice($message);
        self::getInstance()->addMessage($message);
    }
 
    /**
     * Add an information message
     *
     * @param string $message
     */
    public static function addInfo($message)
    {
        require_once "App/MessageHandler/Message/Info.php";
 
        $message = new App_MessageHandler_Message_Info($message);
        self::getInstance()->addMessage($message);
    }
 
    /**
     * Magic toString method
     *
     * Acts as a facade to the {@link render()} method when the class
     * instance is casted to a string
     *
     * @return string HTML rendering of stored messages
     * @see    render()
     */
    public function __toString()
    {
        return $this->render();
    }
}

The class implements the Singleton pattern to ensure that there is only going to be one instantiation of the message handler in the application. The class also has a couple of static methods to add messages, which makes it much easier to use the component, because you won't need an instance of the class to add messages and the code for requiring the message classes and instantiation of those classes are neatly hidden in the static methods.

The class also implements two standard interfaces: Iterator and Countable. The Iterator interface makes it possible to use the class instance in a foreach() construct and iterate over each stored message, just as if it was an array. The Countable interface makes it possible to count the number of stored messages, by passing the class instance as a parameter to PHP's built in count() function.

The key methods in the class are:

  • getInstance() - Get the singleton instance
  • addSuccess($message) - Add a success message
  • addError($message) - Add an error message
  • addNotice($message) - Add a notice message
  • addInfo($message) - Add an information message
  • render() - Get HTML rendered presentation of each stored message

App_MessageHandler_Message_Abstract

The App_MessageHandler_Message_Abstract class contains the common code for all the concrete implementations.

The code:

<?php
 
abstract class App_MessageHandler_Message_Abstract
{
    /**
     * @var string
     */
    protected $_message;
 
    /**
     * The CSS class to add when rendering message
     *
     * @var string
     */
    protected $_class = 'none';
 
    /**
     * Protected method to clean up and normalize a message
     *
     * @param  string $message
     * @return string Normalized message
     */
    protected function _normalizeMessage($message)
    {
        $message = ucfirst(trim($message));
 
        $punctuationMarks = array('.', '!', '?', ',', ':', ';');
 
        if (!in_array(substr($message, -1), $punctuationMarks)) {
            $message = $message . ".";
        }
 
        return $message;
    }
 
    /**
     * Class constructor
     *
     * @param string $message
     * @uses  setMessage()
     */
    public function __construct($message)
    {
        $this->setMessage($message);
    }
 
    /**
     * Get the stored message
     *
     * @return string
     */
    public function getMessage()
    {
        return $this->_message;
    }
 
    /**
     * Set the message
     *
     * @param string $message
     * @throws App_MessageHandler_Message_Exception if message is empty
     */
    public function setMessage($message)
    {
        $message = trim($message);
 
        if (empty($message)) {
            require_once "App/MessageHandler/Message/Exception.php";
            throw new App_MessageHandler_Message_Exception(
                "An empty message was given.");
        }
 
        $this->_message = $this->_normalizeMessage($message);
    }
 
    /**
     * Get the CSS class
     *
     * @return string
     */
    public function getClass()
    {
        return $this->_class;
    }
 
    /**
     * Set the CSS class
     * 
     * @param string $class
     */
    public function setClass($class)
    {
        $this->_class = $class;
    }
 
    /**
     * Render the stored message
     *
     * Returnes the stored message wrapped in a div tag with class 'message' and
     * the string stored in $_class property.
     *
     * @return string HTML rendered message
     */
    public function render()
    {
        $output = sprintf("<div class='message %s'><p>%s</p></div>",
                           $this->_class, $this->_message);
 
        return $output;
    }
 
    /**
     * Magic toString method
     *
     * Acts as a facade for {@link render()} method when the class
     * instance is casted to a string.
     *
     * @return string HTML rendered message
     * @see    render()
     */
    public function __toString()
    {
        return $this->render();
    }
}

As we can see, the message class is quite simple. It's basically just a small class to store a string that can be returned wrapped in basic HTML by the render() method:

<div class='message {class}'>
    <p>{message}</p>
</div>

When the setMessage() is given a message, it passes it on the the protected _normalizeMessage() method:

   /**
     * Protected method to clean up and normalize a message
     *
     * @param  string $message
     * @return string Normalized message
     */
    protected function _normalizeMessage($message)
    {
        $message = ucfirst(trim($message));
 
        $punctuationMarks = array('.', '!', '?', ',', ':', ';');
 
        if (!in_array(substr($message, -1), $punctuationMarks)) {
            $message = $message . ".";
        }
 
        return $message;
    }

The method makes sure that all messages has a consistent look. It basically just trims white-space, makes sure that the first letter in the message is upper-case and appends a period if the message does not already end with a punctuation mark. This is done to make all the messages uniform which will make it look more professional. Users loves consistency!

Now that the abstract class is in place, it's a piece of cake to make the concrete messages:

App_MessageHandler_Message_Success

class App_MessageHandler_Message_Success extends App_MessageHandler_Message_Abstract
{
    protected $_class = 'success';
}

App_MessageHandler_Message_Error

class App_MessageHandler_Message_Error extends App_MessageHandler_Message_Abstract
{
    protected $_class = 'error';
}

App_MessageHandler_Message_Notice

class App_MessageHandler_Message_Notice extends App_MessageHandler_Message_Abstract
{
    protected $_class = 'notice';
}

App_MessageHandler_Message_Info

class App_MessageHandler_Message_Info extends App_MessageHandler_Message_Abstract
{
    protected $_class = 'info';
}

The concrete classes only have to do one thing, and that is to override the protected $_class property in order to render the messages differently.

Now that we have the basic component in place, let's head over to part two to see how the system is used in an application.

Go to Handling messages in web applications - Part 2

Share this post:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • StumbleUpon
  • Twitter

Related posts:

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment


Please leave these two fields as-is:

No trackbacks yet.