[ Team LiB ] Previous Section Next Section

Working with the Framework

The work we have done so far deals with central control, command selection, and view dispatch. Although we have examined the source code in detail, getting a sense of the code's functionality is difficult without trying it first.

We are going to build a very basic example. We'll create code for extracting task data from, and adding task data to, a database, concentrating on two views and two commands.

We will use a MySQL database and a task table that can be created with the following statement:


CREATE TABLE tasks (
 id int(11) NOT NULL auto_increment,
 summary varchar(255) default NULL,
 description text,
 person varchar(255) default NULL,
 PRIMARY KEY (id)
);

You might want to refer to Listing 24.1 to remind yourself of the way the program flow works. Notice that initialization is handled by an ApplicationResources object.

Implementing ApplicationResources

In Listing 24.9 we create an application-specific ApplicationResources class called ApplicationResourcesImpl.

Listing 24.9 The ApplicationResourcesImpl Class
 1: <?php
 2: // controller/ApplicationResourcesImpl.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5: require_once 'controller/ApplicationResources.php';
 6: require_once 'controller/SimpleDispatcher.php';
 7: require_once 'command/DataStore.php';
 8: require_once 'command/Command.php';
 9: require_once 'command/SimpleCommandFactory.php';
10:
11: class ApplicationResourcesImpl extends ApplicationResources {
12:   private $dispatcher;
13:   private $commandfactory;
14:
15:   function __construct() {
16:     $this->dispatcher = new SimpleDispatcher( "views" );
17:     $this->commandfactory = new SimpleCommandFactory();
18:   }
19:
20:   function init() {
21:     $this->commandfactory->addPackage("my_commands");
22:     $this->commandfactory->setDefaultCommand( "MyDefault" );
23:     $this->setupDispatcher();
24:     $this_>primeDatabase();
25:   }
26:
27:   function primeDatabase() {
28:     $user = "p24_user";
29:     $pass = "cwaffie";
30:     $host = "localhost";
31:     $database = "p24";
32:
33:     $dsn = "mysql://$user:$pass@$host/$database";
34:     $store = DataStore::getInstance();
35:     $store->setVar( "dsn", $dsn );
36:   }
37:
38:   function setupDispatcher() {
39:     $success = Command::CMD_SUCCESS;
40:     $unproc = Command::CMD_UNPROCESSED;
41:     $error = Command::CMD_ERROR;
42:
43:     $this->dispatcher->addCondition( "MyDefault", "main.php" );
44:     $this->dispatcher->addCondition( "MyDefault", "error.php", $error );
45:     $this->dispatcher->addCondition( "AddTask", "add.php" );
46:     $this->dispatcher->addCondition( "AddTask", "main.php", $success );
47:     return true;
48:   }
49:
50:   function getDispatcher() {
51:     return $this->dispatcher;
52:   }
53:   function getCommandFactory() {
54:     return $this->commandfactory;
55:   }
56: }
57: ?>

The ApplicationResourcesImpl class sets up our application for us. We know that the Controller needs objects of type CommandFactory and Dispatcher, and it is here that we can choose which implementations to provide. In our constructor on line 15, we instantiate a SimpleDispatcher object and a SimpleCommandFactory object—both of which we store as properties.

The real work of the class takes place in the init() method on line 20. This is called by the Controller at the start of application flow. We call the CommandFactory::addPackage() method to register our own commands directory, my_commands, with the system on line 21. We also set a default command string by calling the setDefaultCommand() method, completing the configuration of our CommandFactory object. From here, it will be smart enough to find any command objects we drop into the my_commands directory.

To configure our SimpleDispatcher object, we call a utility function called setupDispatcher(), which is declared on line 38. We set up a number of conditions between lines 43 and 47. As you can see, we are working with two commands: MyDefault, which provides the default action of listing all tasks in the database, and AddTask, which handles the adding of task data to the database. In both cases, we set up default views. The MyDefault command is associated with a file called main.php by default. Only if an error occurs (that is, if MyDefault::doExecute() returns Command::CMD_ERROR) is another document—error.ph—served. The AddTask command is associated with a document called add.php by default. If the command reports a success, however, this command uses the main.php template, too.

After calling setUpDispatcher() on line 23, we call another convenience method: primeDatabase().primeDatabase() is declared on line 27 and creates a DSN as used by the PEAR::DB package. This is then registered with the system's DataStore object on line 35. This will be used later to connect to our database.

Finally, we implement the getDispatcher() and getCommandFactory() methods on lines 50 and 53.

When our Controller object runs, it at least has some real objects to work with. We have yet to define our commands and views. Before we get into that, though, let's think briefly about our application's model.

The TaskFacade Object

In this example we are not implementing an entire MVC pattern. We are concentrating on the view and controller tiers (especially the controller tier). In a full MVC pattern example, we would create business objects to represent the data and relationships within our system. We would need to implement a strategy for making these objects persist, and probably a facade object or a series of facade objects to simplify communication with them from the control/command tier. The various structures and techniques for this are beyond the scope of this chapter. We can, however, build a simple TaskFacade class whose job it is to work with the database to get and set task data. The TaskFacade class is demonstrated in Listing 24.10.

Listing 24.10 The TaskFacade Class
 1: <?php
 2: // facade/TaskFacade.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5: require_once 'DB.php';
 6:
 7: class TaskFacade {
 8:   private $db_common;
 9:
10:   function __construct() {
11:     // throws Exception from getConnection()
12:     $this->db_common = $this->getConnection();
13:   }
14:
15:   private function getConnection() {
16:     $data = DataStore::getInstance();
17:     $dsn = $data->getVar("dsn");
18:     if ( empty( $dsn) ) {
19:       throw new TaskFacadeException ( "There should be a 'dsn'" );
20:     }
21:     $connection = DB::connect ( $dsn );
22:     if (DB::isError( $connection) ) {
23:       throw new TaskFacadeException( $connection->getMessage() );
24:     }
25:     return $connection;
26:   }
27:
28:   private function selectQuery( $query) {
29:     $result = $this->db_common->query( $query );
30:     if ( $result instanceof db_error ) {
31:         throw new TaskFacadeException( $result->getMessage() );
32:     }
33:
34:     $ret = array();
35:     while ( $row = $result->fetchRow( DB_FETCHMODE_ASSOC ) ) {
36:       foreach( $row as $key=>$val ) {
37:         $row[$key] = stripslashes( $val );
38:       }
39:       $ret[] = $row;
40:     }
41:     return $ret;
42:   }
43:
44:   function getTask( $id ) {
45:     $query = "SELECT * FROM tasks WHERE id=$id";
46:     // throws excption from selectQuery
47:     return $this->selectQuery( $query );
48:   }
49:
50:    function getTasks() {
51:      $query = "SELECT * FROM tasks";
52:      // throws excption from selectQuery
53:      $result = $this->selectQuery( $query );
54:      return $result;
55:   }
56:
57:    function setTask( $data_array ) {
58:      if ( ! is_array ( $data_array )) {
59:        throw new TaskFacadeException( "setTask() requires an array" );
60:      }
61:
62:      if (  empty( $data_array['summary'] ) ||
63:          empty ( $data_array['person'] ) ) {
64:        throw new TaskFacadeException( "setTask(): missing data" );
65:      }
66:      $fields = array( "person", "summary", "description" );
67:      foreach ( $fields as $key ) {
68:       $add_array[$key] = $data_array[$key];
69:      }
70:
71:      $data_array['id'] = $this->db_common->nextId("tasks");
72:      $result = $this->db_common->autoExecute( "tasks", $add_array,
73:          DB_AUTOQUERY_INSERT );
74:      if ( $result instanceof db_error ) {
75:          throw new TaskFacadeException( $result->getMessage() );
76:      }
77:      return true;
78:   }
79: }
80:
81: class TaskFacadeException extends Exception { }
82:
83: ?>

The TaskFacade class should be easy to read by now. In essence, we do no more than use the PEAR::DB package to work with the tasks database. The constructor is declared on line 10 and calls the private getConnection() method to acquire a PEAR::DB_Common object. getConnection() calls DB::connect() in the usual way. Notice how we acquire our DSN from the system's DataStore object, using the getVar() method. If the DSN is not found, we throw a TaskFacadeException object. Because the constructor does not catch any Exception objects, the TaskFacadeException thrown by getConnection() is rethrown to the calling code. All operations in this class that might fail protect themselves with Exception objects in this way.

The public methods used by Command objects are getTask() (line 44), getTasks() (line 50), and setTask() (line 57). These do exactly what you would expect. The getter methods construct SQL statements for extracting data, and the setter method uses the DB_Common::autoExecute() method to automate the data insert.

Now that we have some code to talk to our database, we can implement some Command objects.

The Command Classes

All the hard work has already been done, so we will find the Command classes quite simple. Listing 24.11 shows the MyDefault class.

Listing 24.11 The MyDefault Class
 1: <?php
 2: // my_commands/MyDefault.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5: require_once "command/Command.php";
 6: require_once "facade/TaskFacade.php";
 7:
 8: class MyDefault extends Command {
 9:
10: function doExecute( RequestHelper $requestHelper ) {
11:   $requestHelper->setMessage( "welcome" );
12:   $taskfacade = new TaskFacade();
13:   $tasks = $taskfacade->getTasks();
14:   $requestHelper->saveVar( "tasks", $tasks );
15:
16:   return CMD_SUCCESS;
17:  }
18: }
19: ?>

The MyDefault class only implements the doExecute() class. We call RequestHelper::setMessage() on line 11 to welcome the user. We then instantiate a TaskFacade object on line 12 and call getTasks() on line 13 to get an array containing all the task information in the database. We pass this on to the system's DataStore object via the RequestHelper object's convenience method, saveVar(). Finally, we return CMD_SUCCESS.

The AddTask class is only slightly more involved than MyDefault (see Listing 24.12).

Listing 24.12 The AddTask Class
 1: <?php
 2: // my_commands/AddTask.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5: require_once "command/Command.php";
 6: require_once "my_commands/MyDefault.php";
 7: require_once "facade/TaskFacade.php";
 8:
 9: class AddTask extends Command {
10:
11:   function doExecute( RequestHelper $requestHelper ) {
12:
13:     $params = $requestHelper->getParams();
14:
15:     if ( empty ( $params['addtask_submit'] ) ) {
16:       $params = $requestHelper->setMessage("enter task details");
17:       return CMD_UNPROCESSED;
18:     }
19:
20:     if (  empty( $params['summary'] ) ||
21:         empty( $params['person'] ) ) {
22:       $requestHelper->setMessage("All fields mandatory");
23:       return CMD_ERROR;
24:     }
25:     $taskfacade = new TaskFacade();
26:     $taskfacade->setTask( $params );
27:     $cmd = new MyDefault();
28:     return $cmd->execute( $requestHelper );
29:   }
30: }
31: ?>

The AddTask::doExecute() method behaves differently according to the client's submission. Remember that we can get an array of request elements from RequestHelper::getParams(). We test for a flag argument called "addtask_submit". If this is not present, we assume that we are not being called to act and return CMD_UNPROCESSED on line 17.

If the "addtask_submit" flag is present, we must assume that we are being asked to handle data. We test the incoming data for the essential "summary" and "person" fields on line 20. If one of them is not present, we use Request::setMessage() to report the error to the user and return CMD_ERROR.

If all our checks have worked, we instantiate a TaskFacade() object on line 25 and call setTask(), passing it the user input array.

Finally, we instantiate a MyDefault object and call its execute() method. This ensures that the user can see an updated list of tasks.

Now all we need to do is create the views for the application.

The Views

All the underlying work is now done. In this section we give the application a face. If all has gone well, the views in our system should be programmatically simple. They will have a few loops and variables to be sure, but all the logic should be tucked away neatly. In our system, our views have an implicit contract with the system. They won't interfere with the intricacies of program logic, and in return they have a right to expect that the DataStore object will be primed with the data they need to do their job of presenting information.

Listing 24.13 contains main.php, the default view for our system.

Listing 24.13 The Main View
 1: <?php
 2: // views/main.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5:   $data = DataStore::getInstance();
 6:   $tasks = $data->getVar( "tasks" );
 7: ?>
 8: <!DOCTYPE html PUBLIC
 9:    "-//W3C//DTD XHTML 1.0 Strict//EN"
10:    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
11: <html>
12: <head>
13: <title>The Task List</title>
14: </head>
15: <body>
16: <div>
17:
18: <h3> <?php print $data->getMessage(); ?> </h3>
19:
20: <p>
21: <b>The tasks</b><br />
22: <a href="?cmd=AddTask">add a task</a>
23: </p>
24: <table border="1">
25: <tr><td><b>owner</b></td><td><b>summary</b></td></tr>
26: <?php
27: foreach ( $tasks as $task ) {
28: print <<<TASK
29:   <tr>
30:   <td>{$task['person']}</td><td>{$task['summary']}</td>
31:   </tr>
32: TASK;
33: }
34: ?>
35: </table>
36: </div>
37: </body>
38: </html>

The DataStore object is the main means by which views gain access to data generated by the system, and we acquire the object on line 5 using the static getInstance() method. DataStore is an example of a Singleton object, and only one of them exists in a process at the same time. We acquire an array of task data on line 6 using the getVar() method.

We print any message that has been left with DataStore on line 18. In a real-world application, we would probably use a helper function to test that a message exists before including any formatting. We would also use an include statement to output the message data because it is likely to be something that will be needed on most view pages.

Notice our navigation on line 22. By passing a cmd parameter of 'AddTask' to the Controller, a click on the hyperlink causes the AddTask command to be run and the Dispatcher to switch the view.

On line 27 we loop through the $task array, outputting each row to the browser.

You can see the view presented by main.php in Figure 24.1.

Figure 24.1. The main view.

graphics/24fig01.gif

Finally, let's take a quick look at the add.php view. This simply presents a form so the user can add task information (see Listing 24.14).

Listing 24.14 The Add View
 1: <?php
 2: // views/add.php
 3: // qframe license: http://resources.corrosive.co.uk/pkg/qframe/license.txt
 4:
 5:   $data = DataStore::getInstance();
 6: ?>
 7: <!DOCTYPE html PUBLIC
 8:    "-//W3C//DTD XHTML 1.0 Strict//EN"
 9:    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
10: <html>
11: <head>
12: <title>Add a task</title>
13: </head>
14: <body>
15: <div>
16:
17: <h3> <?php print $data->getMessage(); ?> </h3>
18:
19: <form method="post" action="index.php" />
20: <input type="hidden" name="cmd" value="AddTask" />
21: <input type="hidden" name="addtask_submit" value="true" />
22: <p>Your name<br />
23: <input type="text" name="person" value="<?php print $_REQUEST['name'] ?>" />
24: </p><p>Task summary<br />
25: <input type="text" name="summary"
value="<?php print $_REQUEST['summary'] ?>" />
26: </p>
27: <p> <input type="submit" value="add task" /> </p>
28: </form>
29: <p>
30: </div>
31: </body>
32: </html>

There is little new to see in view.php. Notice again that we acquire a DataStore object and output any user message to the browser. The only other features of interest are our hidden form inputs on lines 20 and 21. By sending a cmd parameter with an "AddTask" value on line 20, we ensure that our form submission is sent back to the AddTask command. It is the 'cmd' argument that the CommandFactory uses to select a command for execution. Also of note is the "addtask_submit" field, which is used by the AddTask command to confirm that we want our data to be processed.

You can see the add.php view in action in Figure 24.2.

Figure 24.2. The Add Task view.

graphics/24fig02.gif

    [ Team LiB ] Previous Section Next Section