PHP4MVC

An MVC architecture in PHP4

Mark Schofield

 

Return to articles

Download source | Demonstration

Table of Contents

1. MVC

1.1 Introduction

MVC (Model-View-Controller) is an architectural design pattern for separating the business logic (Model) from the user interface (View) using an intermediate layer (Controller).

The aim of MVC is to make applications easier to design and maintain. By separating the model from the view, business logic can be written without any concern for the way in which data will be presented to the user. The view can be reorganised, or an additional view added without the need to revisit the code in the model.

A well designed MVC produces code that is both modular and reusable. Components from both the view and controller can be borrowed from an existing MVC application and reconfigured for use in another, allowing the development effort to shift into designing the model.

1.2 Web-based MVC

Web-based MVC differs greatly from Winforms-type MVC, and requires a different approach. The principal differences between the two are :

Winform Web
View is a graphical user interface (GUI) View is a static web page
Application is statefull Application is stateless
Multiple views possible Single view

Web-based MVC uses the request/response cycle of HTTP. The user interface is typically composed of static HTML pages which are re-rendered by the server in every cycle.

The sequence of events in the request/response cycle can be summarized as follows :

PHP4MVC diagram

1.3 Struts

Struts is a open-source MVC architecture written in Java and JSP. It introduces 3 new elements to the basic schema defined above :

2. PHP4MVC

2.1 Introduction

PHP4MVC is my personal implementation of MVC using classes written in PHP4. PHP4 is, at the time of writing this article, the latest version of PHP provided by my ISP. Although PHP4 lacks some of the best object orientated principles such as abstract classes and interfaces found in PHP5, it is nevertheless possible to write a good object orientated MVC architecture in PHP4.

PHP4MVC borrows from Struts, including the following elements :

The sequence of events in a PHP4MVC request/response cycle can be summarized as follows :

2.2 Design Patterns

The PHP4MVC architecture incorporates 3 design patterns : Factory, Observer and Component :

2.2.1 Factory

Action, ActionForm and other action dependent classes are created dynamically using a class factory:

ClassFactory.php

<?php
class ClassFactory {

	function ClassFactory() {
	}

	function getInstance($type, $path) {
		include("${path}");
	    	
        	$classname = "${type}";

        	if (class_exists($classname)) {
            		return $obj =& new $classname;
       		}
     		return NULL;
    	}
} 
?>

2.2.2 Observer

An action (Observer) is notified by the model (Observable) when the model changes (the Observer and Observable classes together make up the Observer design pattern)

Observer.php

<?php
// Observer class of Observer design pattern
class Observer {
	var $name;

	function Observer($name) {
		$this->__uniqid = uniqid(rand(), true);
		$this->name = $name;
	}

	// Observer is notified by the Observable class
	function notify($sender, $args) {
	}
}
?>

Observable.php

<?php
// Observable class of Observer design pattern
class Observable {
	var $_listeners = array();

	function Observable() {
	}

	function register(&$listener) {
		if (!is_object($listener) || !is_a($listener, 'Observer'))
			return;

		foreach ($this->_listeners as $el) {
			if ($el === $listener) {
				return;
			}
		}
		$this->_listeners[] =& $listener;
	}

	// Observable class notifies Observer classes
	function _notify($sender, $args) {
		for ($i=0; $i<sizeof($this->_listeners); $i++) {
			$this->_listeners[$i]->notify($sender, $args);
		}
	}
}
?>

2.2.3 Component

The view is composed of a hierarchy of components (widgets) rendered in XHTML using the Component design pattern (see View).

2.3 Basic elements

2.3.1 Configuration file

As with Struts, a configuration file is used to direct the work of the front controller :

config.php

<?php
// Config file used by controller class

// Constants
$conf['cActionKey'] = 'a';		// variable in URL for action
$conf['cActionDefault'] = 'none';	// default action
$conf['cActionInvalid'] = 'Invalid';	// invalid action
$conf['cTempC'] = 'c';
$conf['cTempF'] = 'f';

// Actions requested
$conf['get:none'] = 'CelsiusToFarenheit';
$conf['get:ctof'] = 'CelsiusToFarenheit';
$conf['post:ctof'] = 'CelsiusToFarenheit';
$conf['get:ftoc'] = 'FarenheitToCelsius';
$conf['post:ftoc'] = 'FarenheitToCelsius';
$conf['get:invalid'] = 'Invalid';

// ActionForms
$conf['CelsiusToFarenheit']['actionForm'] = array('name'=>'TemperatureActionForm', 'path'=>'./controller/actionform/TemperatureActionForm.php');
$conf['FarenheitToCelsius']['actionForm'] = array('name'=>'TemperatureActionForm', 'path'=>'./controller/actionform/TemperatureActionForm.php');
$conf['Invalid']['actionForm'] = array('name'=>'TemperatureActionForm', 'path'=>'./controller/actionform/TemperatureActionForm.php');

// Actions
$conf['CelsiusToFarenheit']['action'] = array('name'=>'CelsiusToFarenheitAction', 'path'=>'./controller/action/CelsiusToFarenheitAction.php', 
'model_name'=>'TemperatureModel', 'model_path'=>'./model/TemperatureModel.php', 'dispatcher_name'=>'ActionDispatcher', 
'dispatcher_path'=>'./controller/ActionDispatcher.php', 'forward'=>array('success'=>'./view/CtoF.php', 'failure'=>'./view/error.php'));
$conf['FarenheitToCelsius']['action'] = array('name'=>'FarenheitToCelsiusAction', 'path'=>'./controller/action/FarenheitToCelsiusAction.php', 
'model_name'=>'TemperatureModel', 'model_path'=>'./model/TemperatureModel.php', 'dispatcher_name'=>'ActionDispatcher', 
'dispatcher_path'=>'./controller/ActionDispatcher.php', 'forward'=>array('success'=>'./view/FtoC.php', 'failure'=>'./view/error.php'));
$conf['Invalid']['action'] = array('name'=>'InvalidAction', 'path'=>'./controller/action/InvalidAction.php', 'model_name'=>'TemperatureModel', 
'model_path'=>'./model/TemperatureModel.php', 'dispatcher_name'=>'ActionDispatcher', 
'dispatcher_path'=>'./controller/ActionDispatcher.php', 'forward'=>array('success'=>'', 'failure'=>'./view/error.php'));
?>

This configuration file is from the demonstration project and will be covered in detail in the following chapters

2.3.2 Front Controller

The front controller is the entry point of the application, and all user actions are directed to it. It's role is to map user requests to a suitable ActionForm and Action using the values in the configuration file. The front controller instantiates the ActionForm and Action classes using the ClassFactory and passes over responsibility to the Action for handling the request.

Controller.php

<?php
include './lib/ClassFactory.php';

// Basic web application controller 
class Controller {
	var $classFactory;		// factory for object creation
	var $config;			// configuration file for controller

	// Constructor for controller
	function Controller() {
		$this->classFactory =& new ClassFactory();
	}

	// Set configuration file for controller
	function setConfig($config) {
		$this->config = $config;	
	}
	
	// Process request
	function process() {
		if (file_exists($this->config)) {
			include $this->config;

			// Instantiate objects
			$actionKey = $this->getAction($conf);
			$actionVal = $conf[$actionKey]['action'];
			$actionFormVal = $conf[$actionKey]['actionForm'];
		
			$actionObj =& $this->getActionObject($actionVal);
			$actionFormObj =& $this->getActionFormObject($actionFormVal);
			$actionDispatcherObj =& $this->getActionDispatcherObject($actionVal);
			$actionObj->setActionForm($actionFormObj);

			$actionObj->setAttribute('success', $actionVal['forward']['success']);
			$actionObj->setAttribute('failure', $actionVal['forward']['failure']);
			$actionObj->setAttribute('model_name', $actionVal['model_name']);
			$actionObj->setAttribute('model_path', $actionVal['model_path']);
		
			// Process objects
			$actionObj->process();
			$actionDispatcherObj->process($actionObj);
		}
	}

	// Get action from URL, otherwise get default action
	function getAction($conf) {
		$a = $conf['cActionDefault'];
		if (isset($_GET['a'])) { 
			$a = strtolower($_GET['a']);
		}
		
		$a = strtolower($_SERVER['REQUEST_METHOD']).":$a";
		if (isset($conf[$a]) ) { 
			return $conf[$a];
		} else {
			return $conf['cActionInvalid']; 
		}
	}

	// Create action object
	function getActionObject($action) {
		return $this->classFactory->getInstance($action['name'], $action['path']);
	}


	// Create action form object
	function getActionFormObject($actionForm) {
		return $this->classFactory->getInstance($actionForm['name'], $actionForm['path']);
	}

	// Create action dispatcher object
	function getActionDispatcherObject($action) {
		return $this->classFactory->getInstance($action['dispatcher_name'], $action['dispatcher_path']);
	}
}
?>

2.3.3 ActionForm

The action form is a container for data supplied in the HTTP request

ActionForm.php

<?php
// Base class for action form which handles $_GET, $_POST, $REQUEST arrays
class ActionForm {
	var $get;
	var $post;
	var $request;
	var $server;
		
	function ActionForm() {
	}
	
	function process() {
		$this->get = $this->decodeVals($_GET);
		$this->post = $this->decodeVals($_POST);	
		$this->request = $this->decodeVals($_REQUEST);
		$this->server = $_SERVER;
		
		$this->processRequest();
	}

	function decodeVal($val) {
		return stripslashes(urldecode($val));
	}

	function decodeVals($vals) {
		$arr = array();

		foreach ($vals as $key => $val) {
			$arr[$key] = $this->decodeVal($val);
		} 

		return $arr;
	}

	// Update variables in request array
	function processRequest() {
		// implement in child class
	}
}
?>

2.3.4 Action

The action is a controller which handles a specific request. It instantiates the model and updates the model with data from the action form. The model (Observable) processes the data and notifies the action (Observer) when the state of the model has changed. Data from the model is stored in the action form. The action then determines the forward URI to use according to whether the processing was error free (success) or not (failure)

Action.php

<?php
include './lib/Observer.php';

// Class containing the references to the model and the action form 
// Action form supplies data to the model
// Model returns data to the action form
class Action extends Observer {
	var $model;				// model object
	var $actionForm;			// action form object
	var $errors		= array();	// error array
	var $classFactory;			// factory for creating objects
	var $attributes		= array();	// action attributes array
	
	function Action() {
		$this->classFactory =& new ClassFactory();
		$this->initialize();
	}

	function setActionForm(&$actionForm) {
		$this->actionForm =& $actionForm;
	}

	function &getActionForm() {
		return $this->actionForm;
	}
	
	function addError($error) {
    		$this->errors[] = $error;
	}

	function getErrors() {
		return $this->errors;
	}

	function setAttribute($name, $attribute) {
		$this->attributes[$name] = $attribute;
	}
	
	function &getAttribute($name) {
		if (array_key_exists($name, $this->attributes)) {
			$resp =& $this->attributes[$name];		
		} 
		return $resp;
	}
	
	// Process action 
	function process() {
		// Process action form
		$this->actionForm->process();
		
		// Create instance of model and register action as a listener
		$this->model =& $this->classFactory->getInstance($this->getAttribute('model_name'), $this->getAttribute('model_path'));
		$this->model->register($this);
			
		// Initialize model with request array from action form
		$this->model->init($this->actionForm->request);	
		
		// Validate action before processing model
		$this->validate();	
		if ($this->isValid()) {
			$this->finalize();
			$this->updateModel();				
		}

		$this->handleUri();	
	}

	// Start processing in model
	function updateModel() {
		// implement this in child class	
	}

	function initialize() {
		// implement this in child class
	}

	// Validate the request and store any errors in the error array
	function validate() {
		// implement this in child class
	}

	function finalize() {
		// implement this in child class
	}

	// Determine URI for dispatcher
	function handleUri() {
		if ($this->isValid()) {
			$this->setAttribute('URI', $this->getAttribute('success'));	// route to success page
		} else {
			$this->setAttribute('URI', $this->getAttribute('failure'));	// route to failure page
		}
	}
	
	// Determine whether action is in a valid state (true) or not (false)
	function isValid() {
    		return (count($this->errors) > 0 ? false : true);
	}
	
	// Receive notifications of changes in state of model
	function notify($sender, $args) {
		// Handle property changes
		if (isset($args['property'])) {
			// Update action form property by calling property accessor in model
			$this->actionForm->{$args['property']} = call_user_func(array(&$this->model, 'get'.ucfirst($args['property'])));		
		}

		// Handle errors
		if (isset($args['error'])) {
			// Update error array by calling error accessor in model
			$this->addError($this->model->getErr($args['error']));		
		}
	}
}
?>

2.3.5 Model

The model uses data sent by the request to perform application specific processing, keeping track of any errors that are encountered.

Model.php

<?php
include './lib/Observable.php';

// Base model class with error handling
class Model extends Observable {
	var $vars = array();			// input variables array
	var $errors = array();			// error array

	function init($vars=null) {	
		// Initialize variable array with variable array from caller
		$this->vars = $vars;		
	}

	// Add an error to the error array and notify any listeners
	function addError($error) {
	    	$this->errors[] = $error;
	    	$this->_notify($this, array('error'=>$error));
	}

	function getErrors() {
		return $this->errors;
	}
	
	// Determine whether model is in a valid state (true) or not (false)
	function isValid() {
		return (count($this->errors) > 0 ? false : true);
	}
}
?>

2.3.6 ActionDispatcher

The action dispatcher transforms data stored in the action form into values suitable for the view. It then includes the page for the view.

ActionDispatcher.php

<?php
// Handle data from the action and create view
class ActionDispatcher {
	function process(&$action) {
		$data = $action->getActionForm();
		$errors = $action->getErrors();
		$uri = $action->getAttribute('URI');

		include $uri;
	}
}
?>

2.3.7 View

The view is built from objects that inherit a base class called Widget. Based on the Component design pattern, widgets can be arranged in a hierarchy.

The XHTML markup of each widget is created by calling methods on the Document Object Model (DOM). The Widget class has a static instance of the DOM API which is used to create DOM nodes dynamically. Child nodes are appended to parent nodes in order to create a node hierarchy.

When the required nodes have been created, the widget is built and the markup retrieved for display. Performing these methods on the topmost widget ensures that the entire markup of a hierarchy of widgets can be retrieved in a single call.

Widget.php

<?php
// Base class for widgets
class Widget {
	var $dom;
	var $name;
	var $out;
	var $parent;
	var $children;
	var $node;

	function Widget($name, $out='string') {
		$this->__uniqid = uniqid(rand(), true);
		$this->dom =& staticDOM(); // construct DOM from static instance
		$this->name = $name;
        	$this->out = $out;
		$this->parent = null;
		$this->children = array();
		$this->comment = false;
		$this->trace = true;
		$this->initialize();
	}

	function initialize() {
		// implement this in child class
	}	

	function finalize() {
		// implement this in child class
	}

	function create() {
		// implement this in child class
	}


	function addChild(&$child) {
		if (!is_object($child) || !is_a($child, 'Widget'))
			return false;

		if ($child->parent != null)
			$child->parent->removeChild($child);

		$this->children[] =& $child;
		$child->parent =& $this;

		return true;
	}

	function removeChild(&$child) {
		if (!is_object($child) || !is_a($child, 'Widget'))
			return false;

		for ($i = 0; $i < count($this->children); $i++) {
			if (isset($this->children[$i]) && $this->children[i] === $child) {
				unset($this->children[$i]);
				$this->children[$i]->parent = null;
				return true;
			}
		}

		return false;
	}

    	function build() {
		$this->finalize();
		$this->create();

		if ($this->parent == null) {
			$this->dom->appendChild($this->node);		// add root
		} else {
			if ($this->node != null and $this->parent->node != null) { 
				$this->parent->node->appendChild($this->node);
			}
		}

		if ($this->children != null) {
			for ($i = 0; $i < count($this->children); $i++) {
				$this->children[$i]->build();
			}			
		}
    	}

	function setOut($out) {
		$this->out = $out;
	}

	function setDom(&$dom) {
		$this->dom =& $dom;
	}
		
	// Create a DOM element node with multiple attribute(s) and or text node
	function &createNode($name, $attributeArr=null, $text=null) {
		$element = $this->dom->createElement($name);
		
		if ($text != null) {
			$element->appendChild($this->dom->createTextNode($text));
		}
	
		if ($attributeArr != null) { 
			for($i = 0; $i < count($attributeArr); $i++) {
				$element->setAttribute($attributeArr[$i][0], $attributeArr[$i][1]);
			} 
		}

		return $element;
	} 

	function &fetch($xmlDeclaration=false) {
        	if ( $this->out == 'string') {
			if($xmlDeclaration) {            		
				return $this->dom->saveXML();
			} else {
				return (trim(preg_replace('/<\?xml.*\?>/', '', $this->dom->saveXML(), 1)));	// remove xml declaration
			}
        	} else {
            		return $this->node;
        	}
	}
}
?>

In the PHP4MVC implementation, a facade class called DOM.inc.php is used to translate PHP5 calls to the DOM API into PHP4 calls to the DOM XML API, thus making the solution forward compatible with PHP5.

2.4 Example

To illustrate the PHP4MVC architecture, I will use the example of a Celsius to Farenheit converter. This example uses the configuration file config.php shown above

In this example, the user is presented with one of two views; a Celsius to Farenheit view or a Farenheit to Celsius view.

CtoF screenshot

The typical sequence of operations would be :

  1. User enters a temperature and clicks Submit
  2. The front controller selects the appropriate action (Celsius to Farenheit conversion or Farenheit to Celsius conversion)
  3. The action creates the model and passes it the value for conversion
  4. The model handles the conversion and supplies the result to the action.
  5. The action updates the action form with the result and passes the action form to the action dispatcher.
  6. The action dispatcher decides which view to display and provides it with data from the action form
  7. The view displays the value entered by the user and the result of the conversion

We shall take the example of a user who has navigated to the index page, entered a Celsius temperature of 45 and clicked the Submit button

2.4.1 TemperatureController

The index page index.php instantiates the front controller class TemperatureController and passes it the path to the configuration file config.php

index.php

<?php
include './controller/TemperatureController.php';

$c =& new TemperatureController();
$c->setConfig('./config.php');
$c->process();
?>

TemperatureController.php

<?php
include 'Controller.php';

// Test controller 
class TemperatureController extends Controller {
}
?>

The TemperatureController uses the front controller process() method to handle the request

Controller.php

	...
	// Process request
	function process() {
		if (file_exists($this->config)) {
			include $this->config;

			// Instantiate objects
			$actionKey = $this->getAction($conf);
			$actionVal = $conf[$actionKey]['action'];
			$actionFormVal = $conf[$actionKey]['actionForm'];
		
			$actionObj =& $this->getActionObject($actionVal);
			$actionFormObj =& $this->getActionFormObject($actionFormVal);
			$actionDispatcherObj =& $this->getActionDispatcherObject($actionVal);
			$actionObj->setActionForm($actionFormObj);

			$actionObj->setAttribute('success', $actionVal['forward']['success']);
			$actionObj->setAttribute('failure', $actionVal['forward']['failure']);
			$actionObj->setAttribute('model_name', $actionVal['model_name']);
			$actionObj->setAttribute('model_path', $actionVal['model_path']);
		
			// Process objects
			$actionObj->process();
			$actionDispatcherObj->process($actionObj);
		}
	}	
	...

The getAction($conf) method of the front controller determines which action to take from data supplied in the HTTP request

config.php

...
$conf['cActionDefault'] = 'none';	// default action
...
$conf['get:none'] = 'CelsiusToFarenheit';
$conf['get:ctof'] = 'CelsiusToFarenheit';
$conf['post:ctof'] = 'CelsiusToFarenheit';	
...

Controller.php

	...
	// Get action from URL, otherwise get default action
	function getAction($conf) {
		$a = $conf['cActionDefault'];
		if (isset($_GET['a'])) { 
			$a = strtolower($_GET['a']);
		}
		
		$a = strtolower($_SERVER['REQUEST_METHOD']).":$a";
		if (isset($conf[$a]) ) { 
			return $conf[$a];
		} else {
			return $conf['cActionInvalid']; 
		}
	}
	...

In our example :

It should be noted that the following user actions also lead to the CelsiusToFarenheit action :

  1. Navigating to Celsius to Farenheit page via a link (action key is get:ctof)
  2. Navigating to Celsius to Farenheit page via a link and clicking 'Submit' (action key is post:ctof)

2.4.2 CelsiusToFarenheitAction and TemperatureActionForm

Using the value of A from the request (see above), the front controller must perform the following tasks :

Looking at the CelsiusToFarenheit definition in config.php:

config.php

...
$conf['CelsiusToFarenheit']['actionForm'] = array(
'name'=>'TemperatureActionForm', 
'path'=>'./controller/actionform/TemperatureActionForm.php'
);
...
$conf['CelsiusToFarenheit']['action'] = array(
'name'=>'CelsiusToFarenheitAction', 
'path'=>'./controller/action/CelsiusToFarenheitAction.php', 
'model_name'=>'TemperatureModel', 
'model_path'=>'./model/TemperatureModel.php',
'dispatcher_name'=>'ActionDispatcher', 
'dispatcher_path'=>'./controller/ActionDispatcher.php', 
'forward'=>array('success'=>'./view/CtoF.php', 'failure'=>'./view/error.php')
);
...

The following action mappings are obtained:

Key Value
success ./view/CtoF.php
failure ./view/error.php
model_name TemperatureModel
model_path ./model/TemperatureModel.php

The following action is instantiated:

Key Value
name CelsiusToFarenheitAction
path ./controller/action/CelsiusToFarenheitAction.php

The following action form is instantiated:

Key Value
name TemperatureActionForm
path ./controller/actionform/TemperatureActionForm.php

The following action dispatcher is instantiated:

Key Value
dispatcher_name ActionDispatcher
dispatcher_path ./controller/ActionDispatcher.php

2.4.3 Processing TemperatureActionForm and CelsiusToFarenheitAction

TemperatureActionForm inherits from the base class ActionForm. It is initialised using data supplied in the HTTP request. In the TemperatureActionForm, a Celsius temperature c or a Farenheit temperature f are expected.

In our example, a Celsius temperature has been supplied

TemperatureActionForm.php

<?php
include 'ActionForm.php';

// Action form for temperature
class TemperatureActionForm extends ActionForm {
	var $tempC;
	var $tempF;	
	
	function TemperatureActionForm() {
	}
	
	function processRequest() {
		$this->tempC = isset($_REQUEST['c']) && $_REQUEST['c']!="" ? $_REQUEST['c'] : NULL;
		$this->tempF = isset($_REQUEST['f']) && $_REQUEST['f']!="" ? $_REQUEST['f'] : NULL;
	}	
}
?>

CelsiusToFarenheitAction inherits from the base class Action which performs the following tasks :

In config.php, the action CelsiusToFarenheit references the model TemperatureModel. CelsiusToFarenheitAction therefore creates an instance of TemperatureModel, passing it the Celsius temperature stored in TemperatureActionForm through a call to the setTempC($tempC) method

CelsiusToFarenheitAction.php

<?php
include 'Action.php';

class CelsiusToFarenheitAction extends Action {
	
	function validate() {
		if (isset($this->actionForm->tempC)) {
			if (!is_numeric($this->actionForm->tempC)) {
				$this->addError("Celsius temperature must be numeric.");	
			}
		}
	}

	function updateModel() {
		$this->model->setTempC($this->actionForm->tempC);
	}
}
?>

2.4.4 TemperatureModel

The TemperatureModel takes the Celsius temperature of 45 and calculates the Farenheit temperature of 113 (45 * 9 / 5 + 32). The TemperatureModel then notifies CelsiusToFarenheitAction of a change in state via the $this->_notify(...) method

TemperatureModel.php

<?php
// Temperature in celsius and farenheit units
include 'Model.php';

class TemperatureModel extends Model {
	var $temperatureF = 32; 	// farenheit temperature
	
	function getTempF() {
		return $this->temperatureF;
	}
	
	function getTempC() {
		return ($this->temperatureF - 32) * 5 / 9;
	}
	
	function setTempF($tempF) {
		$this->temperatureF = $tempF;
		$this->_notify($this, array('property'=>'tempC'));
	}	
	
	function setTempC($tempC) {	
		$this->temperatureF = $tempC*9/5 + 32;
		$this->_notify($this, array('property'=>'tempF'));
	}	
}
?>

2.4.5 Updating the TemperatureActionForm

When the $this->_notify(...) method of the model (Observable) is called, the notify($sender, $args) method of the base class Action (Observer) is called. This is used to obtain the Farenheit temperature from the model which is then stored in the TemperatureActionForm

Action.php

	...
	// Receive notifications of changes in state of model
	function notify($sender, $args) {
		// Handle property changes
		if (isset($args['property'])) {
			// Update action form property by calling property accessor in model
			$this->actionForm->{$args['property']} = call_user_func(array(&$this->model, 'get'.ucfirst($args['property'])));		
		}

		// Handle errors
		if (isset($args['error'])) {
			// Update error array by calling error accessor in model
			$this->addError($this->model->getErr($args['error']));		
		}
	}
	...

2.4.6 Calling Action Dispatcher

The last action of the front controller is to call the process($actionObj) method of the ActionDispatcher. The action dispatcher transforms data stored in the action form into values for use in the view :

Value Source
data Properties of the action form in the request
errors Error array in the request
uri URI attribute in the request (success or failure)

In our example, the processing did not generate any errors so the URI attribute has been set to the success attribute, namely ./view/CtoF.php. This page is included by the action dispatcher.

2.4.7 View CtoF

The page CtoF.php instantiates a list of widgets, all of which inherit from the base class Widget:

Widgets are provided with data from the action form via the data array provided by the action dispatcher.

CtoF.php

<?php
// ***** Includes *****
include './view/dom/DOM.inc.php';
include './view/dom/staticDOM.php';

include './view/widgets/Widget.php';
include './view/widgets/Page.php';
include './view/widgets/Body.php';
include './view/widgets/Form.php';
include './view/widgets/NavBar.php';

// ***** Widgets *****
$page =& new Page('page');
$page->setTitle('Celsius to Farenheit conversion');

$body =& new Body('body');
$page->addChild($body);

$navbar =& new NavBar('navbar');
$navbar->addListItem('Go to Farenheit to Celsius conversion', './index.php?a=ftoc');
$body->addChild($navbar);

$form =& new Form('form');
$form->setAction('./index.php?a=ctof');
$form->setInputCaption('Celsius :');
$form->setInputName('c');
$form->setInputValue($data->tempC);
$form->setOutputCaption('Farenheit :');
$form->setOutputValue($data->tempF);
$body->addChild($form);

// ***** Build *****
$page->build();
$page->dom->formatOutput = true;

// ***** Display *****
print(@ $page->fetch());	// obtaining string not object reference, so get rid of Notice error
?>

The page CtoF.php creates the following widget hierarchy :

      Page
         Body
            Nav
            Form

XHTML nodes are created by using calls to the createNode(...) method of the parent Widget class. Child nodes are appended to parent nodes to create the XHTML markup that is required to render the widget. Here is the Form widget to illustrate the principle:

Form.php

<?php
// Create a form component
class Form extends Widget {
	var $action;
	var $input_caption;
	var $input_name;
	var $input_value;
	
	function setAction($action) {
		$this->action = $action;	
	}
	
	function setInputCaption($input_caption) {
		$this->input_caption = $input_caption;	
	}
	
	function setInputName($input_name) {
		$this->input_name = $input_name;	
	}
	
	function setInputValue($input_value) {
		$this->input_value = $input_value;	
	}
	
	function setOutputCaption($output_caption) {
		$this->output_caption = $output_caption;	
	}
	
	function setOutputValue($output_value) {
		$this->output_value = $output_value;	
	}	
	
	function create() {
		$this->node =& $this->createNode('form', array(array('action', $this->action), array('method', 'post')));
		$tmp1 =& $this->createNode('table');
		$this->node->appendChild($tmp1);
		$tmp2 =& $this->createNode('tr');
		$tmp1->appendChild($tmp2);
		$tmp3 =& $this->createNode('td', null, $this->input_caption);
		$tmp2->appendChild($tmp3);
		$tmp3->appendChild($this->createNode('input', array(array('type', 'text'), array('name', $this->input_name), array('value', $this->input_value))));
		$tmp4 =& $this->createNode('tr');
		$tmp1->appendChild($tmp4);
		$tmp5 =& $this->createNode('td', null, $this->output_caption.$this->output_value);
		$tmp4->appendChild($tmp5);	
		$tmp6 =& $this->createNode('tr');
		$tmp1->appendChild($tmp6);	
		$tmp7 =& $this->createNode('td', array(array('colspan', '2')));
		$tmp6->appendChild($tmp7);
		$tmp7->appendChild($this->createNode('input', array(array('type', 'submit'), array('value', 'Submit'))));
	}
}
?>

The final instructions in the page CtoF.php include a call to the build() then the fetch() method of the Page widget. This ensures that the entire hierarchy of widgets is built and the XHTML document retrieved for display. The result is the source below :

<html>
  <head>
    <title>Celsius to Farenheit conversion</title>
  </head>
  <body>
    <div id="">
      <ul class="horiz">
        <li class="inline">
          <a href="./index.php?a=ftoc">Go to Farenheit to Celsius conversion</a>
        </li>
      </ul>
    </div>
    <form action="./index.php?a=ctof" method="post">
      <table>
        <tr>
          <td>Celsius :<input type="text" name="c" value="45"/></td>
        </tr>
        <tr>
          <td>Farenheit :113</td>
        </tr>
        <tr>
          <td colspan="2">
            <input type="submit" value="Submit"/>
          </td>
        </tr>
      </table>
    </form>
  </body>
</html>

and the screen below :

CtoF screenshot 2

Return to articles | Back to top

Mark Schofield
mark.schofield.free.fr
Last updated : 08 January 2011
Creative Commons License