php.MVC Users Guide 101

php.MVC Users Guide 101

Contents:
  Introduction
  The Benefits
  The MVC Pattern Concepts
  The Web Server Directory Layout and Files
  The Application Object Map
  The Core Components
  The XML Application Configuration System
  The Controller
  The Action Form
  The Action
  The View
  The Action Dispatcher
  Installing the Library
  Running the Example
  Resources

Introduction

php.MVC is a Model View Controller (MVC) framework for PHP Web applications. php.MVC implements the Model-View-Controller design pattern, and encourages Web application design based on the Model 2 paradigm. This design model allows Web pages or other contents (Views) to be separated from the applications business logic, or code (Model). The use of this design philosophy makes it easier for designers and programmers to focus on their respective areas of expertise.

The framework provides a single entry point application controller. The Controller is responsible for allocating HTTP requests to the appropriate Action handler (Model) based on XML mappings defined in the application XML configuration file. The Model contains the business logic (code) for the application.

When request processing is complete, the Controller calls the appropriate resource (View component), which is usually implemented as a template file. The resulting HTTP response is then returned to the client browser, or delivered via another protocol such as SMTP.

php.MVC is a PHP port of the Jakarta Struts project. php.MVC currently supports many features of Jakarta Struts, including declarative XML application configuration via the XML-to-Object mapping tool (the Digester). For example, mappings from the various Action/business logic components to appropriate resource pages can be specified declaratively in the application XML configuration file.

The diagram below shows a high-level overview of the framework:

MVC Concept Overview
Figure 1: The php.MVC Conceptual Overview

As can be seen in figure 1, the framework consists of three main components: The Front Controller, the main Controller and the request Dispatcher. We will look at each of these components in more detail shortly.

Throughout this guide we will make reference to an example application and source files. The complete working example with source files and including this guide can be downloaded here: php.MVC Users Guide 101 example.

The Benefits

The Model-View-Controller pattern for Web application design is now an industry standard in the Java world. There are many excellent books and resources available on the subject, which help to speed the learning process for the development team.

There is some effort required in learning to use a framework like php.MVC, as with most rewarding endeavours. However for the serious Web application developer, this effort should be rewarded by the many benefits of using an accepted design pattern such as MVC:

  • Enforces modularity and application partitioning
  • Increases developer/designer role separation
  • Increases code manageability
  • Increase code extensibility (Ability to adapt to change)

Why not build your own framework:

  • This is not a trivial undertaking
  • Can you to justify the time and expense
  • The php.MVC Open source license gives you complete access to the source code
  • php.MVC framework knowledge learned are transferable to Jakarta Struts
  • Skills in non-standard frameworks may not be as marketable
  • Experienced Struts developers should quickly become productive using php.MVC
  • php.MVC is based on the industry standard and field proven Jakarta Struts. Why re-invent the wheel?

The MVC Pattern Concepts

The php.MVC framework consists of a complex arrangement of many classes. Luckily we do not need to know in great detail how all these classes work, in order to use the framework. The diagram below shows the core components we need to know about in order to get started working with the framework:

The MVC Request/Response Pipeline
Figure 2: The MVC Request/Response Pipeline

In Figure 2 we can see the how a typical HTTP request from a Web client (Web browser) would interact with the core framework classes and our application classes, to produce a HTTP response (Web content) to return to the Web client.

To start the process, a client would send our php.MVC application a request, perhaps a request to view a particular company sales report: http://www.myhost.com/mycompany/Main.php?do=salesReport

The framework (the Controller) will now handle this request using one key piece of information, called the request path. In this particular case, the path is given as salesReport. We will later see how to configure the required application behaviour, and bind our form validation, business code handling and appropriate response resources (usually page templates) using the XML application configuration file.

Now perhaps we want to restrict access to this report to authorised persons only. The user would have had to enter some basic authentication information on the original form requesting the report: usually a username and password.

To validate this users access to our company report, we use a class called an ActionForm. Actually we extend the framework ActionForm class with our own form validation class, perhaps called SalesActionForm, like this:

class SalesActionForm extends ActionForm { ... }.

Within our SalesActionForm class we can check the users credentials and decide on the appropriate action. If the user fails our authentication test, we can redirect the user to the original form so she can try again. If the users authentication as good, the Controller will now call our business code via an Action class.

We will have defined the Action class to handle this request in our XML configuration file. Again we actually extend the framework Action class with our own class, perhaps called SalesAction, like this:

class SalesAction extends Action { ... }.

As we will have defined the Action class to handle this request (path) in our application XML configuration file, the Controller is now configured to hand this request on to our particular application SalesAction class. Within our SalesAction class we can access our business classes and data sources as required (the Model). In our current scenario we could extract company sales data from our database, and compile the required report. For example, we could create some objects to be used on the sales report template, like this:

 // Sales report items - per zone (individual object instances):
 $item1 = new Item('Northern Zone Sales' , $salesNorth);
 $item2 = new Item('Southern Zone Sales' , $salesSouth);
 ...

When finished compiling our company sales report, we instruct the Controller to use a particular resource (the View) to handle the presentation of our report. We do this by returning a pre-configured object called a Forward, something like this:

return $mapping->findForwardConfig('salesReportSuccess');
This Forward object contains the path to our sales report, perhaps salesReport.tpl, as would be defined in the application XML configuration file.

The Controller now passes the request to the ActionDispatcher . The ActionDispatcher is responsible for loading the specified resource, perhaps salesReport.tpl in this case. Report variables and objects on the template can now access the appropriate data we previously prepared in our SalesAction class.

The code fragment below shows how we could access the report data if using the phpTAL templating system to generate our presentation Views. The object $item1 can be used to retrieve the class variable $item1->value:

   ...
<!-- // Sales report items - per zone (individual object instances): -->

<table class='salesTable'>
   <tr>
      <td class='salesItemDesc' tal:content="item1/salesNorth">
            Sales report->value will appear here</td>
   ...

The Controller now saves the results of processing our resource (template) and returns this buffer to the user as the HTTP response (the report Web page). In most cases we will extend the framework supplied ActionDispatcher with our own custom class to handle our specific requirements.

The Web Server Directory Layout and Files

In this section we will look at the anatomy of a simple php.MVC Web application and typical application and library locations.

The figure below shows how a php.MVC application and the core php.MVC libraries can be deployed.

The Web Server Directory Structure
Figure 3: The Web Server Directory Structure

The php.MVC Libraries

We can see in the diagram above that the php.MVC library files have been installed in a directory called Dev that is used hold other common libraries that may be used on this Web server by php.MVC applications or other programs. This Dev (or Development) directory is located off the Web server root directory. By not being located in the Web root, the libraries are protected from direct Web access by users.

If we need to install the php.MVC libraries within the Web root for some reason we can use a .htaccess file (or similar) to control who can access this directory. An example Apache .htaccess file is shown below.

# options the .htaccess files in directories can override.
   # Edit apache/conf/httpd.conf to AllowOverride in .htaccess
   # AllowOverride AuthConfig

# Stop the directory list from being shown
  Options -Indexes

# Controls who can get stuff from this server.
  Order Deny,Allow
  Deny from all
  Allow from localhost

This instructs the Apache Web server to:

  1. Deny all access to the directory containing this .htaccess file, and all directories below. In this example that means the php.MVC library /WEB-INF directory, and all the other files and directories it contains.
  2. Allow access to localhost. This refers to the Web server host machine. This would allow a developer on this Web server could browse to the test directories within the library and run the unit tests, if required.

The php.MVC library files should require no further modification for normal use. To access the library classes from our application, we set the $appServerRootDir variable in the applications Main.php file, something like:

$appServerRootDir = 'D:/Dev/PHP/phpmvc-base'; // no trailing slash

The php.MVC Web Application

In the diagram above we see that the developer has created a directory within the Web root called SalesReports that will contain the application.

Within the top level SalesReports application directory we have several directories and the Main.php file. The art directory is used to hold the application graphics, and will be accessible to the Web. The style directory is used to hold the application style sheets and will also be accessible to the Web. These directories can be renamed to suit the developers preferences. We can access these resources from our template pages something like:

<link rel='stylesheet' type='text/css' href="./style/mystyles.css">

Next is the /WEB-INF directory. This is where we locate the application classes and resources. The /WEB-INF directory should not be accessible to the Web, and so is also protected by a .htaccess file. In this example we have a classes directory and a tpl directory. The developer is free to create directories and subdirectories as required within the /WEB-INF directory, and name them as needed. The only requirement is that the paths to these directories should be declared in the ModulePaths.php file, as we will see shortly. In the example above the classes directory holds the application classes, and perhaps other resources such as string resource property files. The tpl directory holds the applications View resources, such as page templates.

Also within the /WEB-INF directory are the .htaccess, ModulePaths.php, phpmvc-config.xml, phpmvc-config_1_1.dtd, phpmvc-config.data and prepend.php files. The .htaccess file has been discussed previously.

The ModulePaths.php file is used to define the application specific paths to our classes and resources. We can define the application paths like this:

 $appDirs   = array();
 $appDirs[] = ''; // starting with the sub-application home directory
 $appDirs[] = 'WEB-INF';
 $appDirs[] = 'WEB-INF/classes';
 $appDirs[] = 'WEB-INF/tpl';

The phpmvc-config.xml file is the central component of the php.MVC application. This is where we setup our application using XML nodes to define behaviour and properties. We will look at the phpmvc-config.xml file in more detail below.

The phpmvc-config_1_1.dtd file contains the Document Type Definitions (DTD) for the phpmvc-config.xml file. The DTD specifies what can and cannot be included in the phpmvc-config.xml file, and is the ultimate reference to the behaviour of the application. Most good XML editors use the DTD file to validate the phpmvc-config.xml file.

The phpmvc-config.data file contains the serialised configuration data for the application. This configuration data is regenerated whenever the application phpmvc-config.xml file is modified. Tip: if your application does not respond as expected, "touch" (white-space edit) the phpmvc-config.xml file and re-run the application to force the serialised configuration data to be regenerated.

The prepend.php file is used to include application files. We do not need to include the application classes and template files as the framework will find then provided we have specified the paths correctly, as outlined above. However we may need to include other class files and resources, as shown below:

include_once 'Locale.php';
include_once 'PropertyMessageResources.php'; 

The Main.php file is the single entry point for an application, and is located in the application root directory. This is the single entry point for an application. All requests to a particular php.MVC application are handled by the applications Main.php file. The Main.php file is also used to set some parameters that help setup the application when a request comes in.

As we saw above, the path to the php.MVC library must be defined in the $appServerRootDir variable:

$appServerRootDir = 'D:/Dev/PHP/phpmvc-base'; // no trailing slash
Next the path to the application is define in the variable $moduleRootDir like this:
$moduleRootDir = 'C:/WWW/SalesReports'; // no trailing slash
Where SalesReports is the application root directory.

We also set the path to our applications ActionDispatcher. Each php.MVC application will usually use a custom ActionDispatcher that is tailored to that applications specific requirements. We define the ActionDispatcher variable like this:

$actionDispatcher = 'ReportActionDispatcher';
The ActionDispatcher is covered in more detail later in this guide.

The $osType variable is an optional parameter that can be defined if the php.MVC framework has a problem detecting the host operating system (OS). Normally the framework should auto-detect the host operating system and setup the application paths accordingly. If it appears that the application paths are not correct, we can manually set the OS parameter like this:

$osType = 'UNIX';
for the Unix and Linux type of operating systems. And like this:
$osType = 'WINDOWS';
for Windows operating systems.

The remaining parameters in the Main.php file should not normally require modification.

Note: The use of the Main.php file for setting application bootup parameters will change in future, with the implementation of an web.xml FrontController style boot loading and configuration system.

The Application Object Map

The diagram below shows the relationship between the main classes and resources in a sample php.MVC application, from an application developers perspective.

The Sales Report Object Map
Figure 4: The Sales Report Object Map

The ActionForm set of classes can be seen on the left in the above diagram. We can use ActionForm derived classes to handle our HTML form validation and other form related tasks. The example above uses an AbstractBaseForm class that can contain common logic that may be used by several of our application ActionForm derived classes. The SalesReportForm class is a concrete implementation of the AbstractBaseForm class. The ActionForm classes are covered in more detail below.

The Action classes as seen in the diagram above usually handle the processing of a request after initial validation in the related ActionForm class. Again we see the use of a base class (AbstractBaseAction) that can contain common logic used by several Action derived classes. The SalesReportAction class extends the AbstractBaseForm class to provide a concrete implementation of the logic used to handle this request. From the SalesReportAction class we can call our business classes (e.g. ReportsBusinessClass) and access other resources and data stores (databases) as required. The Action classes are covered below.

The ActionDispatcher class is responsible for processing a predefined View resource (usually a page template) for this Action request, and can be seen in the lower portion of the diagram above. We usually extend the framework supplied ActionDispatcher class with our own custom dispatcher class, ReportActionDispatcher in the example above. We will look at the Action Dispatcher below.

The Core Components

In the following sections we will examine the core components of the php.MVC library:

  • The XML configuration file is the "switchboard" of a php.MVC application. This is how we bind the various components to a HTTP request path.
  • The Controller consists of the php.MVC framework classes that are responsible for routing HTTP requests to the correct Form and Action classes and View resources, as defined in the application phpmvc-config.xml file.
  • The Action Form classes can be used to handle Web form checks and validation.
  • The Action classes are used to access our business classes. We can direct the application flow to resources based on run-time conditions.
  • The Action Dispatcher class handles the resource (template) processing.
The XML Application Configuration System

The phpmvc-config.xml file is the main configuration component of a php.MVC application. Each php.MVC web application has its own phpmvc-config.xml file, usually located in the applications WEB-INF directory. Whenever this file is changed the Controller calls the XML Digester, which in turn parses the phpmvc-config.xml file, and converts the XML nodes into an hierarchy of configuration objects. These configuration objects are then cached to the phpmvc-config.data file, usually locates in the same directory as the phpmvc-config.xml file. On subsequent requests the Controller will load the cached phpmvc-config.data file, saving processing time.

The diagram below shows a typical phpmvc-config.xml file when viewed as XML nodes and attributes.

The XML configuration representation
Figure 5: The phpmvc-config.xml XML configuration representation

In Figure 3 we can see how the sections of the configuration relate to the corresponding application components. We have defined an action element with a unique path attribute called salesReport. This is the key identifier for this action and related components. The user would request this Action path (usually via a link) with a URL something like: http://www.myhost.com/mycompany/Main.php?do=salesReport.

Within the salesReport action node we have an attribute called name, with a value of salesReportForm. This tells the Controller that we want to use the form-bean named salesReportForm to handle validation requirements for this action node. We can see the salesReportForm form-bean node defined within the form-beans node above the action-mappings node. Also note the attribute named validate with a boolean value of true, which instructs the Controller to call the validate(...) method of our form-bean.

In both the action node and the associated form-bean node, there is an attribute called type. We use these attributes to define our form-bean and action classes respectively. For example the action node type attribute defines an Action class called SalesReportAction , and the form-bean node type attribute defines an ActionForm class called SalesReportForm.

The next items of interest are the forward nodes within the action node. We use these forward nodes to define resource URI's (normally templates) that comprise the presentation View component of the framework. The name attribute of the forward node defines an identifier by which we can retrieve this forward item. The path attribute defines the resource or template we will associate with this action. For example in the first forward node we define an attribute called salesReportSuccess with a resource path of salesReport.tpl, and in the second forward node we define an attribute called salesReportFailure with a resource path of salesReportIndex.tpl. We can use these forward nodes within our classes to identify resources to handle a particular situation. In this case if report generation is successful, we could use the salesReportSuccess object and it's defined resource salesReport.tpl to handle the request. If report generation failed for some reason we could forward control to the salesReportFailure forward object and the defined resource salesReportIndex.tpl will handle the presentation, perhaps returning the user to the original page.

The phpmvc-config.xml configuration file can be modified in an ordinary text editor, or using a specialised XML resource editor. Specialised XML editors are usually capable of validating the configuration file against the associated XML dtd file phpmvc-config_1_1.dtd. The DTD file defines the XML elements and their associated attributes and values that are valid for the phpmvc-config.xml file. The DTD file is the ultimate arbiter of what elements, attributes and values can be used in the phpmvc-config.xml file.

A sample phpmvc-config.xml file is shown below:

XML Configurations and Bindings: phpmvc-config.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE phpmvc-config PUBLIC "-//PHPMVC//DTD PHPMVC Configuration 1.0//EN" 
         "./phpmvc-config_1_1.dtd">

<phpmvc-config>

   <form-beans>
    <form-bean name="salesReportForm" 
               type="SalesReportForm"/>
   </form-beans>

   <action-mappings>
      <action  path = "salesReport" 
               type = "SalesReportAction" 
               name = "salesReportForm" 
               scope = "request" 
            validate = "true">
         <forward name="salesReportSuccess" path="salesReport.tpl"/>
         <forward name="salesReportFailure" path="salesReportIndex.tpl"/>
      </action>
   </action-mappings>

</phpmvc-config>

The Controller

The Controller component of the php.MVC framework consists of classes that handle a users request according to predefined configuration options. A typical user request may look something like:

http://www.myhost.com/mycompany/Main.php?do=salesReport.

The php.MVC Controller consists of two distinct sections: The Front Controller and the Controller proper. The Front Controller is responsible for setting up the application when a request arrives, and the Controller handles the request according to the configuration properties defined in the phpmvc-config.xml file.

The figure below shows the main tasks the php.MVC Front Controller performs when the application receives a Web request.

The php.MVC Front Controller
Figure 6: The Front Controller

The user request is received by the applications Main.php file, and this is where we setup some simple boot parameter options. The Front Controller performs the following tasks:

  • Define the application paths: This is where we define the path to the php.MVC library installation, and the path to our Web application. For example:
    $appServerRootDir = 'C:/WWW/phpmvc-base';
    $moduleRootDir    = 'C:/WWW/mycompany';
    
  • Define the application's ActionDispatcher: We will usually use our own custom Dispatcher class that extends the framework ActionDispatcher:
    $actionDispatcher = 'MyActionDispatcher';
    
  • Initialise the application class paths: The Front Controller loads the predefined global paths, and our application paths, so that the class loader can find our classes and resources. We can set the paths to additional application directories in the /WEB-INF/ModulePaths.php file, perhaps like:
    $appDirs = array();
    ...
    $appDirs = 'WEB-INF/report_tpl';
    $appDirs = 'WEB-INF/report_classes';
    
  • Include the application classes: Now the Front Controller loads the class files it needs to operate. We can optionally load some application specific class files using the /WEB-INF/prepend.php file as required, like this:
    include_once './WEB-INF/mytools/MyTools.php';
    
  • Configure the application: The Front Controller sets some configuration information for the application, such as the ActionDispacher we defined previously.
  • Initialise the Controller: The Front Controller now creates an application server instance (ActionServer).
  • Configure the application: The Front Controller now loads the application configuration information. If the phpmvc-config.xml file for this application has been modified since the last request, the phpmvc-config.xml file is reprocessed and cached to the applications ./WEB-INF/ directory as phpmvc-config.data.
  • Initialise the HTTP request: The Front Controller now sets up HTTP request and adds the request attributes.
  • Call the application controller: The Front Controller has now finished preparing the application server to handle this request, and passes request processing responsibility to the Controller.

Note: The previous section will change in future, with the implementation of an web.xml style boot loading and configuration system.


The Controller receives the request from the Front Controller, and performs a series of operations on the request depending on the configuration properties defined for this path (Main.php?do=salesReport).

The figure below depicts the php.MVC Controller.

The php.MVC Controller
Figure 7: The Controller

  • Process action path: The Controller identifies the path component used to select an action mapping. For example: if the request was something like http://www.myhost.com/mycompany/Main.php?do=salesReport, the path would be salesReport.
  • Process locale: Select a Locale for the current user if requested (*)
  • Process content: Set the content type headers if requested. The default type is text/html.
  • Process nocache: Set the no-cache headers, if requested. The defaults are
    "Pragma", "No-cache"
    "Cache-Control", "no-cache"
    "Expires", 1
    
  • Process preprocess: General purpose preprocessing hook. You can override this method in your own custom in ActionServer subclass, to perform some application specific preprocessing.
  • Process Action mapping: The Controller identifies the action mapping for this request, based on the request path (salesReport). This action mapping object (ActionConfig) was generated from the corresponding node in our phpmvc-config.xml file, with the path defined as salesReport (<action path = "salesReport" ...)
  • Process roles: Check for any authentication role required to perform this action (*)
  • Process Action Form: The Controller retrieves the ActionForm bean associated with this mapping. For example the form-bean we define with the action attribute <action ... name = "salesReportForm" .../>.
  • Process populate: Populate the properties of the specified ActionForm instance from the request parameters included with this request.(*)
  • Validate Action Form: Calls the validate() method of the specified ActionForm. To call the validate() method set <action validate = "true" .../> in the application web.xml file. If validate() returns False (validation failed), the Controller will use the resource (template) specified in the input attribute of the action configuration. For example:
     <action path = "salesReport" 
               ...
         validate = "true"
            input = reportsIndex.tpl>
    
    If validate() returns True (validation passed), normal processing continues to the Action Form class.
  • Process Forward: The Controller checks if a forward mapping uri has been set to override normal processing. Normally returns True, so we continue normal processing.
  • Process Include: The Controller checks if an include mapping uri has been set to override normal processing. Normally returns True, so we continue normal processing.
  • Process Action create: The Controller creates or acquires the Action instance to process this request. This is defined in the phpmvc-config.xml file using the type property, something like this:
     <action path = "salesReport"
                type = "SalesReportAction"
               ...
    
  • Process Action execute: The Controller now calls the execute() method on our Action class. For example SalesReportAction->execute(...). From the execute() method we can call our business logic as required.
  • Process Action Chain: The Controller checks if we have another Action to process. We can define a series of Actions to execute sequentially by defining an ActionChain in the application web.xml file. To define an ActionChain add a forward element to the action node with a nextActionPath attribute, something like this:
    <action path = "salesReport"
            type = "SalesReportAction"
            ...
           <forward
               name = "salesReportSuccess"
               path = "salesReport.tpl"
               nextActionPath = "salesReport2"/>
            ...
    
    The path attribute is a required attribute of the <forward .../> element, so if we have no output for this particular Action, set path = "" . See the ActionChains guide for more information on sequentially actions.
  • Process Action Forward: The Controller now forwards or redirects to the specified destination, by the specified mechanism. A forward request is handled within the current processor. The RequestProcessor simply passes control to the ActionDispatcher, which includes the specified URI (page/template). This is the default behaviour:
    <forward 
        name="forward_path 
        path="forwardRequest" 
        redirect="false"/>
    
    A redirect request actually sends the client (browser) a header response redirecting the client to a new URL. Execution of the current process terminates immediately on sending the request redirect header to the client.
    <!-- This server -->
    <forward 
        name="redirect_path 
        path="/MyApp/Main.php?do=newRequest" 
        redirect="true"/>
    
    Or perhaps a remote server:
    <!-- This server, or a remote server -->
    <forward 
        name="redirect_path 
        path="http://www.myhost.com/MyApp/Main.php?do=newRequest" 
        redirect="true"/>
    
    If there are no more Actions to process (ActionChains), the Controller process terminates.

(*) Not all of these operations are implemented at present.

The Action Form

ActionForms are used to provide initial form processing for HTTP requests to our application. To take advantage of this feature we extend the framework provided ActionForm class within our own application, and write or import specific form validation handling routines to meet our needs. In this example we use an abstract ActionForm child class called AbstractBaseForm, that extends ActionForm. This abstract class can contain common logic that may be used in several form handling classes. The form class SalesReportForm extends the AbstractBaseForm to provide a concrete example of form handling within the application. This design was discussed in the Application Object Map section above.

An ActionForm is represented in the XML Application Configuration like this:

 <form-beans>
    <form-bean name="salesReportForm" 
               type="SalesReportForm"/>
 </form-beans>

The use of an ActionForm for a particular request path is optional, and can be activated in the XML Application Configuration System using the validate attribute of the action mapping node like this:

 <action path = "salesReport"
                ...
            name = "salesReportForm"
        validate = "true"
           input = "salesReportIndex.tpl"
                ...>
 </action>
To disable form validation we use:
 validate = "false"
The default value for the validate attribute is True so there is no need to declare this attribute specifically, however by declaring the validate attribute we may avoid misunderstanding. If form validation fails, the framework Controller can return a resource (template) defined in the action node by the input attribute. For example:
 input = "salesReportIndex.tpl"
In this case if we find input errors on the HTML form, we can save and return the errors in an ActionErrors object and Controller will save the ActionErrors object to the request before forwarding control to the salesReportIndex.tpl resource. The diagram below shows how this works:

ActionForm Validation
Figure 8: ActionForm Validation

We saw in the XML Application Configuration section that the name attribute of the action mapping element binds a form to an Action. For example the action node and the form-bean node share a common name attribute (name="salesReportForm") that binds the form-bean to the specific Action.

Using ActionForm Classes

A review of the Jakarta Struts literature indicates that we should do preliminary form field checking in our ActionForm classes, for example checking for blank fields. More rigorous field checking is then done in the Action classes. However we are free to design our form authentication/authorisation scheme to best suite out application and development preferences.

In this example used in this guide we will perform our user authentication in the ActionForm (derived) class, and do additional user role authorisation (simulated) checking in the Action (derived) class.

The AbstractBaseForm Class

As noted above we are using an AbstractBaseForm class that extends the framework ActionForm class, as we saw depicted in the Application Object Map section above. We can use this AbstractBaseForm class to bundle some properties and methods that may be used in several related ActionForm concrete classes. The code section below shows a abridged version of the AbstractBaseForm class used in the example.

class AbstractBaseForm extends ActionForm {

   // ----- Instance Variables --------------------------------------------- //
   var $actionErrors = NULL;
   var $pmr          = NULL;
   var $locale       = NULL;

   // ----- Constructor ---------------------------------------------------- //
   function AbstractBaseForm() {

      $this->actionErrors =& new ActionErrors();
      $config = 'MyAppResources';
      $returnNull = False;
      $defaultLocale =& new Locale();
      $factory = NULL;
      $pmr =& new PropertyMessageResources($factory, $config, $returnNull);
      $pmr->setDefaultLocale($defaultLocale);
      $this->pmr = $pmr;
   }

   // ----- Public Methods ------------------------------------------------- //

   function reset(&$mapping, &$request) {
      ;
   }

   // Check if this is the first call to this form.
   function isSubmit(&$request, &$actionErrors) {
      if($request->getParameter('submit') == '') {

         $this->setUserName('Username=john');
         $this->setPassWord('Password=aaa');
         $this->setUserRole('accounts');
         $request->setAttribute( 'ACTION_FORM', $this );
         $actionErrors->add( 'first_form_call', new ActionError('') );
         return $actionErrors;
      } else {
         return NULL;
      }
   }

   // ----- Form Properties ------------------------------------------------ //

   var $username = '';
   var $password = '';
   var $userrole = '';

   function setUserName($username) {
      $this->username = $username;
   }
   function getUserName() {
      return $this->username;
   }
	...
}

The constructor.  In the constructor section of the AbstractBaseForm class we can see that an ActionErrors object is created and a reference to it saved to a class variable $actionErrors. This is the errors object that we will use to hold any form errors found while processing the form.

The Locale and PropertyMessageResources.  Also created are Locale and PropertyMessageResources object instances. Of particular interest is the $config='MyAppResources' variable which is used as an argument to the PropertyMessageResources constructor call. The PropertyMessageResources class provides message string handling used to format the error messages. The message strings for this example are defined in an application property file called MyAppResources.properties. The PropertyMessageResources class locates the correct properties file using the $config='MyAppResources' variable. For example the parameter $config='MyAppResources' is used by the message class to locate a property file called MyAppResources.properties

The Locale() class can be used to select specific property resources according to a users locale (language, COUNTRY and VARIANT). [Ed: Message handling and Locale require a dedicated guide. Please see the MessageFormatTestCase and PropertyMessageResourcesTestCase test cases in the ./WEB-INF/classes/phpmvc/test/ directory for usage examples.]

Form properties.  The AbstractBaseForm class also contains some form properties and a isSubmit(...) method. This example uses manually created form variables and corresponding setter and getter methods, just to keep the code as simple as possible. A more scalable approach for a real-world application would be to use one of the PHP form handling libraries, such as phplib::OOHForms or PEAR::HTML_QuickForm.

In this example we use the form properties ($username, $password, $userrole) to hold the received corresponding form variables. If we find form input errors, these form properties are sent back to the form and displayed within the input form fields, along with any error messages.

The isSubmit(...) method.  The isSubmit(...) method is used to check if a particular request is the first request. For example the Submit button was not pressed, so we have no 'submit' parameter ($request->getParameter('submit') == ''). In this case we set some default values for the form input fields ($this->setUserName('Username=john'), etc). We then save the ActionForm class to the request object ($request->setAttribute('ACTION_FORM', $this )) for later retrieval in our ActionDispatcher. We also add a dummy errors item ($actionErrors->add('first_form_call', new ActionError(''))) to force the controller to bypass the Action class processing and forward control directly to the input resource (salesReportIndex.tpl), as shown in the ActionForm Validation diagram (Figure 8) above. Finally the errors object is returned (via the concrete ActionForm class).

The SalesReportForm Class

The abridged SalesReportForm class shown below is our concrete implementation of the php.MVC frameworks ActionForm class. As can be seen, the SalesReportForm class extends the behaviour of our AbstractBaseForm, which in turn extends the ActionForm class.

 class SalesReportForm extends AbstractBaseForm {

   // ----- Constructor ---------------------------------------------------- //
   function SalesReportForm() {
      // Setup the parent object first
      parent::AbstractBaseForm();
   }

   // ----- Public Methods ------------------------------------------------- //
   function reset(&$mapping, &$request) {
      parent::reset($mapping, $request);
   }

   function validate(&$mapping, &$request) {

      $actionErrors =& $this->actionErrors;
      $pmr =& $this->pmr;
      $locale =& $this->locale;

      if($this->isSubmit($request, $actionErrors)) {
         return $actionErrors;
      }

      $username = $request->getParameter('uname');
      $password = $request->getParameter('pword');

      if($username != 'john') {
         $args = array($username);
         $msg = $pmr->getMessage($locale, 'logon.username.reqd', $args);
         $actionErrors->add( 'logon_username_reqd', new ActionError($msg) );           
      }

      if($password != 'aaa') {   
         $args = array($password, $username);
         $msg = $pmr->getMessage($locale, 'logon.password.reqd', $args);
         $actionErrors->add( 'logon_password_reqd', new ActionError($msg) );
      }

      if($actionErrors->isEmpty() == False) {
         $this->setUserName($username);
         $this->setPassWord($password);
         $this->setUserRole($request->getParameter('urole'));  
      }

      $request->setAttribute( 'ACTION_FORM', $this );

      return $actionErrors;

   }

   // ----- Form Properties ------------------------------------------------ //
   // See the base class
 }

The constructor.  As can be seen in the code section above, the first thing that happens when our SalesReportForm class is created is that the base class AbstractBaseForm constructor is called from within the class constructor method SalesReportForm(). This allows us to setup some common behaviour for our form handling, as we saw in the AbstractBaseForm Class section above.

The reset() method.  We can use the reset() method to perhaps reset the form variables in certain cases. We do not use the reset() method in this example.

The validate() method.  As noted above, ActionForm validation is controlled by the XML Application Configuration System using the validate attribute of the action mapping node like this:

 <action path = "salesReport"
                ...
            name = "salesReportForm"
        validate = "true"
           input = "salesReportIndex.tpl"
                ...>
 </action>
When a request (e.g. http://www.myhost.com/mycompany/Main.php?do=salesReport) comes in for a particular path (do=salesReport) the Controller retrieves the action mapping object for the requested path. In this case, this is the action mapping object that is generated from the above action mapping (<action path = "salesReport" ... </action>). From this action mapping the Controller determines whether we require form validation. For this particular action mapping we have declared (validate="true") that we require ActionForm validation.

ActionErrors, PropertyMessageResources and Locale.  The first thing we do within the validate method is to retrieve local references to the error, message string handling and locale resources we setup previously, in our AbstractBaseForm base class:

 $actionErrors =& $this->actionErrors;
 $pmr =& $this->pmr;
 $locale =& $this->locale;

The isSubmit(...) method.  Now we call the base class isSubmit() method to test whether this is the first call to the form:

 if($this->isSubmit($request, $actionErrors)) {
    return $actionErrors;
 }
If this is the first call to the form, the 'submit' form variable (<input type='submit' name='submit' Value='Submit'/>) will not be exist in the request parameters. On the first call we setup some default values in the base class and return the errors object which should contain the input="salesReportIndex.tpl" resource, and skip any further processing in the validate method.

Form field validation.  Now we perform some simple tests on the example form input. As noted above, in a more complex application we would probably use one of the dedicated HTML form handling libraries like phpLIB::OOHForms or PEAR::HTML_QuickForm, which would automate much of the form validation tasks. However for simple applications it may be preferable to use a light-weight approach as shown here.

The variables of interest here are the $username and $password form parameters. These fields are defined on the salesReportIndex.tpl form like this:

<input type='text' Name='uname' value='<?php print $form->username ?>' />
...
<input type='text' Name='pword' value='<?php print $form->password ?>' />
These form variables have been automatically saved to the request object, and can be retrieved like this:
 $username = $request->getParameter('uname');
 $password = $request->getParameter('pword');

A note on security
In a production application we should always assume that all input form data is tainted (e.g. untrustworthy). All inputs should be checked for correctness and valid character/numeric ranges. For example we could check the username form variable using a regular expression (RE) such as "^[a-z0-9]{8}$". This RE specifies that the username field should only contain a total of eight printable characters or numbers. Form libraries like phpLIB::OOHForms have facilities that make this task easier.

We can now attempt to validate the form variables. In a real-world application we could compare the users password to information we retrieve from a database, for this user. To keep things simple we will just compare the username variable to a string, like this:

 if($username != 'john') {
    $args = array($username);
    $msg = $pmr->getMessage($locale, 'logon.username.reqd', $args);
    $actionErrors->add( 'logon_username_reqd', new ActionError($msg) );           
 }
If the username does not equal (!=) the string 'john' then we add an error item to the errors object. First we create a error message string appropriate for this error condition.

By using the PropertyMessageResources class we can retrieve message strings from an application property file, and also use variable substitution on the message string. In this way we can use different property files for various languages, countries and variants. For this example we are using a property file called MyAppResources.properties that is locates in the example /WEB-INF/classes directory. Message strings in the property file are keyed by an identifying string before an equal sign. A typical message string from our property file looks something like

 logon.username.reqd=Please enter a valid username [{0}]
where the {0} is a replacement parameter. We can have up to four replacement parameters in a message string ({0} to {3}).

Now we can create the error message. In this case we are using the username variable as our (first) replacement parameter, so we create an array containing our replacement parameter, like this: $args = array($username). Next we call the getMessage() method on the PropertyMessageResources class like this: $msg = $pmr->getMessage($locale, 'logon.username.reqd', $args). The $msg variable should now contain the message string, perhaps something like "Please enter a valid username [jblogs]".

And finally we can add this message string to our errors object like this: $actionErrors->add( 'logon_username_reqd', new ActionError($msg) ), where 'logon_username_reqd' is the message key.

For details on how the PropertyMessageResources class selected the correct string property file, please review the AbstractBaseForm Class section above.

We handle the password form variable in a similar way as we did the username above, except that we demonstrate the use of two message replacement parameters:

$args = array($password, $username)
And use the string 'logon_password_reqd' as the message key.

Persisting the form fields.  Now we can check if any form errors were detected by calling the isEmpty() method on the $actionErrors class:

 if($actionErrors->isEmpty() == False) {
    $this->setUserName($username);
    $this->setPassWord($password);
    $this->setUserRole($request->getParameter('urole'));  
}
If $actionErrors is not empty we save the $username and $password variables to the ActionForm object. We also save the code 'urole' (or user-role) variable to the form. We make use of the user-role in the Action class.

Finally we can save the ActionForm object (SalesReportForm) to the request object for later reference on the View templates:

$request->setAttribute('ACTION_FORM', $this)
And return any $actionErrors. As noted above, if the Controller detects that any $actionErrors are set it will bypass the Action class, forwarding the request to the resource (template) defined in the action mapping by the attribute ( input="salesReportIndex.tpl").

The Action

Action classes provides an opportunity to perform additional processing of input data, accessing a database and calling application specific business classes (the Model). For example we could check the users permissions to access certain company reports, call our business logic (perhaps a report generator class) and query or update our database within our Action classes.

To use Action classes we extend the framework provided Action class within our own application. In this example we use an abstract Action child class called AbstractBaseAction, that extends Action. This abstract Action class can contain common logic that may be used in several of our application Action classes. The Action class SalesReportAction extends AbstractBaseAction to provide a concrete Action class within our application. An overview of this design can be seen in the Application Object Map section above.

An Action class is represented in the XML Application Configuration like this:

 <action-mappings>
   <action    path = "salesReport"
              type = "SalesReportAction"
              name = "salesReportForm"
          validate = "true"
             input = "salesReportIndex.tpl"
             scope = "request">
      <forward name="salesReportSuccess" path="salesReport.tpl" />
      <forward name="salesReportFailure" path="salesReportIndex.tpl"/>
   </action>
  </action-mappings>
As noted in the XML Application Configuration section above, the path="salesReport" attribute of this node acts as the key identifier for this action mapping. The Controller will identify this action mapping from a request such as:
http://www.myhost.com/mycompany/Main.php?do=salesReport

The AbstractBaseAction Class

Shown below is an abridged version of the AbstractBaseAction class.

class AbstractBaseAction extends Action {

   // ----- Instance Variables --------------------------------------------- //
   var $log          = NULL;
   var $actionErrors = NULL;
   var $pmr          = NULL;
   var $locale       = NULL;
   var $dbConn       = NULL;     

   // ----- Constructor ---------------------------------------------------- //
   function AbstractBaseAction() {

      $this->log  = new PhpMVC_Log();

      $this->actionErrors =& new ActionErrors();

      $config = 'MyAppResources';
      $returnNull = False;
      $defaultLocale =& new Locale();
      $factory = NULL;
      $pmr =& new PropertyMessageResources($factory, $config, $returnNull);
      $pmr->setDefaultLocale($defaultLocale);
      $this->pmr = $pmr;
   }

   // ----- Public Methods ------------------------------------------------- //
   function execute($mapping, $form, &$request, &$response) {
      ; // Implement this method in your concrete Action classes
   }
}

The constructor.  The AbstractBaseAction class provided common services, such as error and message string handling, as did the AbstractBaseForm class shown above. Also provided is a logging class which could be useful when tracking down errors. The Locale and PropertyMessageResources classes were covered in the Action Form section above.

The execute() method.  The execute() method is called by the framework when it receives a request for this particular action. We provide a concrete implementation of this method in our SalesReportAction class.

The SalesReportAction Class

An abridged version of the SalesReportAction is shown below. This is our application specific concrete implementation of the Action class (via the AbstractBaseAction class).

class SalesReportAction extends AbstractBaseAction {

   // ----- Constructor ---------------------------------------------------- //
   function SalesReportAction() {

      parent::AbstractBaseAction();

      $this->log->setLog('isTraceEnabled'	, True);

   }

   // ----- Public Methods ------------------------------------------------- //
   function execute($mapping, $form, &$request, &$response) {

      $trace = $this->log->getLog('isTraceEnabled');

      if($trace) {
         $this->log->trace('Start: SalesReportAction->execute(...)'.
                           '['.__LINE__.']');
      }

      $actionErrors =& $this->actionErrors;
      $pmr =& $this->pmr;
      $locale =& $this->locale;

      $username = $request->getParameter('uname');
      $password = $request->getParameter('pword');
      $group    = $request->getParameter('urole');

      // SQL as required

      $aclCheck = False;
      if($group == 'accounts') {
         $aclCheck = True;
      }

      $myForward = NULL;

      if($aclCheck == True) {
         $myForward = $mapping->findForwardConfig('salesReportSuccess');
         $sales = NULL;

         $salesReport =& new ReportsBusinessClass();
         $sales = $salesReport->getSales($this->dbConn);

         $request->setAttribute('FORM_DATA', $sales);
      } else {
         $myForward = $mapping->findForwardConfig('salesReportFailure');

         $msg = $pmr->getMessage($locale, 'report.auth.failed');
         $actionErrors->add( 'report_auth_failed', new ActionError($msg) );
         $request->setAttribute(Action::getKey('ERROR_KEY'), $actionErrors);
   
         $form->setUserName($username);
         $form->setPassWord($password);
         $form->setUserRole($group);
         $request->setAttribute( 'ACTION_FORM', $form );
  
      }  

      return $myForward;

   }
}

The constructor.  The constructor is called automatically when our class is instantiated. within the constructor we call the parent class constructor (parent::AbstractBaseAction()) to setup our common services in the base class. We can optionally enable logging in our class like $this->log->setLog('isTraceEnabled', True).

The execute() method.  As noted above the execute() method is called when the framework receives a request for this particular action path.

Logging.  Within the execute() method it may be useful to use logging to trace the execution path through our application. We can use a log trace in our methods like this:

 $trace = $this->log->getLog('isTraceEnabled');
 if($trace) {
   $this->log->trace('Start: SalesReportAction->execute(...)'.
                     '['.__LINE__.']');
 }
This will produce an output something like:
 Trace: Start: SalesReportAction->execute(...)[85]

ActionErrors, PropertyMessageResources and Locale.  We again retrieve references to the errors, message resources and locale objects:

 $actionErrors =& $this->actionErrors;
 $pmr =& $this->pmr;
 $locale =& $this->locale;
Please refer back to the Action Form for usage.

Accessing the Form Fields.  We can now retrieve the form fields, perhaps to perform further user authentication. Note that we should have verified the form fields for correct content in the ActionForm class SalesReportBaseForm. So we now assume the fields are untainted (checked for hacks).

 $username = $request->getParameter('uname');
 $password = $request->getParameter('pword');
 $group    = $request->getParameter('urole');

Checking User Permission.  At this stage we may want to check if this user has the correct permission to access resources provided by our Action class. We could query our database, or use a dedicated user authentication and permission management system such as phpLIB::auth or PEAR::LiveUser. In this example we will just fudge the user permission checking and simply test if the 'urole' form field is set to a predetermined string 'accounts', as shown below:

$aclCheck = False;
if($group == 'accounts') {
   $aclCheck = True;
}
If the user has the correct "role" permission we set the access control list (ACL) variable ($aclCheck) to True, indicating that this user has permission to access our resources.

The Business Resources.  Now we can call our business class, ReportsBusinessClass, if the ACL variable ($aclCheck) is set.

 if($aclCheck == True) {
    ...

Assuming this user is good to go, we retrieve the 'salesReportSuccess' Forward configuration object:
$myForward = $mapping->findForwardConfig('salesReportSuccess');
This corresponds to the action forward element in our phpmvc-config.xml file:
 <forward name="salesReportSuccess" path="salesReport.tpl" />
Now we create a new instance of our business report class, and call the $salesReport->getSales(...) method:
$salesReport =& new ReportsBusinessClass();
$sales = $salesReport->getSales($this->dbConn);
This business class simply returns a $sales object that contains several class variables that we can access on our report template:
 class Sales {
    var $salesNorth;
    var $salesSouth; 
    ...
 }

Note: To reduce coupling and promote code reuse, we should avoid passing library specific objects as arguments to our business classes. In the example above we simply pass a database reference ( $this->dbConn) to the business object method. This design may enable we to reuse our business classes in other applications.

Finally we save the $sales object to the request. We will have access to this object on our View template:

$request->setAttribute('FORM_DATA', $sales);

Permission Denied.  If this user is not authorised to access this particular resource ($aclCheck = False), we retrieve the 'salesReportFailure' Forward configuration object:

 $myForward = $mapping->findForwardConfig('salesReportFailure');
This corresponds to the action forward element in our phpmvc-config.xml file:
 <forward name="salesReportFailure" path="salesReportIndex.tpl"/> 
The next task is to setup the appropriate error message to be displayed on the form. Please see the Action Form section above for a description of the PropertyMessageResources and ActionErrors class usage .
 $msg = $pmr->getMessage($locale, 'report.auth.failed');
 $actionErrors->add( 'report_auth_failed', new ActionError($msg) );
Note that we are creating a message with no replacement parameters ($args) in this case.

The next statement saves the errors object to the request object for later reference on our template page:

 $request->setAttribute(Action::getKey('ERROR_KEY'), $actionErrors);
We can later retrieve the errors object like this:
 $errors = $request->getAttribute( Action::getKey('ERROR_KEY') );

The relevant input fields are now saved to the $form (ActionForm) object, that was passes as a parameter to the SalesReportAction->execute() method:

 $form->setUserName($username);
 $form->setPassWord($password);
 $form->setUserRole($group);
And the $form object is saved to the request object for later reference:
 $request->setAttribute( 'ACTION_FORM', $form );
The $form object can be retrieved like this:
 $form = $request->getAttribute('ACTION_FORM');

Next Stop: The ActionDispatcher.  All that we need to do now is return the $myForward object we setup previously, depending on the users permission to access our business reports. The $myForward object now contains the name of the resource (template) that is to be used.

 return $myForward;
The Controller now calls the ActionDispatcher to handle the processing and dispatch of the generated response. Or more precisely, our application specific class that extends the framework ActionDispatcher class.

The View

Before we look at the ActionDispatcher, we should take a quick look at the View, or template resources.

As mentioned above in this guide we will be using simple page templates as our View components. These templates contain some PHP language tags that use form data objects that we created earlier. This technique may be more suitable for small projects that do not warrant the additional overhead of specialised form handling classes. For larger projects developers can use a library such as phplib::OOHForms or PEAR::HTML_QuickForm for more automated approach to View implementation in php.MVC.

Whichever technique is used, the application template resources should be constructed in such a way that non-programmers can easily work on the template pages without affecting our critical business logic. For example, no business code should be included in the templates, only calls to the predefined data objects.

There is currently much discussion on the pros and cons of the various templating techniques. The benefits and costs of these approaches to page design is a research topic in itself and is beyond the scope of this guide. Please see the Resources section of this guide for links to other resources on this topic.

The example used in this guide uses two template resources. The salesReportIndex.tpl page is the index, or starting page. If we find input errors when processing the form, we return the user to this page and display the appropriate error messages. The salesReport.tpl page acts as a simple business report that displays our predefined sales data.

The Sales Report Index Page.  The Sales Report Index page simply allows the user to enter a username, password and user-role. We saw in the Action section how the user-role input variable was used to check the users permission to access our business report.

A slightly abridged version of the sales report index page template (salesReportIndex.tpl) is shown below:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
   <title>Sales Report Index Page</title>
   <link rel='stylesheet' type='text/css' href="./style/man.css">
</head>

<body leftmargin='2' topmargin='2' marginwidth='2' marginheight='2'>

<table class='header'>
   <tr>
      <td align="center">
         <font class="pageHeader">Sales Report Index Page</font>
      </td>
   </tr>
</table>

<h3>Welcome to Our Sales Reporting Page</h3><br>

...

<font color='red'>
<?php
print ($er = $errors->getItemString('report_auth_failed')) ? $er.'<br>': '';
print ($er = $errors->getItemString('logon_username_reqd')) ? $er.'<br>': '';
print ($er = $errors->getItemString('logon_password_reqd')) ? $er.'<br>': '';
?>
</font>

<form action='Main.php?do=salesReport' Method='POST'/>
<table class='' border=1>
   <tr>
      <td bgcolor='#EFEFEF'>
         Username:
      </td>
      <td>
         <input type='text' Name='uname' value='<?php print $form->username ?>' />
      </td>
   </tr>
   <tr>
      <td bgcolor='#EFEFEF'>
         Password:
      </td>
      <td>
         <input type='text' Name='pword' value='<?php print $form->password ?>' />
      </td>
   </tr>
   <tr>
      <td bgcolor='#EFEFEF'>
         User Role:
      </td>
      <td>
         <input type='text' Name='urole' value='<?php print $form->userrole ?>' />
      </td>
   </tr>
</table>
<input type='submit' name='submit' Value='Submit' />

</body>
</html>
As can be seen this is simply a standard Web page, with the addition of some PHP language tags.

The first item of interest is the error handling section:

<?php
print ($er = $errors->getItemString('report_auth_failed')) ? $er.'<br>': '';
print ($er = $errors->getItemString('logon_username_reqd')) ? $er.'<br>': '';
print ($er = $errors->getItemString('logon_password_reqd')) ? $er.'<br>': '';
?>
Here we access the errors items (if any) we setup previously using the error string identifiers like 'report_auth_failed'. Otherwise a null string is inserted into the page (''). The objects referred to on the template are made available via our ReportActionDispatcher , as we will see in the next section.

The next item of interest is the form tag shown below:

 <form action='Main.php?do=salesReport' Method='POST'/>
The action in the form tag specifies the path to the Action class we developed earlier. This path ( do=salesReport) is used by the Controller to retrieve the matching action mapping:
 <action path = "salesReport"
         type = "SalesReportAction"
         ... />
 </action> 

Next comes the form input tags: "Username"; "Password" and "User Role". They are all much the same, so we will just look at the Username field, as shown below:

 <input type='text' Name='uname' value='<?php print $form->username ?>'/>
As we can see this is a standard HTML input element, with the exception of the value attribute. We are using a simple PHP language statement within the value attribute to retrieve the "username" property from the $form object, which is actually our SalesReportForm ActionForm class.

And the usual submit element completes the form element in this page:

 <input type='submit' name='submit' Value='Submit'/>

When the user submits the form with the correct entries the sales report page is displayed.

The Sales Report Page.  The sales report page displays a table containing our sales data to the user after s/he has entered the correct credentials in the preceding form.

An abridged version of the sales report page template (salesReport.tpl) is shown below:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
...
<h4>Sales Reports (Millions of Dollars AU)</h4>
<table class='idxTable' bgcolor='#C0C0C0'>
<tr class='idxTableHeader'>
   <td>Report Description</td>
   <td>Revenues</td>
</tr>
<tr class='idxTableRow'>
   <td class='idxTableCell' nowrap>
      Northern Sector
   </td>
   <td class='idxTableCellData' nowrap>
      $ <?php print $data->salesNorth ?>
   </td>
</tr>
...
</table>
...
</body>
</html>
This is a very simple HTML page containing a single table.

Within the <td ... /> tag we use a PHP tag to access the $data object property salesNorth:

 <?php print $data->salesNorth ?>
This $data object is actually the form data we created in the SalesReportAction class above. In the ActionDispatcher section below we will see that the $data object made available to this template like this:
 $data = $request->getAttribute('FORM_DATA');

The remaining table rows are very similar, and simply retrieve the remaining $data properties: salesSouth; salesEast and salesTerrit.

The sales report page should look something this:

TThe Sales Report Page
Figure 9: The Sales Report Page

The Action Dispatcher

The ActionDispatcher is responsible for selecting the correct View resource and composing the response to the user. The diagram below shows an overview of the ActionDispatcher process:

The Action Dispatcher
Figure 10: The Action Dispatcher

Extending The ActionDispatcher.  In most cases we will need to extend the php.MVC framework supplied ActionDispatcher class with our own application specific dispatcher class. In these example we create a dispatcher class called ReportActionDispatcher. Within our dispatcher class we need to override the serviceResponse() method, as shown below:

class ReportActionDispatcher extends ActionDispatcher {

   function ReportActionDispatcher($uri='', $wrapper='', $servletPath='',
                                 $pathInfo='', $queryString='', $name='') {

      parent::ActionDispatcher($uri='', $wrapper='', $servletPath='',
                                 $pathInfo='', $queryString='', $name='');

      $this->log->setLog('isDebugEnabled' , False);
      $this->log->setLog('isTraceEnabled' , False);
   }

   function serviceResponse(&$request, &$response) {

      $trace = $this->log->getLog('isTraceEnabled');
      if($trace)
         $this->log->trace('Start: TestActionDispatcher->serviceResponse(..)['.__LINE__.']');

      $requestURI = $this->uri;

      $form = $request->getAttribute('ACTION_FORM');
      $data = $request->getAttribute('FORM_DATA');
      $errors = $request->getAttribute( Action::getKey('ERROR_KEY') );

      $pageBuff = '';
      ob_start();
         include $requestURI;
         $pageBuff = ob_get_contents();
      ob_end_clean();

      $response->setResponseBuffer($pageBuff);
   }
}

The serviceResponse() Method.  Within the serviceResponse() method we can enable logging by calling the logging class (after setting the appropriate logging flags in the constructor above).

Now we retrieve the resource (URI) to use as the View object for this request:

 $requestURI = $this->uri;
In this example the URI will be either the salesReportIndex.tpl or salesReport.tpl template page.

Now we retrieve the ActionForm, form data and ActionErrors objects.

 $form = $request->getAttribute('ACTION_FORM');
 $data = $request->getAttribute('FORM_DATA');
 $errors = $request->getAttribute( Action::getKey('ERROR_KEY') );
These are the objects we creates in our ActionForm and Action classes.
Note: These objects are now in the scope of this method, and so will be visible to the template (URI) we will load next.

Now we can include our resource template ($requestURI) within the PHP output buffering statements: ob_start() and ob_end_clean():

 ob_start();
    include $requestURI;
    $pageBuff = ob_get_contents();
 ob_end_clean();
As the template is being included, any PHP statements within the template will be executed as if the template were a normal PHP script. The $form, $data and $errors objects are now visible to any code in the template.

The PHP output buffering allows us to capture the included resource ($requestURI) and save the output to a buffer variable: $pageBuff. The $pageBuff variable now contains the complete HTTP response contents, which our user will see as a Web page in her/his browser.

And finally we return the $pageBuff contents to the Dispatcher:
 $response->setResponseBuffer($pageBuff);
The Dispatcher will now send the $pageBuff contents as the HTTP response to the client (usually the users browser).

Installing the Library

Download the latest version of the php.MVC library here: CVS Snapshots.

Unpack the library archive to a directory, preferably off the Web root.

To access the library classes from an application, set the $appServerRootDir variable in the applications Main.php file, something like:

 $appServerRootDir = 'D:/Dev/PHP/phpmvc-base'; // no trailing slash

Review the Web Server Directory Layout and Files section for more information on directory layout and security.

External Libraries.  Although php.MVC includes some external libraries, or part of, such as some PEAR modules, these are mainly for the convenience of new users. It would be desirable for developers to install and maintain their own external libraries.

Running the Example

ATTENTION.  Please note that the example application requires the library version, php.MVC Beta 0.3.4 or better. A new method getItemString(...) was added to the ActionErrors class to simplify error string retrieval. This method call is used in the example template.

Download the example application. The complete working example with source files and including this guide can be downloaded here: php.MVC Users Guide 101 example.

Unpack the php.MVC Users Guide 101 example archive to a directory within the Web root of the host server. Perhaps something like: C:/WWW/SalesReports

Check the /WEB-INF/.htaccess file is setup to block Web access to the application /WEB-INF/ directory and sub-directories.

Edit the application Main.php file. Set the path to the php.MVC library, and the path to the example application. Something like:

 $appServerRootDir = 'D:/Dev/PHP/phpmvc-base'; // no trailing slash
 $moduleRootDir = 'C:/WWW/SalesReports'; // no trailing slash

To test the example application, enter a URL something like: http://www.myhost.com/SalesReports/Main.php?do=salesReport

Review the Web Server Directory Layout and Files section for more information on directory layout and security.

Resources

php.MVC Web Sites
The project homepage: www.phpmvc.net.
The SourceForge project page and forums: php.MVC on SourceForge.

Troubleshooting Guide.  Please check the Troubleshooting Guide first, when having problems with the php.MVC framework. You may find the answer to your problem has been detailed there, thus saving you the time of ask for help and waiting for a reply.

Other Guides.  There a several guides on other topics listed here: User Guides.

The php.MVC Library SourceCVS Snapshots.

Examples.  There a working examples on task based topics listed here: Other Releases.

Compare and Contrast:
New York PHP: A study of how different templating solutions compare
WACT: Template View - More research regarding template technology

Other Useful Resources:
The Web Design Group's HTML 4.0 Reference

The Web Design Group's CSS Reference

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值