Skip to content

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
limit
coeffR
coeffN
12,620.0
0
0
13190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4,900
48,360
0.3
6,898.5
55,790
0.35
9316.5
92970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39,312
0
0.65
49,062
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:

Image

Its contents are as follows:

Image

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:

  $dConfig['actions']['get:init']=array('url'=>'a-init.php');

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:

  $dConfig['actions']['post:calculateTax']=array('url'=>'a-calculateTax.php');

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.

  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');

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:

  $dConfig['actions']['get:return-form']=array('url'=>'a-return-form.php');

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:

  $dConfig['actions']['invalidSequence']=array('url'=>'a-invalidSequence.php');

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:

Image

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:

  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');

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:

    $dResponse['viewResponse'] = 'template1';

This template corresponds to the m-reponse.php script:

  $dConfig['responseViews']['template1']=array('url'=>'m-reponse.php');

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:

  $dConfig['vuesReponse']['modele2']=array('url'=>'m-reponse2.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.