Skip to content

4. 一个示例应用

我们建议通过一个税费计算的示例来说明上述方法。

4.1. 问题

我们的目标是编写一个程序来计算纳税人的税款。我们考虑一种简化情况,即纳税人仅需申报工资收入:

  • 我们计算该员工的税率档次数量:nbParts = nbEnfants / 2 + 1(若未婚),nbEnfants / 2 + 2(若已婚),其中 nbEnfants 表示子女数量。若子女数量为三个或以上,则税率档次数量增加 0.5。
  • 计算其应税收入 R = 0.72 * S,其中 S 为其年薪
  • 我们计算其家庭系数 Q = R/N
  • 根据以下数据计算其应纳税额 I
限值
R系数
系数R
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
每行包含 3 个字段:limitcoeffR coeffN。要计算税款 I,请查找 QF <= limit 的第一行。例如,如果 QF = 30000,则找到的行是: 31810 0.2 3309.5此时税额 I 等于 0.2*R - 3309.5*nbParts。如果 QF 使得条件 QF <= limit 永远无法满足,则使用最后一行中的系数:0 0.65 49062,由此得出的税额 I 为 0.65*R - 49062*nbParts

4.2. 数据库

上述数据存储在一个名为 dbimpots 的 MySQL 数据库中用户 seldbimpots(密码为 mdpseldbimpots)对数据库内容具有只读权限。该数据库包含一个名为 impots 的表,其结构如下:

Image

其内容如下:

Image

4.3. 应用程序的MVC架构

该应用程序将采用以下MVC架构:

  • main.php 控制器即为上述所述的通用控制器
  • 客户端的请求以 main.php?action=xx 形式的查询发送至控制器。action 参数的值决定了应执行 ACTIONS 块中的哪个脚本。被执行的操作脚本会向控制器返回一个变量,该变量指示 Web 应用程序应处于何种状态。基于此状态,控制器将调用其中一个视图生成器,向客户端发送响应。
  • impots-data.php 是负责向控制器提供所需数据的类
  • impots-calcul.php 是负责计算税款的业务逻辑类

4.4. 数据访问类

数据访问类旨在将数据源从 Web 应用程序中隐藏起来。其接口包含一个 getData 方法,该方法返回计算税额所需的三个数据数组。在本示例中,数据是从 MySQL 数据库中检索的。为了使该类与实际的数据库管理系统 (DBMS) 类型无关,我们将使用附录中描述的 pear::DB 库。该类的代码如下:

<?php

   // libraries
  require_once 'DB.php';

  class impots_data{  
     // data source access class DBIMPOTS

       // attributes
        var $sDSN;         // the connection chain
        var $sDatabase;    // base name
        var $oDB;          // base connection
        var $aErreurs;     // error list
        var $oRésultats;   // query result
        var $connecté;     // boolean indicating whether or not you are connected to the database
        var $sQuery ;      // the last query executed

     // manufacturer
    function impots_data($dDSN){

         // $dDSN: dictionary defining the link to be established
       // $dDSN['sgbd']: type of SGBD to be connected to
       // $dDSN['host']: name of the host machine hosting it      
       // $dDSN['database']: name of the database to be connected to      
       // $dDSN['user']: a bse user
       // $dDSN['mdp']: its password

       // creates in $oDB a connection to the database defined by $dDSN as $dDSN['user']
       // if the connection is successful  
           // sets $sDSN to the database connection string
           // sets $sDataBase to the name of the database to which you are connecting
         // sets $connecté to true
       // if the connection fails
           // puts the appropriate error msg in the $aErreurs list
         // closes the connection if necessary
         // sets $connecté to false 

       // raz error list
            $this->aErreurs=array();

       // create a connection to the $sDSN database
      $this->sDSN=$dDSN["sgbd"]."://".$dDSN["user"].":".$dDSN["mdp"]."@".$dDSN["host"]."/".$dDSN["database"];
      $this->sDatabase=$dDSN["database"];
      $this->connect();

       // connected?
      if( ! $this->connecté) return;

       // the connection was successful     
      $this->connecté=TRUE;     
    }//manufacturer

    // ------------------------------------------------------------------
    function connect(){
         // (re)connecting to the base
       // raz error list
            $this->aErreurs=array();

       // create a connection to the $sDSN database
        $this->oDB=DB::connect($this->sDSN,true);

         // mistake?
        if(DB::iserror($this->oDB)){
          // on note l'erreur
          $this->aErreurs[]="Echec de la connexion à la base [".$this->sDatabase."] : [".$this->oDB->getMessage()."]";
         // connection failed
        $this->connecté=FALSE;
         // end
        return;
      }

       // we're connected
      $this->connecté=TRUE;
    }//connect

    // ------------------------------------------------------------------
    function disconnect(){
       // if connected, closes the connection to the $sDSN database
        if($this->connecté){
              $this->oDB->disconnect();
           // we are disconnected
        $this->connecté=FALSE;
            }//if
    }//disconnect

    // -------------------------------------------------------------------
    function execute($sQuery){
         // $sQuery: query to be executed

         // memorize the request
            $this->sQuery=$sQuery;    

       // are we connected?
      if(! $this->connecté){
          // on note l'erreur
        $this->aErreurs[]="Pas de connexion existante à la base [$this->sDatabase]";
         // end
        return;
      }//if

       // query execution
      $this->oRésultats=$this->oDB->query($sQuery);

         // mistake?
        if(DB::iserror($this->oRésultats)){
            // on note l'erreur
            $this->aErreurs[]="Echec de la requête [$sQuery] : [".$this->oRésultats->getMessage()."]";
           // return
        return;
      }//if     
    }//execute

    // ------------------------------------------------------------------
    function getData(){
       // we retrieve the 3 limit data series, coeffr, coeffn
      $this->execute('select limites, coeffR, coeffN from impots');
       // mistakes?
      if(count($this->aErreurs)!=0) return array();
       // browse the result of the select
      while ($ligne = $this->oRésultats->fetchRow(DB_FETCHMODE_ASSOC)) {
        $limites[]=$ligne['limites'];
        $coeffr[]=$ligne['coeffR'];
        $coeffn[]=$ligne['coeffN'];                
      }//while
      return array($limites,$coeffr,$coeffn);      
    }//getDataImpots

  }//class
?>      

一个测试程序可能如下所示:

<?php

   // library
  require_once "c-impots-data.php";  
  require_once "DB.php";

    // testing the impots-data class
  ini_set('track_errors','on');
  ini_set('display_errors','on');

     // dbimpots base configuration
    $dDSN=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );

   // opening of the session
  $oImpots=new impots_data($dDSN);
  // mistakes?
  if(checkErreurs($oImpots)){
    exit(0);
  }
   // follow-up
  echo "Connecté à la base...\n";

   // recovery of limit data, coeffr, coeffn
  list($limites,$coeffr,$coeffn)=$oImpots->getData();
   // mistakes?
  if( ! checkErreurs($oImpots)){
     // content
    echo "données : \n";
    for($i=0;$i<count($limites);$i++){
      echo "[$limites[$i],$coeffr[$i],$coeffn[$i]]\n";
    }//for
  }//if

   // disconnect
  $oImpots->disconnect();
   // follow-up
  echo "Déconnecté de la base...\n";  
   // end
  exit(0);

  // ----------------------------------
  function checkErreurs(&$oImpots){
       // mistakes?
    if(count($oImpots->aErreurs)!=0){
        // display
      for($i=0;$i<count($oImpots->aErreurs);$i++){
          echo $oImpots->aErreurs[$i]."\n";
      }//for
      // errors
      return true;
    }//if
     // no errors
    return false;
  }//checkErreurs  

?>     

运行此测试程序将得到以下结果:

Connecté à la base...
données : 
[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]
Déconnecté de la base...

4.5. 税额计算类

该类用于计算纳税人的税款。计算所需的数据通过构造函数提供,随后该类会计算出相应的税款。类代码如下:

<?php

  class impots_calcul{  
    // tax calculation class

     // manufacturer
    function impots_calcul(&$perso,&$data){
       // $perso: dictionary with the following keys
       // children : number of children
       // salary: annual salary
       // married: Boolean indicating whether the taxpayer is married or not
       // impot(s): tax payable calculated by this manufacturer
       // $data: dictionary with the following keys
       // limits: tranche limits table
       // coeffr: income coefficients table
       // coefficients tablaeu of coefficients of number of shares
       // the 3 arrays have the same number of elements

       // calculating the number of shares
      if($perso['marié'])
        $nbParts=$perso['enfants']/2+2;
        else $nbParts=$perso['enfants']/2+1;
      if ($perso['enfants']>=3) $nbParts+=0.5;

      // taxable income
      $revenu=0.72*$perso['salaire'];

       // family quotient
      $QF=$revenu/$nbParts;

       // search for tax bracket corresponding to QF
      $nbTranches=count($data['limites']);
      $i=0;
      while($i<$nbTranches-2 && $QF>$data['limites'][$i]) $i++;

      // tax
      $perso['impot']=floor($data['coeffr'][$i]*$revenu-$data['coeffn'][$i]*$nbParts);
    }//manufacturer
  }//class
?>

一个测试程序可能如下所示:

<?php

   // library
  require_once "c-impots-data.php";
  require_once "c-impots-calcul.php";  

     // dbimpots base configuration
    $dDSN=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );

   // opening of the session
  $oImpots=new impots_data($dDSN);
  // mistakes?
  if(checkErreurs($oImpots)){
    exit(0);
  }
   // follow-up
  echo "Connecté à la base...\n";  
   // recovery of limit data, coeffr, coeffn
  list($limites,$coeffr,$coeffn)=$oImpots->getData();
   // mistakes?
  if(checkErreurs($oImpots)){
    exit(0);
  }
   // we disconnect
  $oImpots->disconnect();
   // follow-up
  echo "Déconnecté de la base...\n";  

   // tax calculation
  $dData=array('limites'=>&$limites,'coeffr'=>&$coeffr,'coeffn'=>&$coeffn);
  $dPerso=array('enfants'=>2,'salaire'=>200000,'marié'=>true,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);
  $dPerso=array('enfants'=>3,'salaire'=>200000,'marié'=>false,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);
  $dPerso=array('enfants'=>3,'salaire'=>20000,'marié'=>true,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);
  $dPerso=array('enfants'=>3,'salaire'=>2000000,'marié'=>true,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);

   // end
  exit(0);

  // ----------------------------------
  function checkErreurs(&$oImpots){
       // mistakes?
    if(count($oImpots->aErreurs)!=0){
        // display
      for($i=0;$i<count($oImpots->aErreurs);$i++){
          echo $oImpots->aErreurs[$i]."\n";
      }//for
      // errors
      return true;
    }//if
     // no errors
    return false;
  }//checkErreurs  

?>        

运行此测试程序将得到以下结果:

Connecté à la base...
Déconnecté de la base...
[enfants,2] [salaire,200000] [marié,1] [impot,22506] 
[enfants,3] [salaire,200000] [marié,] [impot,22506] 
[enfants,3] [salaire,20000] [marié,1] [impot,0] 
[enfants,3] [salaire,2000000] [marié,1] [impot,706752]

4.6. 应用程序的工作原理

启动基于网页的税务计算器后,将显示以下 [v-form] 视图:

用户填写相关字段并请求计算税额:

请注意,表单会以用户提交时的状态重新生成,并显示应缴税额。用户可能在输入数据时出现错误。这些错误会通过一个错误页面进行标记,我们将该页面称为 [v-errors] 视图。

[返回输入表单] 链接允许用户返回其提交时的表单。

最后,[清空表单]按钮将表单重置为初始状态,即用户在首次请求时收到的状态。

4.7. 返回应用程序的 MVC 架构

该应用程序采用以下 MVC 架构:

我们刚刚介绍了 impots-data.phpimpots-calcul.php 这两个类。接下来我们将介绍该架构的其他组成部分。

4.8. 应用程序控制器

应用程序的 main.php 控制器即本章前半部分所描述的那个。这是一个与应用程序无关的通用控制器。

<?php
     // generic controller

   // configurable reading
  include 'config.php';

   // including libraries
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

   // start or resume session
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

   // retrieve the action to be taken
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // is the sequence of actions normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
     // abnormal sequence
    $sAction='enchainementinvalide';
  }//if

     // share processing
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;


   // send response(view) to customer
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

   // end of script - we shouldn't get there unless there's a bug
  trace ("Erreur de configuration.");
  trace("Action=[$sAction]");
  trace("scriptAction=[$scriptAction]");
  trace("Etat=[$sEtat]");
  trace("scriptVue=[$scriptVue]");
  trace ("Vérifiez que les script existent et que le script [$scriptVue] se termine par l'appel à finSession.");
  exit(0);

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
     // $dConfig: configuration dictionary
       // $dSession: dictionary containing session info
         // $dReponse: the dictionary of arguments for the response page

     // session registration
    if(isset($dSession)){
      // put the query parameters in the session
      $dSession['requete']=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();
    }

        // we present the answer
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

     // end of script
    exit(0);
  }//endsession      

  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
       // checks whether the current action is authorized with respect to the previous state
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

     // check action
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

  //--------------------------------------------------------------------
  function dump($dInfos){
       // displays an information dictionary
    while(list($clé,$valeur)=each($dInfos)){
        echo "[$clé,$valeur]<br>\n";
    }//while
  }//follow-up

  //--------------------------------------------------------------------
  function trace($msg){
      echo $msg."<br>\n";
  }//follow-up
?>

4.9. Web 应用程序操作

共有四个操作:

  • get:init: 这是在向控制器发送初始请求(不带参数)时触发的操作。它会渲染空的 'form' 视图。
  • post:clearform:由 [清除表单] 按钮触发的操作。它渲染空的 [v-form] 视图。
  • post:calculateTax: 由 [计算税款] 按钮触发的操作。它会生成包含应缴税额的 [v-form] 视图,或者生成 [v-errors] 视图。
  • get:returnform: 由 [返回输入表单] 链接触发的操作。它将渲染 [v-form] 视图,并预先填充错误数据。

这些操作在配置文件中的配置如下:

<?php

   // configuration of application actions
  $dConfig['actions']['get:init']=array('url'=>'a-init.php');  
  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

每个操作都与负责处理它的脚本相关联。每个操作都会将 Web 应用程序带入由 $dSession['state']['main'] 元素定义的状态。该状态旨在保存在会话中。此外,操作还会在 $dResponse 字典中存储显示与应用程序新状态相关联的视图所需的信息。

4.10. Web 应用程序状态

共有两种:


[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.

这些状态下允许的操作如下:

<?php

   // application status configuration
  $dConfig['etats']['formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));        

在某个状态下,允许的操作对应于与该状态关联的视图中链接或 [submit] 按钮的目标 URL。此外,'get:init' 操作始终被允许。这允许用户从浏览器的地址栏中获取 main.php 的 URL 并重新加载它,无论应用程序处于何种状态。这是一种“手动”重置。'sansetat' 状态仅在应用程序启动时存在。

每个应用程序状态都关联有一个脚本,负责生成与该状态相关的视图:

  • state [e-form]: 脚本 e-formulaire.php
  • 状态 [e-errors]:脚本 e-errors.php

[e-form] 状态将显示具有多种变体的 [v-form] 视图。实际上,[v-form] 视图可以显示为空表单、预填充表单,或包含税额的表单。将应用程序带入 [e-form] 状态的操作会在 $dSession['state']['main'] 变量中指定应用程序的主状态。控制器仅使用此信息。 在我们的应用程序中,导致进入 [e-form] 状态的操作会向 $dSession['state']['secondary'] 添加额外信息,从而让响应生成器知道应生成空表单、预填充表单,还是带税额或不带税额的表单。我们本可以采取另一种方法,即假设存在三种不同的状态,因此需要编写三个视图生成器。

4.11. Web 应用程序的 config.php 配置文件

<?php

     // php configuration
  ini_set("register_globals","off");
  ini_set("display_errors","off");  
  ini_set("expose_php","off");

  // list of modules to be included
  $dConfig['includes']=array('c-impots-data.php','c-impots-calcul.php');

  // application controller
  $dConfig['webapp']=array('titre'=>"Calculez votre impôt");

   // aplication view configuration
  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');
  $dConfig['vuesReponse']['modele2']=array('url'=>'m-reponse2.php');  
  $dConfig['vues']['formulaire']=array('url'=>'v-formulaire.php');
  $dConfig['vues']['erreurs']=array('url'=>'v-erreurs.php');
  $dConfig['vues']['formulaire2']=array('url'=>'v-formulaire2.php');
  $dConfig['vues']['erreurs2']=array('url'=>'v-erreurs2.php');
  $dConfig['vues']['bandeau']=array('url'=>'v-bandeau.php');
  $dConfig['vues']['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:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

  // application status configuration
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

   // model application configuration
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );
?>

4.12. Web 应用程序操作

4.12.1. 操作脚本的一般运行机制

  • 控制器会根据从客户端接收的 action 参数调用动作脚本。
  • 执行完成后,操作脚本必须告知控制器将应用程序置于何种状态。该状态必须指定在 $dSession['etat']['principal'] 中。
  • 操作脚本可能需要将信息存储在会话中。它通过将这些信息放入 $dSession 字典中来实现,该字典会在请求-响应周期结束时由控制器自动保存到会话中。
  • 一个操作脚本可能包含需要传递给视图的信息。这一方面与控制器无关。它是操作与视图之间的接口,且该接口因应用程序而异。在本例中,操作将通过一个名为 $dResponse 的字典向视图生成器提供信息。

4.12.2. get:init 动作

这是生成空表单的操作。配置文件显示该操作将由 a-init.php 脚本处理:

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

a-init.php 脚本的代码如下:

<?php
     // the input form is displayed
  $dSession['etat']=array('principal'=>'e-formulaire', 'secondaire'=>'init');
?>  

该脚本仅将应用程序应处于的状态——即 [e-form] 状态——设置在 $dSession['etat']['principal'] 中,并在 $dSession['etat']['secondaire'] 中提供了该状态的描述。配置文件显示,控制器将执行 e-formulaire.php 脚本以生成对客户端的响应。

<?php

  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');

e-formulaire.php 脚本将生成 [v-formulaire] 视图的 [init] 变体,即空表单。

4.12.3. post:calculateTax 操作

这是根据表单中输入的数据计算税款的操作。配置文件指定由 a-calculimpot.php 脚本处理此操作:

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

a-calculimpot.php 脚本的代码如下:

<?php
     // tax calculation request

   // we first check the validity of the parameters
  $sOptMarie=$_POST['optmarie'];
  if($sOptMarie!='oui' && $sOptMarie!='non'){
      $erreurs[]="L'état marital [$sOptMarie] est erroné";
  }
  $sEnfants=trim($_POST['txtenfants']);
  if(! preg_match('/^\d{1,3}$/',$sEnfants)){
      $erreurs[]="Le nombre d'enfants [$sEnfants] est erroné";
  }
  $sSalaire=trim($_POST['txtsalaire']);
  if(! preg_match('/^\d+$/',$sSalaire)){
      $erreurs[]="Le salaire annuel [$sSalaire] est erroné";
  }

   // if there are mistakes, it's over
  if(count($erreurs)!=0){
      // preparing the error page
    $dReponse['erreurs']=&$erreurs;
    $dSession['etat']=array('principal'=>'e-erreurs','secondaire'=>'saisie');
      return;
  }//if

   // the data entered is correct
   // retrieve the data needed to calculate taxes
  if(! $dSession['limites']){
       // the data is not in the session
     // we retrieve them from the data source
    list($erreurs,$limites,$coeffr,$coeffn)=getData($dConfig['DSN']);
     // if there are errors, the error page is displayed
    if(count($erreurs)!=0){
        // preparing the error page
      $dReponse['erreurs']=&$erreurs;
        $dSession['etat']=array('principal'=>'e-erreurs','secondaire'=>'database');
      return;
    }//if
     // no errors - data put into session
    $dSession['limites']=&$limites;
    $dSession['coeffr']=&$coeffr;
    $dSession['coeffn']=&$coeffn;
  }//if

   // here you have the data you need to calculate your taxes
  // on calcule celui-ci
  $dData=array('limites'=>&$dSession['limites'],
      'coeffr'=>&$dSession['coeffr'],
    'coeffn'=>&$dSession['coeffn']);
  $dPerso=array('enfants'=>$sEnfants,'salaire'=>$sSalaire,'marié'=>($sOptMarie=='oui'),'impot'=>0);
  new impots_calcul($dPerso,$dData);

    // preparing the answer page
  $dSession['etat']=array('principal'=>'e-formulaire','secondaire'=>'calculimpot');
  $dReponse['impot']=$dPerso['impot'];
  return;

  //-----------------------------------------------------------------------
  function getData($dDSN){
       // connection to the data source defined by the $dDSN dictionary
        $oImpots=new impots_data($dDSN);
    if(count($oImpots->aErreurs)!=0) return array($oImpots->aErreurs);
    // recovery of limit data, coeffr, coeffn
        list($limites,$coeffr,$coeffn)=$oImpots->getData();
         // we disconnect
        $oImpots->disconnect();
     // we return the result
    if(count($oImpots->aErreurs)!=0) return array($oImpots->aErreurs);
        else return array(array(),$limites,$coeffr,$coeffn);
  }//getData

该脚本完成了其预定功能:计算税款。具体处理代码的解析就留给读者自行研究了。我们关注的是此操作可能引发的状态:

  • 输入数据有误或访问数据时出现问题:应用程序将进入 [e-errors] 状态。配置文件显示,e-errors.php 脚本将负责生成响应视图:
<?php

  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  • 在所有其他情况下,应用程序将设置为 [e-form] 状态,并使用 $dSession['state']['secondary'] 中指定的税费计算方案。配置文件显示,e-formulaire.php 脚本将生成响应视图。它将使用 $dSession['state']['secondary'] 的值,生成一个预填充表单,其中包含用户输入的值和税费金额。

4.12.4. post:effacerformulaire 操作

该操作通过配置与前文所述的 a-init.php 脚本相关联。

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

4.12.5. get:return-form 操作

它允许您从 [e-errors] 状态返回至 [e-form] 状态。a-retourformulaire.php 脚本负责处理此操作:

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

a-retourformulaire.php 脚本内容如下:

<?php
     // the input form is displayed
  $dSession['etat']=array('principal'=>'e-formulaire','secondaire'=>'retourformulaire');
?>    

我们只需将应用程序设置为 [e-form] 状态及其 [form-return] 变体。配置文件显示,控制器将执行 e-form.php 脚本以生成对客户端的响应。

<?php

  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');

e-formulaire.php 脚本将生成 [v-formulaire] 视图的 [retourformulaire] 变体,即表单中已预填用户输入的值,但不包含税额。

4.13. 无效的操作序列

应用程序在给定状态下的有效操作由配置设定:

<?php

   // application status configuration
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

我们已经解释过此配置。如果检测到无效的操作序列,将执行脚本 a-invalid-sequence.php

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

该脚本的代码如下:

<?php 
     // invalid sequence of actions
  $dReponse['erreurs']=array("Enchaînement d'actions invalide");
  $dSession['etat']=array('principal'=>'e-erreurs','secondaire'=>'enchainementinvalide');  
?>

这将应用程序设置为 [e-errors] 状态。我们在 $dSession['state']['secondary'] 中提供了信息,这些信息将被错误页面生成器使用。正如我们之前所见,该生成器是 e-errors.php

<?php

  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');

我们稍后将查看此生成器的代码。发送给客户端的视图如下:

Image

4.14. 应用程序的视图

4.14.1. 显示最终视图

让我们看看在执行完客户端请求的操作后,控制器是如何将响应发送给客户端的:

<?php

....
   // start or resume session
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

   // retrieve the action to be taken
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // is the sequence of actions normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
     // abnormal sequence
    $sAction='enchainementinvalide';
  }//if

     // share processing
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

   // send response(view) to customer
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

.....

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
     // $dConfig: configuration dictionary
       // $dSession: dictionary containing session information
         // $dReponse: the dictionary of arguments for the response page

     // session registration
...

         // reply sent to customer
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

     // end of script
    exit(0);
  }//endsession      

从操作脚本返回后,控制器会从 $dSession['etat']['principal'] 中获取必须用于设置应用程序的状态。该状态由刚刚执行的操作所设定。随后,控制器会执行与该状态关联的视图生成器。它会在配置文件中查找视图生成器的名称。视图生成器的作用如下:

  • 将要使用的响应模板名称设置在 $dResponse['responseView'] 中。该信息将传递给控制器。模板是由基本视图组合而成的,这些基本视图组合后形成最终视图。
  • 准备将在最终视图中显示的动态信息。此步骤独立于控制器。它充当视图生成器与最终视图之间的接口,且针对每个应用程序具有特定实现。
  • 必须以调用控制器的 finSession 函数结束。该函数将
    • 保存会话
    • 发送响应

finSession 函数的代码如下:

<?php

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
     // $dConfig: configuration dictionary
       // $dSession: dictionary containing session information
         // $dReponse: the dictionary of arguments for the response page

     // session registration
...

         // reply sent to customer
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

     // end of script
    exit(0);
  }//endsession      

发送给用户的视图由 $dResponse['responseView'] 实体定义,该实体指定了用于最终响应的模板。

4.14.2. 响应模板

应用程序将基于以下单一模板生成各种响应:

该模板与 $dConfig['vuesreponse'] 字典中的 'modele1' 键相关联:

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

m-reponse.php 脚本负责生成此模板:

<html>
    <head>
      <title>Application impots</title>
      <link type="text/css" href="<?php echo $dReponse['urlstyle'] ?>" rel="stylesheet" />
  </head>
  <body>
    <?php
            include $dReponse['vue1'];
        ?>
    <hr>
    <?php
            include $dReponse['vue2'];
        ?>
    </body>
</html>

该脚本包含三个动态元素,这些元素存储在字典 $dResponse 中,并与以下键相关联:

  • urlstyle:模板样式表的 URL
  • view1:负责生成 view1 视图的脚本名称
  • vue2:负责生成 vue2 视图的脚本名称

希望使用 modèle1 模板的视图生成器必须定义这三个动态元素。现在,我们定义可以替换模板中 [vue1] 和 [vue2] 元素的基本视图。

4.14.3. 基本视图 v-bandeau.php

v-bandeau.php 脚本生成一个视图,该视图将被放置在 [vue1] 区域中:

<table>
    <tr>
      <td><img src="univ01.gif"></td>
    <td>
      <table>
        <tr>
          <td><div class='titre'><?php echo $dReponse['titre'] ?></div></td>
        </tr>
        <tr>
            <td><div class='resultat'><?php echo $dReponse['resultat']?></div></td>
        </tr>
      </table>
    </td>
  </tr>
</table>    

视图生成器必须在字典 $dResponse 中定义两个动态元素,并将其与以下键关联:


title: title to display
result: amount of tax due

4.14.4. 基本视图 v-form.php

[vue2] 部分对应 [v-form] 视图或 [v-errors] 视图。 [v-form] 视图由 v-formulaire.php 脚本生成:

<form method="post" action="main.php?action=calculerimpot">
    <table>
      <tr>
        <td class="libelle">Etes-vous marié(e)</td>
      <td class="valeur">
          <input type="radio" name="optmarie" <?php echo $dReponse['optoui'] ?> value="oui">oui
          <input type="radio" name="optmarie" <?php echo $dReponse['optnon'] ?> value="non">non        
      <td>
    <tr>
        <td class="libelle">Nombre d'enfants</td>
      <td class="valeur">
          <input type="text" class="text" name="txtenfants" size="3" value="<?php echo $dReponse['enfants'] ?>"        
      </td>
    </tr>
    <tr>
        <td class="libelle">Salaire annuel</td>
      <td class="valeur">
          <input type="text" class="text" name="txtsalaire" size="10" value="<?php echo $dReponse['salaire'] ?>"        
      </td>
    </tr>
    </table>
  <hr>
  <input type="submit" class="submit" value="Calculer l'impôt">  
</form>
<form method="post" action="main.php?action=effacerformulaire">
  <input type="submit" class="submit" value="Effacer le formulaire">
</form>                                            

此视图中必须由视图生成器定义的动态部分与 $dReponse 字典中的以下键相关联:

  • optoui:名为 optoui 的单选按钮的状态
  • optnon:名为 optnon 的单选按钮的状态
  • children:将放置在 txtenfants 字段中的子项数量
  • salary:将放入 txtsalary 字段的年薪

4.14.5. 基本视图 v-erreurs.php

[v-errors] 视图由 v-errors.php 脚本生成:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($dReponse["erreurs"]);$i++){
            echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
        }//for
    ?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<br>
<a href="<?php echo $dReponse["href"] ?>"><?php echo $dReponse["lien"] ?></a>

此视图中由视图生成器定义的动态部分与 $dReponse 字典中的以下键相关联:

  • errors:错误消息数组
  • info:信息提示
  • link:链接文本
  • href:上述链接的目标 URL

4.14.6. 样式表

所有视图均由样式表进行样式设置。若要更改应用程序的外观,请修改其样式表。以下是 style1.css 样式表:

div.menu {
    background-color: #FFD700;
    color: #F08080;
    font-weight: bolder;
    text-align: center;
}
td.separateur {
    background: #FFDAB9;
    width: 20px;
}

table.modele2 {
    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.titre {
    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.libelle {
    background-color: #F0FFFF;
    color: #0000CD;
}

td.valeur {
    background-color: #DDA0DD;
}

DIV.resultat {
    background-color : #FFA07A;
    font : bold 12pt;
}

div.info {
    color: #FA8072;
}

li.erreur {
    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. 查看生成器

4.15.1. 视图生成器的作用

让我们回顾一下执行视图生成器的控制器代码片段:

<?php

     // share processing
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

   // send response(view) to customer
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

视图生成器与应用程序所在的状态相关联。状态与视图生成器之间的关联通过配置进行设置:

<?php

   // application status configuration
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

我们已经解释过视图生成器的作用。让我们在此回顾一下。视图生成器:

  • $dResponse['responseView'] 中设置要使用的响应模板名称。该信息将传递给控制器。模板是由基本视图组合而成的,这些基本视图组合在一起形成最终视图。
  • 准备将在最终视图中显示的动态信息。此步骤独立于控制器。它充当视图生成器与最终视图之间的接口,且针对每个应用程序具有特定性。
  • 必须以调用控制器的 finSession 函数作为结尾。该函数将
    • 保存会话
    • 发送响应

4.15.2. 与 [e-form] 状态关联的视图生成器

负责生成与 [e-form] 状态相关联视图的脚本名为 e-form.php

<?php

  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');

其代码如下:

<?php
   // we prepare the answer form
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele1';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['formulaire']['url'];  
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];

   // configuration according to the type of form to be generated
    $type=$dSession['etat']['secondaire'];
  if($type=='init'){
      // empty form
    $dReponse['optnon']='checked';
  }//if
  if($type=='calculimpot'){
      // we need to redisplay the input parameters stored in the query
    $dReponse['optoui']=$_POST['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui'] ? '' : 'checked';
    $dReponse['enfants']=$_POST['txtenfants'];
    $dReponse['salaire']=$_POST['txtsalaire'];
    $dReponse['resultat']='Impôt à payer : '.$dReponse['impot'].' F';    
  }//if
  if($type=='retourformulaire'){
      // we need to redisplay the input parameters stored in the session
    $dReponse['optoui']=$dSession['requete']['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui']=='' ? 'checked' : '';  
    $dReponse['enfants']=$dSession['requete']['txtenfants'];
    $dReponse['salaire']=$dSession['requete']['txtsalaire'];
  }//if
   // we send the answer
  finSession($dConfig,$dReponse,$dSession);
?>      

请注意,该视图生成器符合视图生成器的要求:

  • $dResponse['vuereponse'] 中设置要使用的响应模板
  • 将信息传递给该模板。此处是通过 $dResponse 字典进行传递的。
  • 最后调用控制器的 finSession 函数

此处使用的模板为 'template1'。因此,视图生成器定义了该模板所需的两项信息:$dResponse['view1']$dResponse['view2']

就本应用的具体情况而言,与 [e-form] 状态关联的视图取决于变量 $dSession['etat']['secondaire'] 中存储的信息。这是一种设计选择。其他应用可能会选择以不同的方式传递额外信息。此外,此处显示最终视图所需的所有信息都放置在 $dResponse 字典中。 同样,这取决于开发者的选择。[e-form] 状态可能在四种不同操作之后出现:initcalculateTaxreturnFormclearForm。在所有情况下,要显示的视图并不完全相同。因此,我们在 $dSession['state']['secondary'] 中区分了三种情况:

  • init:显示空表单
  • calculateTax:表单显示税额及用于计算的原始数据
  • returnform:表单显示初始输入的数据

上文中的 e-formulaire.php 脚本利用这些信息,根据这三种情况呈现相应的响应。

4.15.3. 与 [e-errors] 状态关联的视图

负责生成与 [e-errors] 状态相关视图的脚本名为 e-errors.php,内容如下:

<?php

   // we prepare the answer errors
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele1';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['erreurs']['url'];  
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];
  $dReponse['lien']='Retour au formulaire de saisie';
  $dReponse['href']='main.php?action=retourformulaire';

   // additional information
  $type=$dSession['etat']['secondaire'];
  if($type=='database'){
      $dReponse['info']="Veuillez avertir l'administrateur de l'application";
  }

   // we send the answer
  finSession($dConfig,$dReponse,$dSession);
?>

4.15.4. 显示最终视图

生成这两个最终视图的脚本均以调用控制器中的 finSession 函数结尾:

<?php

  function finSession(&$dConfig,&$dReponse,&$dSession){
     // $dConfig: configuration dictionary
       // $dSession: dictionary containing session information
         // $dReponse: the dictionary of arguments for the response page

....

         // we present the answer
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

     // end of script
    exit(0);
  }//endsession      

发送给用户的视图由 $dResponse['responseView'] 实体定义,该实体指定了用于最终响应的模板。对于 [e-form][e-errors] 状态,此模板已设置为 template1

    $dReponse['vuereponse']='modele1';

该模板对应于 m-response.php 脚本:

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

4.16. 修改响应模板

在此,我们假设决定更改发送给客户端的响应的视觉外观,并希望了解这会对代码产生哪些影响。

4.16.1. 新模板

响应的结构现在将如下所示:

该模板将命名为 template2,负责生成此模板的脚本将命名为 m-response2.php

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

与该模板对应的脚本如下:

<html>
    <head>
      <title>Application impots</title>
      <link type="text/css" href="<?php echo $dReponse['urlstyle'] ?>" rel="stylesheet" />
  </head>
  <body>
      <table class='modele2'>
        <!-- start banner -->
        <tr>
          <td colspan="2"><?php    include $dReponse['vue1']; ?></td>
      </tr>
         <!-- slim band -->            
      <tr>
             <!-- start menu -->      
          <td><?php include $dReponse['vue2']; ?></td>
             <!-- end menu -->
             <!-- start zone 3 -->                        
          <td><?php include $dReponse['vue3']; ?></td>
             <!-- end of zone 3 -->        
      </tr>
   </table>
    </body>
</html>

该模板的动态元素如下:

  • $dReponse['urlstyle']: 要使用的样式表
  • $dReponse['vue1']: 用于生成 [vue1] 的脚本
  • $dReponse['vue2']: 用于生成 [vue2] 的脚本
  • $dReponse['vue3']: 用于生成 [vue3] 的脚本

这些元素必须由视图生成器设置。

4.16.2. 不同的响应页面

应用程序现在将向用户呈现以下响应。在首次调用时,响应页面如下所示:

如果用户提供了有效数据,系统将计算税款:

如果用户输入有误,将显示错误页面:

如果用户点击[返回输入表单]链接,将看到上次提交时的表单:

如果在上述情况下,用户点击[重置表单]链接,将看到一个空白的表单:

请注意,该应用程序使用的操作与之前相同。仅响应的显示形式发生了变化。

4.16.3. 基本视图

基本视图 [view1] 将与 v-bandeau.php 脚本关联,与前面的示例一样:

<table>
    <tr>
      <td><img src="univ01.gif"></td>
    <td>
      <table>
        <tr>
          <td><div class='titre'><?php echo $dReponse['titre'] ?></div></td>
        </tr>
        <tr>
            <td><div class='resultat'><?php echo $dReponse['resultat']?></div></td>
        </tr>
      </table>
    </td>
  </tr>
</table>  

此视图包含两个动态元素:

  • $dResponse['title']: 要显示的标题
  • $dResponse['resultat']: 应缴税额

基本视图 [vue2] 将与以下 v-menu.php 脚本关联:

<table class="menu">
    <tr>
      <td><div class="menu">Options</div></td>
  </tr>
  <?php
      for($i=0;$i<count($dReponse['liens']);$i++){
        echo '<tr><td><div class="option"><a href="'.
          $dReponse['liens'][$i]['url'].
        '">'.$dReponse['liens'][$i]['texte']."</a></div></td></tr>\n";
    }//$i
  ?>
</table>

此视图包含以下动态元素:

  • $dResponse['links']: 用于在 [view2] 中显示的链接数组。数组的每个元素都是一个字典,包含两个键:
    • 'url':链接的目标 URL
    • 'text':链接文本

基础视图 [view3] 将与 v-form2.php 脚本关联(若需显示输入表单),或与 v-errors2.php 脚本关联(若需显示错误页面)。v-form2.php 脚本的代码如下:

<form method="post" action="main.php?action=calculerimpot">
    <table>
      <tr>
        <td class="libelle">Etes-vous marié(e)</td>
      <td class="valeur">
          <input type="radio" name="optmarie" <?php echo $dReponse['optoui'] ?> value="oui">oui
          <input type="radio" name="optmarie" <?php echo $dReponse['optnon'] ?> value="non">non        
      </td>
    </tr>
    <tr>
        <td class="libelle">Nombre d'enfants</td>
      <td class="valeur">
          <input type="text" class="text" name="txtenfants" size="3" value="<?php echo $dReponse['enfants'] ?>"        
      </td>
    </tr>
    <tr>
        <td class="libelle">Salaire annuel</td>
      <td class="valeur">
          <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="Calculer l'impôt"></td>
    </tr>
    </table>
</form>

该视图中必须由视图生成器定义的动态部分与 $dResponse 字典中的以下键相关联:

  • optoui:名为 optoui 的单选按钮的状态
  • optnon:名为 optnon 的单选按钮的状态
  • children:将放入 txtenfants 字段中的子项数量
  • salaire:将放入 txtsalaire 字段的年薪

生成错误页面的脚本名为 v-erreurs2.php。其代码如下:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($dReponse["erreurs"]);$i++){
            echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
        }//for
    ?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>

此视图中由视图生成器定义的动态部分与 $dReponse 字典中的以下键相关联:


errors: array of error messages
info: information message

4.16.4. 样式表

它没有改变。仍然是 style1.css

4.16.5. 新的配置文件

要实现这些新视图,我们需要修改配置文件中的某些行。

<?php

     // php configuration
  ini_set("register_globals","off");
  ini_set("display_errors","off");  
  ini_set("expose_php","off");

  // list of modules to be included
  $dConfig['includes']=array('c-impots-data.php','c-impots-calcul.php');

  // application controller
  $dConfig['webapp']=array('titre'=>"Calculez votre impôt");

   // aplication view configuration
  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');
  $dConfig['vuesReponse']['modele2']=array('url'=>'m-reponse2.php');  
  $dConfig['vues']['formulaire']=array('url'=>'v-formulaire.php');
  $dConfig['vues']['erreurs']=array('url'=>'v-erreurs.php');
  $dConfig['vues']['formulaire2']=array('url'=>'v-formulaire2.php');
  $dConfig['vues']['erreurs2']=array('url'=>'v-erreurs2.php');
  $dConfig['vues']['bandeau']=array('url'=>'v-bandeau.php');
  $dConfig['vues']['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:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

  // application status configuration
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire2.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs2.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

   // model application configuration
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );
?>

关键变更涉及更新与 [e-form] 和 [e-errors] 报表关联的视图生成器。完成此操作后,新的视图生成器将负责生成新的响应页面。

4.16.6. 与 [e-form] 报告关联的视图生成器

在配置文件中,[e-form]状态现已关联至e-form2.php视图生成器。该脚本的代码如下:

<?php
   // we prepare the answer form
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele2';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['menu']['url'];
  $dReponse['vue3']=$dConfig['vues']['formulaire2']['url'];
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];
  $dReponse['liens']=array(
      array('texte'=>'Réinitialiser le formulaire', 'url'=>'main.php?action=init')
  );              

   // configuration according to the type of form to be generated
    $type=$dSession['etat']['secondaire'];
  if($type=='init'){
      // empty form
    $dReponse['optnon']='checked';
  }//if
  if($type=='calculimpot'){
      // we need to redisplay the input parameters stored in the query
    $dReponse['optoui']=$_POST['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui'] ? '' : 'checked';
    $dReponse['enfants']=$_POST['txtenfants'];
    $dReponse['salaire']=$_POST['txtsalaire'];
    $dReponse['resultat']='Impôt à payer : '.$dReponse['impot'].' F';
  }//if
  if($type=='retourformulaire'){
      // we need to redisplay the input parameters stored in the session
    $dReponse['optoui']=$dSession['requete']['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui']=='' ? 'checked' : '';  
    $dReponse['enfants']=$dSession['requete']['txtenfants'];
    $dReponse['salaire']=$dSession['requete']['txtsalaire'];
  }//if
   // we send the answer
  finSession($dConfig,$dReponse,$dSession);
?>  

主要更改如下:

  • 视图生成器表明它希望使用响应模板 modele2
  • 因此,它填充了动态元素 $dResponse['vue1'], $dResponse['vue2'], $dResponse['vue3'],这三个元素均是响应模板 modele2 所必需的。
  • 视图生成器还填充了动态元素 $dResponse['links'],该元素用于设置将在响应的 [view2] 区域中显示的链接。

4.16.7. 与 [e-errors] 状态关联的视图生成器

在配置文件中,[e-errors] 状态现已关联到 e-errors2.php 视图生成器。该脚本的代码如下:

<?php

   // we prepare the answer errors
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele2';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['menu']['url'];  
  $dReponse['vue3']=$dConfig['vues']['erreurs2']['url'];  
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];
  $dReponse['liens']=array(
      array('texte'=>'Retour au formulaire de saisie', 'url'=>'main.php?action=retourformulaire')
  );              

   // additional information
  $type=$dSession['etat']['secondaire'];
  if($type=='database'){
      $dReponse['info']="Veuillez avertir l'administrateur de l'application";
  }

   // we send the answer
  finSession($dConfig,$dReponse,$dSession);
?>  

所做的更改与对 e-formulaire2.php 视图生成器所做的更改完全一致。

4.17. 结论

通过一个示例,我们成功展示了通用控制器带来的优势。我们无需亲自编写控制器,只需为应用程序编写操作脚本、视图生成器和视图即可。我们还展示了将操作与视图分离的好处。这使我们能够在不修改操作脚本中任何一行代码的情况下,改变响应的外观。仅需修改参与视图生成的脚本。 要实现这一点,操作脚本绝不能对将要显示其计算结果的视图做出任何假设。它只需将信息返回给控制器,由控制器将其传递给视图生成器进行格式化。这是一条绝对规则:操作必须与视图完全解耦。

在本章中,我们探讨了 Java 开发者熟知的 Struts 理念。一个名为 php.mvc 的开源项目支持基于 Struts 理念进行 Web/PHP 开发。请访问 http://www.phpmvc.net/ 获取更多信息。