In the following sections we will examine the core components of the php.MVC library:
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.
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 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:
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.
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.
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:
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:
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:
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 Source.
CVS 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
Document version: 1.0 - 02-April-2004
Copyright © 2004 John C. Wildenauer. All rights reserved.