4. An illustrative application
We propose to illustrate the previous method with an example of tax calculation.
4.1. The problem
We aim to write a program to calculate a taxpayer’s tax. We consider the simplified case of a taxpayer who has only their salary to report:
- We calculate the number of tax brackets for the employee: nbParts = nbEnfants / 2 + 1 if unmarried, nbEnfants / 2 + 2 if married, where nbEnfants is the number of children. The number of brackets is increased by 0.5 if there are three or more children.
- We calculate their taxable income R = 0.72 * S, where S is their annual salary
- We calculate their family coefficient Q = R/N
- We calculate his tax I based on the following data
| Each row has 3 fields: limit, coeffR, and coeffN. To calculate tax I, find the first row where QF <= limit. For example, if QF = 30000, the row found is: 31810 0.2 3309.5. The tax I is then equal to 0.2*R - 3309.5*nbParts. If QF is such that the condition QF <= limit is never satisfied, then the coefficients from the last row are used: 0 0.65 49062, which gives the tax I = 0.65*R - 49062*nbParts. |
4.2. The database
The preceding data is stored in a MySQL database named dbimpots. The user seldbimpots with password mdpseldbimpots has read-only access to the database’s contents. The database contains a single table named impots with the following structure:

Its contents are as follows:

4.3. The application's MVC architecture
The application will have the following MVC architecture:
![]() |
- The main.php controller will be the generic controller described above
- The client's request is sent to the controller in the form of a query of the form main.php?action=xx. The value of the action parameter determines which script in the ACTIONS block to execute. The executed action script returns a variable to the controller indicating the state in which the web application should be placed. Armed with this state, the controller will activate one of the view generators to send the response to the client.
- impots-data.php is the class responsible for providing the controller with the data it needs
- impots-calcul.php is the business logic class that calculates the tax
4.4. The data access class
The data access class is designed to hide the data source from the web application. Its interface includes a getData method that returns the three data arrays needed to calculate the tax. In our example, the data is retrieved from a MySQL database. To make the class independent of the actual DBMS type, we will use the pear::DB library described in the appendix. The class code is as follows:
<?php
// libraries
require_once 'DB.php';
class impots_data{
// class for accessing the DBIMPOTS data source
// attributes
var $sDSN; // connection string
var $sDatabase; // the database name
var $oDB; // database connection
var $errors; // list of errors
var $oResults; // query result
var $connected; // Boolean indicating whether or not we are connected to the database
var $sQuery; // the last query executed
// constructor
function impots_data($dDSN){
// $dDSN: dictionary defining the connection to be established
// $dDSN['dbms']: the type of DBMS to connect to
// $dDSN['host']: the name of the host machine
// $dDSN['database']: the name of the database to connect to
// $dDSN['user']: a database user
// $dDSN['mdp']: their password
// creates a connection in $oDB to the database defined by $dDSN under the identity of $dDSN['user']
// if the connection is successful
// stores the database connection string in $sDSN
// sets $sDataBase to the name of the database being connected to
// sets $connected to true
// if the connection fails
// store the appropriate error messages in the $aErrors list
// closes the connection if necessary
// sets $connected to false
// clear the error list
$this->aErrors = array();
// create a connection to the database $sDSN
$this->sDSN = $dDSN["sgbd"] . "://" . $dDSN["user"] . ":" . $dDSN["mdp"] . "@" . $dDSN["host"] . "/" . $dDSN["database"];
$this->sDatabase = $dDSN["database"];
$this->connect();
// Connected?
if( ! $this->connected) return;
// connection successful
$this->connected = TRUE;
}//constructor
// ------------------------------------------------------------------
function connect(){
// (re)connect to the database
// clear error list
$this->errors = array();
// create a connection to the database $sDSN
$this->oDB = DB::connect($this->sDSN, true);
// error?
if(DB::iserror($this->oDB)){
// Log the error
$this->errors[] = "Failed to connect to the database [".$this->sDatabase."]: [".$this->oDB->getMessage()."]";
// Connection failed
$this->connected = FALSE;
// end
return;
}
// We are connected
$this->connected = TRUE;
}//connect
// ------------------------------------------------------------------
function disconnect(){
// if connected, close the connection to the $sDSN database
if($this->connected){
$this->oDB->disconnect();
// we are disconnected
$this->connected = FALSE;
}//if
}//disconnect
// -------------------------------------------------------------------
function execute($sQuery){
// $sQuery: query to execute
// store the query
$this->sQuery = $sQuery;
// Are we connected?
if(! $this->connected){
// log the error
$this->errors[] = "No existing connection to the database [$this->sDatabase]";
// end
return;
}//if
// execute the query
$this->oResults = $this->oDB->query($sQuery);
// error?
if(DB::iserror($this->oResults)){
// log the error
$this->errors[] = "Query [$sQuery] failed: [".$this->oResults->getMessage()."]";
// return
return;
}//if
}//execute
// ------------------------------------------------------------------
function getData(){
// retrieve the 3 data sets: limites, coeffr, coeffn
$this->execute('select limits, coeffR, coeffN from taxes');
// Any errors?
if(count($this->errors) != 0) return array();
// iterate through the result of the SELECT
while ($row = $this->oResults->fetchRow(DB_FETCHMODE_ASSOC)) {
$limits[] = $row['limits'];
$coeffr[] = $row['coeffR'];
$coeffn[] = $row['coeffN'];
}//while
return array($limits, $coeffr, $coeffn);
}//getTaxData
}//class
?>
A test program could look like this:
<?php
// library
require_once "c-impots-data.php";
require_once "DB.php";
// test the impots-data class
ini_set('track_errors','on');
ini_set('display_errors','on');
// dbimpots database configuration
$dDSN = array(
"db"=>"mysql",
"user"=>"seldbimpots",
"password"=>"mdpseldbimpots",
"host"=>"localhost",
"database"=>"dbimpots"
);
// Open the session
$oImpots = new impots_data($dDSN);
// errors?
if(checkErrors($oImpots)){
exit(0);
}
// progress
echo "Connected to the database...\n";
// Retrieve limit data, coeffr, coeffn
list($limits, $coeffr, $coeffn) = $oImpots->getData();
// errors?
if( ! checkErrors($oImpots)){
// content
echo "data: \n";
for($i=0;$i<count($limits);$i++){
echo "[$limits[$i],$coeffr[$i],$coeffn[$i]]\n";
}//for
}//if
// log out
$oImpots->disconnect();
// followed by
echo "Disconnected from the database...\n";
// end
exit(0);
// ----------------------------------
function checkErrors(&$oTaxes){
// Any errors?
if(count($oTaxes->errors) != 0){
// display
for($i=0;$i<count($oImpots->aErreurs);$i++){
echo $oTaxes->errors[$i]."\n";
}//for
// errors
return true;
}//if
// no errors
return false;
}//checkErrors
?>
Running this test program yields the following results:
Connected to the database...
data:
[12620,0,0]
[13190,0.05,631]
[15640,0.1,1290.5]
[24740,0.15,2072.5]
[31810,0.2,3309.5]
[39970,0.25,4900]
[48360,0.3,6898]
[55790,0.35,9316.5]
[92970,0.4,12106]
[127860,0.45,16754]
[151250,0.5,23147.5]
[172040,0.55,30710]
[195000,0.6,39312]
[0,0.65,49062]
Disconnected from the database...
4.5. The tax calculation class
This class is used to calculate a taxpayer's tax. The data required for this calculation is provided to its constructor. It then calculates the corresponding tax. The class code is as follows:
<?php
class tax_calculation{
// tax calculation class
// constructor
function tax_calculation(&$user, &$data){
// $perso: dictionary with the following keys
// children: number of children
// salary: annual salary
// married: Boolean indicating whether the taxpayer is married or not
// tax(es): tax(es) due calculated by this constructor
// $data: dictionary with the following keys
// limits: array of tax bracket limits
// coeffr: array of income coefficients
// coeffn: array of coefficients for the number of shares
// the 3 arrays have the same number of elements
// calculation of the number of shares
if($perso['married'])
$nbParts = $perso['children'] / 2 + 2;
else $nbParts=$perso['children']/2+1;
if ($perso['children'] >= 3) $nbParts += 0.5;
// taxable income
$income = 0.72 * $person['salary'];
// family quotient
$QF = $income / $nbParts;
// find the tax bracket corresponding to QF
$nbTranches = count($data['limites']);
$i=0;
while($i<$numberOfBrackets-2 && $QF>$data['limits'][$i]) $i++;
// tax
$perso['tax'] = floor($data['coeffr'][$i] * $income - $data['coeffn'][$i] * $nbParts);
}//constructor
}//class
?>
A test program could look like this:
<?php
// library
require_once "c-tax-data.php";
require_once "c-tax-calculation.php";
// dbimpots database configuration
$dDSN=array(
"db"=>"mysql",
"user"=>"seldbimpots",
"password"=>"mdpseldbimpots",
"host"=>"localhost",
"database"=>"dbimpots"
);
// Open the session
$oImpots = new impots_data($dDSN);
// errors?
if(checkErrors($oImpots)){
exit(0);
}
// progress
echo "Connected to the database...\n";
// Retrieve limit data, coeffr, coeffn
list($limits, $coeffr, $coeffn) = $oImpots->getData();
// errors?
if(checkErrors($oTaxes)){
exit(0);
}
// disconnect
$oImpots->disconnect();
// Log
echo "Disconnected from the database...\n";
// calculate tax
$dData=array('limits'=>&$limits,'coeffr'=>&$coeffr,'coeffn'=>&$coeffn);
$dPerso=array('children'=>2,'salary'=>200000,'married'=>true,'tax'=>0);
new impots_calcul($dPerso, $dData);
dump($dPerso);
$dPerso = array('children' => 3, 'salary' => 200000, 'married' => false, 'tax' => 0);
new tax_calculation($dPerso, $dData);
dump($dPerso);
$dPerso = array('children' => 3, 'salary' => 20000, 'married' => true, 'tax' => 0);
new tax_calculation($person, $data);
dump($dPerso);
$dPerso = array('children' => 3, 'salary' => 2000000, 'married' => true, 'tax' => 0);
new tax_calculation($dPerso, $dData);
dump($dPerso);
// end
exit(0);
// ----------------------------------
function checkErrors(&$oTaxes){
// any errors?
if(count($oTaxes->errors) != 0){
// display
for($i=0;$i<count($oImpots->aErreurs);$i++){
echo $oTaxes->errors[$i]."\n";
}//for
// errors
return true;
}//if
// no errors
return false;
}//checkErrors
?>
Running this test program yields the following results:
Connected to the database...
Disconnected from the database...
[children,2] [salary,200000] [married,1] [tax,22506]
[children,3] [salary,200000] [married,] [tax,22506]
[children,3] [salary,20000] [married,1] [tax,0]
[children,3] [salary,2000000] [married,1] [tax,706752]
4.6. How the application works
When the web-based tax calculator is launched, the following [v-form] view appears:
![]() |
The user fills in the fields and requests the tax calculation:
![]() |
Note that the form is regenerated in the state in which the user submitted it and also displays the amount of tax due. The user may make data entry errors. These are flagged by an error page, which we will call the [v-errors] view.
![]() |
![]() |
The [Return to input form] link allows the user to return to the form as they submitted it.
![]() |
Finally, the [Clear Form] button resets the form to its initial state, i.e., as the user received it during the initial request.
4.7. Back to the application’s MVC architecture
The application has the following MVC architecture:
![]() |
We have just described the two classes impots-data.php and impots-calcul.php. We will now describe the other elements of the architecture.
4.8. The application controller
The application's main.php controller is the one described in the first part of this chapter. It is a generic controller independent of the application.
<?php
// generic controller
// read config
include 'config.php';
// include libraries
for($i=0;$i<count($dConfig['includes']);$i++){
include($dConfig['includes'][$i]);
}//for
// Start or resume the session
session_start();
$dSession = $_SESSION["session"];
if($dSession) $dSession=unserialize($dSession);
// retrieve the action to be performed
$sAction = $_GET['action'] ? strtolower($_GET['action']) : 'init';
$sAction = strtolower($_SERVER['REQUEST_METHOD']) . ":" . $sAction;
// Is the sequence of actions normal?
if( ! enchainementOK($dConfig,$dSession,$sAction)){
// invalid sequence
$sAction = 'invalidSequence';
}//if
// Process the action
$scriptAction = $dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['invalidAction']['url'];
include $scriptAction;
// send the response (view) to the client
$sState = $dSession['state']['main'];
$scriptView = $dConfig['states'][$sState]['view'];
include $scriptView;
// end of script - we shouldn't get here unless there's a bug
trace ("Configuration error.");
trace("Action=[$sAction]");
trace("scriptAction=[$scriptAction]");
trace("Status=[$sStatus]");
trace("scriptView=[$scriptView]");
trace ("Verify that the scripts exist and that the [$scriptVue] script ends with a call to endSession.");
exit(0);
// ---------------------------------------------------------------
function endSession(&$dConfig,&$dResponse,&$dSession){
// $dConfig: configuration dictionary
// $dSession: dictionary containing session information
// $dResponse: dictionary of response page arguments
// session registration
if(isset($dSession)){
// store the request parameters in the session
$dSession['request'] = strtolower($_SERVER['REQUEST_METHOD']) == 'get' ? $_GET :
strtolower($_SERVER['REQUEST_METHOD']) == 'post' ? $_POST : array();
$_SESSION['session'] = serialize($dSession);
session_write_close();
}else{
// no session
session_destroy();
}
// display the response
include $dConfig['responseViews'][$dResponse['responseView']]['url'];
// end of script
exit(0);
}//end session
//--------------------------------------------------------------------
function chainOK(&$dConfig,&$dSession,$sAction){
// checks if the current action is allowed based on the previous state
$state = $dSession['state']['main'];
if(! isset($state)) $state='noState';
// action check
$allowedActions = $dConfig['states'][$state]['allowedActions'];
$authorized = ! isset($allowedActions) || in_array($sAction, $allowedActions);
return $authorized;
}
//--------------------------------------------------------------------
function dump($dInfo){
// displays a dictionary of information
while(list($key, $value) = each($dInfo)){
echo "[$key,$value]<br>\n";
}//while
}//end
//--------------------------------------------------------------------
function trace($msg){
echo $msg."<br>\n";
}//tracking
?>
4.9. Web application actions
There are four actions:
- get:init: This is the action triggered during the initial request to the controller without parameters. It renders the empty 'form' view.
- post:clearform: action triggered by the [Clear Form] button. It renders the empty [v-form] view.
- post:calculateTax: action triggered by the [Calculate Tax] button. It generates either the [v-form] view with the amount of tax due, or the [v-errors] view.
- get:returnform: action triggered by the [Return to input form] link. It renders the [v-form] view pre-filled with the incorrect data.
These actions are configured as follows in the configuration file:
<?php
…
// configuration of application actions
$dConfig['actions']['get:init']=array('url'=>'a-init.php');
$dConfig['actions']['post:calculateTax']=array('url'=>'a-calculateTax.php');
$dConfig['actions']['get:returnForm']=array('url'=>'a-returnForm.php');
$dConfig['actions']['post:clearform']=array('url'=>'a-init.php');
$dConfig['actions']['invalidSequence']=array('url'=>'a-invalidSequence.php');
$dConfig['actions']['invalidAction'] = array('url' => 'a-invalidAction.php');
Each action is associated with the script responsible for processing it. Each action will take the web application to a state defined by the $dSession['state']['main'] element. This state is intended to be saved in the session. Additionally, the action stores in the $dResponse dictionary the information needed to display the view associated with the new state the application will be in.
4.10. Web Application States
There are two:
- [e-form]: state in which the different variants of the [v-form] view are displayed.
- [e-errors]: state in which the [v-errors] view is displayed.
The actions allowed in these states are as follows:
<?php
…
// configuration of application states
$dConfig['states']['form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-form.php');
$dConfig['statuses']['errors'] = array(
'allowedActions'=>array('get:submitForm','get:init'),
'view'=>'e-errors.php');
$dConfig['states']['no-state'] = array('allowedActions' => array('get:init'));
In a state, the allowed actions correspond to the target URLs of the links or [submit] buttons in the view associated with that state. Additionally, the 'get:init' action is always allowed. This allows the user to retrieve the main.php URL from their browser's URL bar and reload it regardless of the application's state. This is a kind of 'manual' reset. The 'sansetat' state exists only when the application starts.
Each application state is associated with a script responsible for generating the view associated with that state:
- state [e-form]: script e-formulaire.php
- state [e-errors]: script e-errors.php
The [e-form] state will display the [v-form] view with variations. In fact, the [v-form] view can be displayed empty, pre-filled, or with the tax amount. The action that brings the application into the [e-form] state specifies the application’s main state in the $dSession['state']['main'] variable. The controller uses only this information. In our application, the action that leads to the [e-form] state will add additional information to $dSession['state']['secondary'], allowing the response generator to know whether to generate an empty form, a pre-filled form, or a form with or without the tax amount. We could have taken a different approach by assuming there were three different states and therefore three view generators to write.
4.11. The config.php configuration file for the web application
<?php
// PHP configuration
ini_set("register_globals","off");
ini_set("display_errors", "off");
ini_set("expose_php", "off");
// list of modules to include
$dConfig['includes'] = array('c-impots-data.php', 'c-impots-calcul.php');
// application controller
$dConfig['webapp'] = array('title' => "Calculate Your Tax");
// application view configuration
$dConfig['responseViews']['template1'] = array('url' => 'm-response.php');
$dConfig['responseViews']['template2'] = array('url' => 'm-response2.php');
$dConfig['views']['form'] = array('url' => 'v-form.php');
$dConfig['views']['errors'] = array('url' => 'v-errors.php');
$dConfig['views']['form2'] = array('url' => 'v-form2.php');
$dConfig['views']['errors2'] = array('url' => 'v-errors2.php');
$dConfig['views']['banner'] = array('url' => 'v-banner.php');
$dConfig['views']['menu'] = array('url' => 'v-menu.php');
$dConfig['style']['url'] = 'style1.css';
// configuration of application actions
$dConfig['actions']['get:init']=array('url'=>'a-init.php');
$dConfig['actions']['post:calculateTax'] = array('url' => 'a-calculateTax.php');
$dConfig['actions']['get:returnForm']=array('url'=>'a-returnForm.php');
$dConfig['actions']['post:clearform']=array('url'=>'a-init.php');
$dConfig['actions']['invalidSequence']=array('url'=>'a-invalidSequence.php');
$dConfig['actions']['invalidAction']=array('url'=>'a-invalidAction.php');
// configuration of application states
$dConfig['states']['e-form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-form.php');
$dConfig['states']['e-errors'] = array(
'allowedActions'=>array('get:returnForm','get:init'),
'view'=>'e-errors.php');
$dConfig['statuses']['no-status']=array('allowed-actions'=>array('get:init'));
// application template configuration
$dConfig["DSN"]=array(
"db"=>"mysql",
"user"=>"seldbimpots",
"password"=>"mdpseldbimpots",
"host"=>"localhost",
"database"=>"dbimpots"
);
?>
4.12. Web application actions
4.12.1. General operation of action scripts
- An action script is called by the controller based on the action parameter it received from the client.
- After execution, the action script must tell the controller the state in which to place the application. This state must be specified in $dSession['etat']['principal'].
- An action script may want to store information in the session. It does so by placing this information in the $dSession dictionary, which is automatically saved to the session by the controller at the end of the request-response cycle.
- An action script may have information to pass to views. This aspect is independent of the controller. It is the interface between actions and views, an interface specific to each application. In the example discussed here, actions will provide information to view generators via a dictionary called $dResponse.
4.12.2. The get:init action
This is the action that generates the empty form. The configuration file shows that it will be handled by the a-init.php script:
The code for the a-init.php script is as follows:
<?php
// display the input form
$dSession['status']=array('primary'=>'e-form', 'secondary'=>'init');
?>
This script simply sets the state in which the application should be—the [e-form] state—in $dSession['etat']['principal'], and provides a description of this state in $dSession['etat']['secondaire']. The configuration file shows us that the controller will execute the e-formulaire.php script to generate the response to the client.
<?php
…
$dConfig['states']['e-form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-formulaire.php');
The e-formulaire.php script will generate the [v-formulaire] view in its [init] variant, i.e., the empty form.
4.12.3. The post:calculateTax action
This is the action that calculates the tax based on the data entered in the form. The configuration file specifies that the a-calculimpot.php script will handle this action:
The code for the a-calculimpot.php script is as follows:
<?php
// tax calculation request
// first, we check the validity of the parameters
$sOptMarie = $_POST['optmarie'];
if($sOptMarie!='yes' && $sOptMarie!='no'){
$errors[] = "The marital status [$sOptMarie] is incorrect";
}
$sChildren = trim($_POST['txtenfants']);
if(! preg_match('/^\d{1,3}$/',$sChildren)){
$errors[] = "The number of children [$sChildren] is incorrect";
}
$sSalary = trim($_POST['txtsalary']);
if(! preg_match('/^\d+$/',$sSalary)){
$errors[] = "The annual salary [$sSalary] is incorrect";
}
// if there are errors, the process is over
if(count($errors)!=0){
// prepare the error page
$dResponse['errors'] = &$errors;
$dSession['status'] = array('main' => 'error', 'secondary' => 'input');
return;
}//if
// the entered data is correct
// retrieve the data needed to calculate the tax
if(! $dSession['limits']){
// the data is not in the session
// retrieve it from the data source
list($errors, $limits, $coeffr, $coeffn) = getData($dConfig['DSN']);
// if there are errors, display the error page
if(count($errors) != 0){
// prepare the error page
$dResponse['errors'] = &$errors;
$dSession['status'] = array('primary' => 'errors', 'secondary' => 'database');
return;
}//if
// no errors - data is placed in the session
$dSession['limits']=&$limits;
$dSession['coeffr']=&$coeffr;
$dSession['coeffn']=&$coeffn;
}//if
// here we have the data needed to calculate the tax
// we calculate the tax
$dData = array('limits' => &$dSession['limits'],
'coeffr'=>&$dSession['coeffr'],
'coeffn'=>&$dSession['coeffn']);
$dPerso=array('children'=>$sChildren,'salary'=>$sSalary,'married'=>($sOptMarried=='yes'),'tax'=>0);
new impots_calcul($dPerso,$dData);
// preparing the response page
$dSession['status']=array('main'=>'e-form','secondary'=>'taxCalc');
$dResponse['tax'] = $dPerson['tax'];
return;
//-----------------------------------------------------------------------
function getData($dDSN){
// Connect to the data source defined by the $dDSN dictionary
$oTaxes = new Taxes_Data($dDSN);
if(count($oImpots->aErreurs)!=0) return array($oImpots->aErreurs);
// retrieve limit data, coeffr, coeffn
list($limits, $coeffr, $coeffn) = $oImpots->getData();
// disconnect
$oTaxes->disconnect();
// return the result
if(count($oImpots->aErreurs)!=0) return array($oImpots->aErreurs);
else return array(array(), $limits, $coeffr, $coeffn);
}//getData
The script does what it's supposed to do: calculate the tax. We'll leave it to the reader to decipher the processing code. We're interested in the states that may arise as a result of this action:
- the entered data is incorrect or there is an issue accessing the data: the application is set to the [e-errors] state. The configuration file shows that the e-errors.php script will be responsible for generating the response view:
<?php
…
$dConfig['states']['e-errors']=array(
'allowedActions'=>array('get:formSubmit','get:init'),
'view'=>'e-errors.php');
- In all other cases, the application is set to the [e-form] state with the tax calculation variant specified in $dSession['state']['secondary']. The configuration file shows that the e-formulaire.php script will generate the response view. It will use the value of $dSession['state']['secondary'] to generate a pre-filled form with the values entered by the user and the tax amount.
4.12.4. The post:effacerformulaire action
It is associated by configuration with the a-init.php script already described.
4.12.5. The get:return-form action
It allows you to return to the [e-form] state from the [e-errors] state. The a-retourformulaire.php script handles this action:
The a-retourformulaire.php script is as follows:
<?php
// display the input form
$dSession['state']=array('main'=>'e-form','secondary'=>'returnform');
?>
We simply ask that the application be set to the [e-form] state in its [form-return] variant. The configuration file shows us that the controller will execute the e-form.php script to generate the response to the client.
<?php
…
$dConfig['states']['e-form'] = array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-formulaire.php');
The e-formulaire.php script will generate the [v-formulaire] view in its [retourformulaire] variant, i.e., the form pre-filled with the values entered by the user but without the tax amount.
4.13. Invalid action sequence
The valid actions from a given state of the application are set by configuration:
<?php
…
// application state configuration
$dConfig['states']['e-form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-form.php');
$dConfig['states']['e-errors'] = array(
'allowedActions'=>array('get:returnForm','get:init'),
'view'=>'e-errors.php');
$dConfig['states']['no-state']=array('allowed-actions'=>array('get:init'));
We have already explained this configuration. If an invalid sequence of actions is detected, the script a-invalid-sequence.php is executed:
The code for this script is as follows:
<?php
// Invalid sequence of actions
$dResponse['errors'] = array("Invalid action sequence");
$dSession['status'] = array('primary' => 'e-errors', 'secondary' => 'invalid-sequence');
?>
This sets the application to the [e-errors] state. We provide information in $dSession['state']['secondary'] that will be used by the error page generator. As we have already seen, this generator is e-errors.php:
<?php
…
$dConfig['states']['e-errors']=array(
'allowedActions'=>array('get:formSubmit','get:init'),
'view'=>'e-errors.php');
We will look at the code for this generator later. The view sent to the client is as follows:

4.14. The application's views
4.14.1. Displaying the final view
Let's look at how the controller sends the response to the client once the action requested by the client has been executed:
<?php
…
....
// start or resume the session
session_start();
$dSession = $_SESSION["session"];
if($dSession) $dSession=unserialize($dSession);
// retrieve the action to be performed
$sAction = $_GET['action'] ? strtolower($_GET['action']) : 'init';
$sAction = strtolower($_SERVER['REQUEST_METHOD']) . ":" . $sAction;
// Is the sequence of actions normal?
if( ! enchainementOK($dConfig,$dSession,$sAction)){
// invalid sequence
$sAction = 'invalidSequence';
}//if
// Process the action
$scriptAction = $dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['invalidAction']['url'];
include $scriptAction;
// send the response (view) to the client
$sState = $dSession['state']['main'];
$scriptView = $dConfig['states'][$sState]['view'];
include $scriptView;
.....
// ---------------------------------------------------------------
function endSession(&$dConfig,&$dResponse,&$dSession){
// $dConfig: configuration dictionary
// $dSession: dictionary containing session information
// $dResponse: dictionary of response page arguments
// session storage
...
// sending the response to the client
include $dConfig['responseViews'][$dResponse['responseView']]['url'];
// end of script
exit(0);
}//end session
Upon returning from an action script, the controller retrieves the state in which it must set the application from $dSession['etat']['principal']. This state was set by the action that was just executed. The controller then executes the view generator associated with that state. It finds the name of the view generator in the configuration file. The role of the view generator is as follows:
- Sets the name of the response template to be used in $dResponse['responseView']. This information will be passed to the controller. A template is a composition of basic views that, when combined, form the final view.
- Prepares the dynamic information to be displayed in the final view. This step is independent of the controller. It serves as the interface between the view generator and the final view. It is specific to each application.
- must end with a call to the controller’s finSession function. This function will
- save the session
- send the response
The code for the finSession function is as follows:
<?php
…
// ---------------------------------------------------------------
function endSession(&$dConfig,&$dResponse,&$dSession){
// $dConfig: configuration dictionary
// $dSession: dictionary containing session information
// $dResponse: dictionary of response page arguments
// session storage
...
// sending the response to the client
include $dConfig['responseViews'][$dResponse['responseView']]['url'];
// end of script
exit(0);
}//end session
The view sent to the user is defined by the $dResponse['responseView'] entity, which defines the template to use for the final response.
4.14.2. Response template
The application will generate its various responses based on the following single template:
![]() |
This template is associated with the 'modele1' key in the $dConfig['vuesreponse'] dictionary:
The m-reponse.php script is responsible for generating this template:
<html>
<head>
<title>Tax Application</title>
<link type="text/css" href="<?php echo $dReponse['urlstyle'] ?>" rel="stylesheet" />
</head>
<body>
<?php
include $dReponse['vue1'];
?>
<hr>
<?php
include $dResponse['view2'];
?>
</body>
</html>
This script has three dynamic elements stored in a dictionary $dReponse and associated with the following keys:
- urlstyle: URL of the template's stylesheet
- view1: name of the script responsible for generating the view1 view
- vue2: name of the script responsible for generating the vue2 view
A view generator wishing to use the modèle1 template must define these three dynamic elements. We now define the basic views that can replace the [vue1] and [vue2] elements in the template.
4.14.3. The basic view v-bandeau.php
The v-bandeau.php script generates a view that will be placed in the [vue1] area:
<table>
<tr>
<td><img src="univ01.gif"></td>
<td>
<table>
<tr>
<td><div class='title'><?php echo $dResponse['title'] ?></div></td>
</tr>
<tr>
<td><div class='result'><?php echo $dResponse['result']?></div></td>
</tr>
</table>
</td>
</tr>
</table>
The view generator must define two dynamic elements placed in a dictionary $dReponse and associated with the following keys:
- title: title to display
- result: amount of tax due
4.14.4. The basic view v-form.php
The [vue2] section corresponds to either the [v-form] view or the [v-errors] view. The [v-form] view is generated by the v-formulaire.php script:
<form method="post" action="main.php?action=calculerimpot">
<table>
<tr>
<td class="label">Are you married?</td>
<td class="value">
<input type="radio" name="optmarie" <?php echo $dReponse['optoui'] ?> value="yes">yes
<input type="radio" name="optmarie" <?php echo $dReponse['optnon'] ?> value="no">no
<td>
<tr>
<td class="label">Number of children</td>
<td class="value">
<input type="text" class="text" name="txtenfants" size="3" value="<?php echo $dReponse['enfants'] ?>"
</td>
</tr>
<tr>
<td class="label">Annual salary</td>
<td class="value">
<input type="text" class="text" name="txtsalaire" size="10" value="<?php echo $dReponse['salaire'] ?>"
</td>
</tr>
</table>
<hr>
<input type="submit" class="submit" value="Calculate Tax">
</form>
<form method="post" action="main.php?action=clearform">
<input type="submit" class="submit" value="Clear form">
</form>
The dynamic parts of this view that must be defined by the view generator are associated with the following keys in the $dReponse dictionary:
- optoui: state of the radio button named optoui
- optnon: state of the radio button named optnon
- children: number of children to be placed in the txtenfants field
- salary: annual salary to be placed in the txtsalary field
4.14.5. The basic view v-erreurs.php
The [v-errors] view is generated by the v-errors.php script:
The following errors occurred:
<ul>
<?php
for($i=0;$i<count($dResponse["errors"]);$i++){
echo "<li class='error'>".$dResponse["errors"][$i]."</li>\n";
}//for
?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<br>
<a href="<?php echo $dResponse["href"] ?>"><?php echo $dResponse["link"] ?></a>
The dynamic parts of this view to be defined by the view generator are associated with the following keys in the $dReponse dictionary:
- errors: array of error messages
- info: information message
- link: text of a link
- href: target URL of the link above
4.14.6. The Style Sheet
All views are styled by a stylesheet. To change the visual appearance of the application, you modify its stylesheet. The following style1.css stylesheet:
div.menu {
background-color: #FFD700;
color: #F08080;
font-weight: bolder;
text-align: center;
}
td.separator {
background: #FFDAB9;
width: 20px;
}
table.model2 {
width: 600px;
}
BODY {
background-image: url(standard.jpg);
margin-left: 0px;
margin-top: 6px;
color: #4A1919;
font-size: 10pt;
font-family: Arial, Helvetica, sans-serif;
scrollbar-face-color: #F2BE7A;
scrollbar-arrow-color: #4A1919;
scrollbar-track-color: #FFF1CC;
scrollbar-3dlight-color: #CBB673;
scrollbar-darkshadow-color: #CBB673;
}
div.title {
font: 30pt Garamond;
color: #FF8C00;
background-color: Yellow;
}
table.menu {
background-color: #ADD8E6;
}
A:hover {
text-decoration: underline;
color: #FF0000;
background-color: transparent;
}
A:ACTIVE {
text-decoration: underline;
color: #BF4141;
background-color: transparent;
}
A:VISITED {
color: #BF4141;
background-color: transparent;
}
.error {
color: red;
font-weight: bold;
}
INPUT.text {
margin-left: 3px;
font-size: 8pt;
font-weight: bold;
color: #4A1919;
background-color: #FFF6E0;
border-right: 1px solid;
border-left: 1px solid;
border-top: 1px solid;
border-bottom: 1px solid;
}
td.label {
background-color: #F0FFFF;
color: #0000CD;
}
td.value {
background-color: #DDA0DD;
}
DIV.result {
background-color: #FFA07A;
font: bold 12pt;
}
div.info {
color: #FA8072;
}
li.error {
color: #DC143C;
}
INPUT.submit {
margin-left: 6px;
font-size: 8pt;
font-weight: bold;
color: #4A1919;
background-color: #FFF1CC;
border-right: 1px solid;
border-left: 1px solid;
border-top: 1px solid;
border-bottom: 1px solid;
}
4.15. View generators
4.15.1. Role of a view generator
Let’s review the controller code snippet that executes a view generator:
<?php
…
// processing the action
$scriptAction = $dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url']:
$dConfig['actions']['invalidAction']['url'];
include $scriptAction;
// send the response (view) to the client
$sState = $dSession['state']['main'];
$scriptView = $dConfig['states'][$sState]['view'];
include $viewScript;
A view generator is linked to the state in which the application will be placed. The link between the state and the view generator is set via configuration:
<?php
…
// configuration of application states
$dConfig['states']['e-form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-form.php');
$dConfig['states']['e-errors'] = array(
'allowedActions'=>array('get:returnForm','get:init'),
'view'=>'e-errors.php');
$dConfig['states']['no-state']=array('allowed-actions'=>array('get:init'));
We have already explained the role of a view generator. Let’s review it here. A view generator:
- sets the name of the response template to be used in $dResponse['responseView']. This information will be passed to the controller. A template is a composition of basic views that, when combined, form the final view.
- prepares the dynamic information to be displayed in the final view. This step is independent of the controller. It serves as the interface between the view generator and the final view. It is specific to each application.
- must end with a call to the controller's finSession function. This function will
- save the session
- send the response
4.15.2. The view generator associated with the [e-form] state
The script responsible for generating the view associated with the [e-form] state is called e-form.php:
<?php
…
$dConfig['reports']['e-form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-formulaire.php');
Its code is as follows:
<?php
// prepare the form response
$dResponse['title'] = $dConfig['webapp']['title'];
$dResponse['responseView'] = 'template1';
$dResponse['view1'] = $dConfig['views']['banner']['url'];
$response['view2'] = $config['views']['form']['url'];
$dResponse['urlstyle'] = $dConfig['style']['url'];
$dResponse['title'] = $dConfig['webapp']['title'];
// configuration based on the type of form to generate
$type = $dSession['status']['secondary'];
if($type == 'init') {
// empty form
$dResponse['optnon'] = 'checked';
}//if
if($type=='taxcalculation'){
// we need to re-display the input parameters stored in the request
$dResponse['optyes'] = $_POST['optmarry'] == 'yes' ? 'checked' : '';
$dResponse['optno'] = $dResponse['optyes'] ? '' : 'checked';
$dResponse['children'] = $_POST['childrenCount'];
$response['salary'] = $_POST['salary'];
$dResponse['result'] = 'Tax due: ' . $dResponse['tax'] . ' F';
}//if
if($type=='formSubmit'){
// we need to re-display the input parameters stored in the session
$dResponse['optyes'] = $_SESSION['request']['optmarry'] == 'yes' ? 'checked' : '';
$dResponse['optno'] = $dResponse['optyes'] == '' ? 'checked' : '';
$dResponse['children'] = $dSession['request']['childrenCount'];
$dResponse['salary'] = $dSession['request']['salaryText'];
}//if
// send the response
finSession($dConfig, $dResponse, $dSession);
?>
Note that the view generator complies with the requirements for a view generator:
- set the response template to use in $dReponse['vuereponse']
- pass information to this template. Here, it is passed via the $dResponse dictionary.
- end by calling the controller's finSession function
Here, the template used is 'template1'. Therefore, the view generator defines the two pieces of information required by this template: $dResponse['view1'] and $dResponse['view2'].
In the specific case of our application, the view associated with the [e-form] state depends on information stored in the variable $dSession['etat']['secondaire']. This is a design choice. Another application might choose to pass additional information in a different way. Furthermore, here all the information needed to display the final view is placed in the $dReponse dictionary. Again, this is a choice left to the developer. The [e-form] state can occur after four different actions: init, calculateTax, returnForm, clearForm. The view to be displayed is not exactly the same in all cases. Therefore, we have distinguished three cases here in $dSession['state']['secondary']:
- init: the form is displayed empty
- calculateTax: the form is displayed with the tax amount and the data used to calculate it
- returnform: the form is displayed with the data initially entered
Above, the e-formulaire.php script uses this information to present the response according to these three variants.
4.15.3. The view associated with the [e-errors] status
The script responsible for generating the view associated with the [e-errors] status is called e-errors.php and is as follows:
<?php
// prepare the error response
$dResponse['title'] = $dConfig['webapp']['title'];
$dResponse['responseView'] = 'template1';
$dResponse['view1'] = $dConfig['views']['header']['url'];
$response['view2'] = $config['views']['errors']['url'];
$response['urlstyle'] = $config['style']['url'];
$dResponse['title'] = $dConfig['webapp']['title'];
$response['link'] = 'Back to the input form';
$dResponse['href'] = 'main.php?action=returnform';
// additional information
$type = $dSession['status']['secondary'];
if($type=='database'){
$dResponse['info'] = "Please notify the application administrator";
}
// send the response
finSession($dConfig, $dResponse, $dSession);
?>
4.15.4. Displaying the final view
Both scripts generating the two final views end with a call to the controller's finSession function:
<?php
…
function finSession(&$dConfig,&$dResponse,&$dSession){
// $dConfig: configuration dictionary
// $dSession: dictionary containing session information
// $dResponse: the dictionary of response page arguments
....
// display the response
include $dConfig['responseViews'][$dResponse['responseView']]['url'];
// end of script
exit(0);
}//end session
The view sent to the user is defined by the $dResponse['responseView'] entity, which specifies the template to use for the final response. For the [e-form] and [e-errors] states, this template has been set to template1:
This template corresponds to the m-reponse.php script:
4.16. Modifying the response template
Here, we assume that we decide to change the visual appearance of the response sent to the client, and we are interested in understanding the implications this has on the code.
4.16.1. The new template
The structure of the response will now be as follows:
![]() |
This template will be called template2, and the script responsible for generating this template will be called m-response2.php:
The script corresponding to this template is as follows:
<html>
<head>
<title>Tax Application</title>
<link type="text/css" href="<?php echo $dResponse['urlstyle'] ?>" rel="stylesheet" />
</head>
<body>
<table class='modele2'>
<!-- banner start -->
<tr>
<td colspan="2"><?php include $dReponse['vue1']; ?></td>
</tr>
<!-- end banner -->
<tr>
<!-- start menu -->
<td><?php include $dReponse['view2']; ?></td>
<!-- end menu -->
<!-- start zone 3 -->
<td><?php include $dReponse['view3']; ?></td>
<!-- end of section 3 -->
</tr>
</table>
</body>
</html>
The dynamic elements of the template are as follows:
- $dReponse['urlstyle']: the style sheet to use
- $dReponse['vue1']: the script to use to generate [vue1]
- $dReponse['vue2']: the script to use to generate [vue2]
- $dReponse['vue3']: the script to use to generate [vue3]
These elements must be set by the view generators.
4.16.2. The different response pages
The application will now present the following responses to the user. Upon the initial call, the response page will be as follows:
![]() |
If the user provides valid data, the tax is calculated:
![]() |
If they make input errors, the error page is displayed:
![]() |
If they use the [Return to input form] link, they will see the form as they last submitted it:
![]() |
If, in the above scenario, they use the [Reset form] link, they will see an empty form:
![]() |
Note that the application uses the same actions as before. Only the appearance of the responses has changed.
4.16.3. Basic views
The basic view [view1] will be associated with the v-bandeau.php script, as in the previous example:
<table>
<tr>
<td><img src="univ01.gif"></td>
<td>
<table>
<tr>
<td><div class='title'><?php echo $dResponse['title'] ?></div></td>
</tr>
<tr>
<td><div class='result'><?php echo $dResponse['result']?></div></td>
</tr>
</table>
</td>
</tr>
</table>
This view has two dynamic elements:
- $dResponse['title']: title to display
- $dReponse['resultat']: amount of tax due
The basic view [vue2] will be associated with the following v-menu.php script:
<table class="menu">
<tr>
<td><div class="menu">Options</div></td>
</tr>
<?php
for($i=0;$i<count($dReponse['links']);$i++){
echo '<tr><td><div class="option"><a href="'.
$dResponse['links'][$i]['url'].
'">'.$dReponse['links'][$i]['text']."</a></div></td></tr>\n";
}//$i
?>
</table>
This view has the following dynamic elements:
- $dResponse['links']: array of links to display in [view2]. Each element of the array is a dictionary with two keys:
- 'url': the link's target URL
- 'text': link text
The basic view [view3] will be associated with the v-form2.php script if you want to display the input form, or with the v-errors2.php script if you want to display the error page. The code for the v-form2.php script is as follows:
<form method="post" action="main.php?action=calculerimpot">
<table>
<tr>
<td class="label">Are you married?</td>
<td class="value">
<input type="radio" name="optmarie" <?php echo $dReponse['optoui'] ?> value="yes">yes
<input type="radio" name="optmarie" <?php echo $dReponse['optnon'] ?> value="no">no
</td>
</tr>
<tr>
<td class="libelle">Number of children</td>
<td class="value">
<input type="text" class="text" name="txtenfants" size="3" value="<?php echo $dReponse['enfants'] ?>"
</td>
</tr>
<tr>
<td class="label">Annual salary</td>
<td class="value">
<input type="text" class="text" name="txtsalaire" size="10" value="<?php echo $dReponse['salaire'] ?>"
</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" class="submit" value="Calculate Tax"></td>
</tr>
</table>
</form>
The dynamic parts of this view that must be defined by the view generator are associated with the following keys in the $dReponse dictionary:
- optoui: state of the radio button named optoui
- optnon: state of the radio button named optnon
- children: number of children to be placed in the txtenfants field
- salaire: annual salary to be placed in the txtsalaire field
The script that generates the error page is called v-erreurs2.php. Its code is as follows:
The following errors occurred:
<ul>
<?php
for($i=0;$i<count($dResponse["errors"]);$i++){
echo "<li class='error'>".$dResponse["errors"][$i]."</li>\n";
}//for
?>
</ul>
<div class="info"><?php echo $dResponse["info"] ?></div>
The dynamic parts of this view to be defined by the view generator are associated with the following keys in the $dReponse dictionary:
- errors: array of error messages
- info: information message
4.16.4. The style sheet
It hasn't changed. It's still style1.css.
4.16.5. The new configuration file
To implement these new views, we need to modify certain lines in the configuration file.
<?php
// PHP configuration
ini_set("register_globals","off");
ini_set("display_errors", "off");
ini_set("expose_php", "off");
// list of modules to include
$dConfig['includes'] = array('c-impots-data.php', 'c-impots-calcul.php');
// application controller
$dConfig['webapp'] = array('title' => "Calculate your tax");
// application view configuration
$dConfig['responseViews']['template1'] = array('url' => 'm-response.php');
$dConfig['responseViews']['template2'] = array('url' => 'm-response2.php');
$dConfig['views']['form'] = array('url' => 'v-form.php');
$dConfig['views']['errors'] = array('url' => 'v-errors.php');
$dConfig['views']['form2'] = array('url' => 'v-form2.php');
$dConfig['views']['errors2'] = array('url' => 'v-errors2.php');
$dConfig['views']['banner'] = array('url' => 'v-banner.php');
$dConfig['views']['menu'] = array('url' => 'v-menu.php');
$dConfig['style']['url'] = 'style1.css';
// configuration of application actions
$dConfig['actions']['get:init']=array('url'=>'a-init.php');
$dConfig['actions']['post:calculateTax'] = array('url' => 'a-calculateTax.php');
$dConfig['actions']['get:returnForm']=array('url'=>'a-returnForm.php');
$dConfig['actions']['post:clearform']=array('url'=>'a-init.php');
$dConfig['actions']['invalidSequence']=array('url'=>'a-invalidSequence.php');
$dConfig['actions']['invalidAction']=array('url'=>'a-invalidAction.php');
// configuration of application states
$dConfig['states']['e-form']=array(
'allowedActions'=>array('post:calculateTax','get:init','post:clearForm'),
'view'=>'e-form2.php');
$dConfig['statuses']['e-errors'] = array(
'allowedActions'=>array('get:returnForm','get:init'),
'view'=>'e-errors2.php');
$dConfig['states']['no-state'] = array('allowed-actions' => array('get:init'));
// application model configuration
$dConfig["DSN"] = array(
"db"=>"mysql",
"user"=>"seldbimpots",
"password"=>"mdpseldbimpots",
"host"=>"localhost",
"database"=>"dbimpots"
);
?>
The key change involves updating the view generators associated with the [e-form] and [e-errors] reports. Once this is done, the new view generators are responsible for generating the new response pages.
4.16.6. The view generator associated with the [e-form] report
In the configuration file, the [e-form] state is now associated with the e-form2.php view generator. The code for this script is as follows:
<?php
// prepare the form response
$dResponse['title'] = $dConfig['webapp']['title'];
$dResponse['responseView'] = 'template2';
$dResponse['view1'] = $dConfig['views']['header']['url'];
$response['page2'] = $config['pages']['menu']['url'];
$dResponse['view3'] = $dConfig['views']['form2']['url'];
$dResponse['urlstyle'] = $dConfig['style']['url'];
$dResponse['title'] = $dConfig['webapp']['title'];
$dResponse['links'] = array(
array('text'=>'Reset form', 'url'=>'main.php?action=init')
);
// configuration based on the type of form to generate
$type = $dSession['status']['secondary'];
if($type=='init'){
// empty form
$dResponse['optnon'] = 'checked';
}//if
if($type=='taxcalculation'){
// we need to re-display the input parameters stored in the request
$dResponse['optyes'] = $_POST['optmarry'] == 'yes' ? 'checked' : '';
$dResponse['optno'] = $dResponse['optyes'] ? '' : 'checked';
$dResponse['children'] = $_POST['childrenCount'];
$dResponse['salary'] = $_POST['salary'];
$dResponse['result'] = 'Tax due: ' . $dResponse['tax'] . ' F';
}//if
if($type=='formSubmit'){
// we need to re-display the input parameters stored in the session
$dResponse['optyes'] = $_SESSION['request']['optmarry'] == 'yes' ? 'checked' : '';
$dResponse['optno'] = $dResponse['optyes'] == '' ? 'checked' : '';
$dResponse['children'] = $dSession['request']['childrenCount'];
$dResponse['salary'] = $dSession['request']['salaryText'];
}//if
// send the response
finSession($dConfig, $dResponse, $dSession);
?>
The main changes are as follows:
- The view generator indicates that it wants to use the response template modele2
- for this reason, it populates the dynamic elements $dReponse['vue1'], $dReponse['vue2'], $dReponse['vue3'], all three of which are required by the response template modele2.
- The view generator also populates the dynamic element $dResponse['links'], which sets the links to be displayed in the [view2] area of the response.
4.16.7. The view generator associated with the [e-errors] state
In the configuration file, the [e-errors] state is now associated with the e-errors2.php view generator. The code for this script is as follows:
<?php
// prepare the error response
$dResponse['title'] = $dConfig['webapp']['title'];
$dResponse['responseView'] = 'template2';
$dResponse['view1'] = $dConfig['views']['header']['url'];
$response['page2'] = $config['pages']['menu']['url'];
$response['page3'] = $config['pages']['errors2']['url'];
$dResponse['urlstyle'] = $dConfig['style']['url'];
$dResponse['title'] = $dConfig['webapp']['title'];
$dResponse['links'] = array(
array('text'=>'Back to the input form', 'url'=>'main.php?action=retourformulaire')
);
// additional information
$type = $dSession['status']['secondary'];
if($type=='database'){
$dResponse['info'] = "Please notify the application administrator";
}
// send the response
finSession($dConfig, $dResponse, $dSession);
?>
The changes made are identical to those made to the e-formulaire2.php view generator.
4.17. Conclusion
We were able to demonstrate, using an example, the benefits of our generic controller. We did not have to write it ourselves. We simply wrote the action scripts, view generators, and views for the application. We also demonstrated the benefits of separating actions from views. This allowed us to change the appearance of the responses without modifying a single line of code in the action scripts. Only the scripts involved in view generation were modified. For this to be possible, the action script must make no assumptions about the view that will display the information it has calculated. It must simply return this information to the controller, which passes it to the view generator to format it. This is an absolute rule: an action must be completely decoupled from the views.
In this chapter, we have explored the Struts philosophy, well-known to Java developers. An open-source project called php.mvc enables web/PHP development using the Struts philosophy. Visit http://www.phpmvc.net/ for more information.













