[ Team LiB ] Previous Section Next Section

The Dispatcher Class

The Dispatcher class is provided to the Controller by the ApplicationResources object. We cover a particular implementation in this section, but we have used an abstract base class again to allow for more sophisticated solutions. The abstract Dispatcher class is shown in Listing 24.7.

Listing 24.7 The Dispatcher Class
 1: <?php
 2: // controller/Dispatcher.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5: require_once 'controller/RequestHelper.php';
 6:
 7:
 8: abstract class Dispatcher {
 9:   private $views;
10:
11:   function __construct( $view_dir ) {
12:     $this->views = $view_dir;
13:   }
14:
15:   abstract function getNext( RequestHelper $helper );
16:
17:   function dispatch( RequestHelper $requestHelper ) {
18:     $next = $this->getNext( $requestHelper );
19:     $view = "{$this->views}/$next";
20:
21:     if ( ! is_file ( $view ) ) {
22:       throw new DispatchException("cannot open $view");
23:     }
24:
25:     include( $view );
26:     exit;
27:   }
28: }

In the constructor on line 11, we demand a string argument that we store in the $views property. This represents the directory where presentation files should be stored. The Dispatcher class defines two methods besides the constructor. The first, getNext() (line 15), is abstract and left to client classes to implement. The second method is dispatch() (line 17), which demands a RequestHelper object and handles the mechanics of delegating to the view tier. We call getNext() to get access to a string reference to the view we want to present. We then test that the view exists on the file system and that it is a file. If the view cannot be found, or if it is a directory, we throw a DispatchException on line 22. DispatchException is simply an empty class that extends the built-in Exception class. If no problems are encountered, we simply include the file, handing responsibility over to the presentation layer. Our work here is done!

Of course, we have yet to implement the getNext() method. What is the logic by which we choose which view to present to the user? Well, our Dispatcher object has access to a primed RequestHelper object. We know that this maintains an array of command names, each one paired with an execution status. This provides enough information to build a logic for dispatch.

In formulating this logic, we have two options. First, we could provide a mechanism for mapping a single command and status combination with a page. Second, we could provide a mechanism for mapping any or all commands and status flags in the array to pages.

Remember that Command objects register themselves and their execution statuses whenever they are called, by calling RequestHelper::registerCommand(). If one Command object invokes another, which in turn invokes a third Command object, we could end up with an extensive list in the RequestHelper object's $commandArray property. We are going to map a view of only the combination of the first Command called and its status flag in this example. We will, however, discuss a more flexible mechanism a bit later.

Listing 24.8 shows the SimpleDispatcher class, which provides a workable solution for mapping command execution to presentations.

Listing 24.8 The SimpleDispatcher Class
 1: <?
 2: // controller/SimpleDispatcher.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5: require 'controller/Dispatcher.php';
 6:
 7: class SimpleDispatcher extends Dispatcher {
 8:   private $dispatchHash = array();
 9:
10:   function addCondition( $cmd, $target, $status=-1 ) {
11:     $cmd = strtolower ( $cmd );
12:     if ( $status > 0 ) {
13:       $this->dispatchHash[ "$cmd.$status" ] = $target;
14:     } else {
15:       $this->dispatchHash [ "$cmd" ] = $target;
16:     }
17:  }
18:
19:  function getNext( RequestHelper $helper ) {
20:    list( $cmd, $status ) = array_pop( $helper->getCommandStack() );
21:    $key = "$cmd.$status";
22:    $view = $this->dispatchHash[$key];
23:    if ( empty ( $view) ) {
24:      $view = $this->dispatchHash[$cmd];
25:    }
26:    return $view;
27:   }
28: }
29: ?>

The SimpleDispatcher class handles two things: It implements getNext() and a method called addCondition(), which enables client code to establish the mapping between commands, command status flags, and pages on the system.

addCondition() requires the name of a command (that is, the name of a class that extends Command class), the path to the view to which the command should be mapped, and optionally an execution flag. Let's work through a quick example. Imagine a Command class called AddTask. A single command can have a number of views according to the way it executes. First, the AddTask command can be called with no data at all. The command might decide that it should do nothing in this case and return Command::CMD_UNPROCESSED. Given a SimpleDispatcher object stored in the variable $simpleDispatcher, we might set up the scenario like this:


$simpleDispatcher->addCondition( "AddTask",
                 "addtaskform.php",
                 Command:CMD_UNPROCESSED);

We are saying that we would like the addtaskform.php view presented when AddTask returns CMD_UNPROCESSED. This would typically be when the user arrives at the AddTask context for the first time.

When AddTask successfully completes a mission, it might return Command::CMD_SUCCESS. We would no longer want to present a form in this context, so we would set up a new mapping for this:


$simpleDispatcher->addCondition( "AddTask",
                 "thankyou.php",
                 Command:CMD_SUCCESS );

If we don't want to define specific pages for all combinations, we can also set a backstop condition. We do this by omitting the status flag argument:


$simpleDispatcher->addCondition( "AddTask", "error.php");

This means that the error.php page is included for all combinations for which no provision has been made. This mechanism is crude but surprisingly flexible.

Let's look again at our addCondition() method. How do we store the conditions provided? In fact, we populate a private associative array property called $dispatchHash. If we are provided with a status flag, we use it and the command name separated by a period as an element key with the target path as the value. So


$simpleDispatcher->addCondition( "AddTask",
                 "thankyou.php",
                 Command:CMD_SUCCESS );

is equivalent to the following:


$dispatchHash["addtask.200"]="thankyou.php";

If we are not provided with a status flag, we change our $dispatchHash element accordingly. So


$simpleDispatcher->addCondition( "AddTask", "error.php");

is equivalent to this:


$dispatchHash["addtask"]="error.php";

In this way, client code can use addCondition() to build up a complete map linking command execution and page presentation.

The getNext() method is declared on line 19 and uses the $dispatchHash array to return the correct page for the current circumstance. On line 20 we call RequestHelper::getCommandArray() to get the record of all commands executed. We acquire the details of the first command called, the primary command, using the array_pop function. First, we attempt to reproduce a specific condition on line 21 by combining the recorded command name with the status flag. If the $dispatchHash property is empty for this combination, we attempt to find a backstop target on line 24 by using the command name alone.

With these few classes we have laid the groundwork for a framework we could use and reuse in building applications. In the next section, we add some classes so you can see the framework in action.

    [ Team LiB ] Previous Section Next Section