Skip to content

4. Uma aplicação ilustrativa

Propomos ilustrar o método anterior com um exemplo de cálculo de impostos.

4.1. O problema

O nosso objetivo é escrever um programa para calcular o imposto de um contribuinte. Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:

  • Calculamos o número de escalões de imposto para o empregado: nbParts = nbEnfants / 2 + 1 se solteiro, nbEnfants / 2 + 2 se casado, onde nbEnfants é o número de filhos. O número de escalões é aumentado em 0,5 se houver três ou mais filhos.
  • Calculamos o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
  • Calculamos o seu coeficiente familiar Q = R/N
  • Calculamos o seu imposto I com base nos seguintes dados
limite
coeffR
coeffN
12 620,0
0
0
13.190
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
9.316,5
92 970
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
Cada linha tem 3 campos: limite, coeffR e coeffN. Para calcular o imposto I, encontre a primeira linha em que QF <= limite. Por exemplo, se QF = 30000, a linha encontrada é: 31810 0,2 3309,5. O imposto I é então igual a 0,2*R - 3309,5*nbParts. Se QF for tal que a condição QF <= limite nunca seja satisfeita, então são utilizados os coeficientes da última linha: 0 0,65 49062, o que dá o imposto I = 0,65*R - 49062*nbParts.

4.2. A base de dados

Os dados anteriores estão armazenados numa base de dados MySQL denominada dbimpots. O utilizador seldbimpots, com a palavra-passe mdpseldbimpots, tem acesso de leitura ao conteúdo da base de dados. A base de dados contém uma única tabela denominada impots com a seguinte estrutura:

Image

O seu conteúdo é o seguinte:

Image

4.3. A arquitetura MVC da aplicação

A aplicação terá a seguinte arquitetura MVC:

  • O controlador main.php será o controlador genérico descrito acima
  • O pedido do cliente é enviado ao controlador sob a forma de uma consulta do tipo main.php?action=xx. O valor do parâmetro action determina qual o script do bloco ACTIONS a executar. O script de ação executado devolve uma variável ao controlador indicando o estado em que a aplicação web deve ser colocada. Com este estado, o controlador ativará um dos geradores de visualização para enviar a resposta ao cliente.
  • impots-data.php é a classe responsável por fornecer ao controlador os dados de que necessita
  • impots-calcul.php é a classe de lógica de negócio que calcula o imposto

4.4. A classe de acesso aos dados

A classe de acesso aos dados foi concebida para ocultar a fonte de dados da aplicação web. A sua interface inclui um método getData que retorna os três vetores de dados necessários para calcular o imposto. No nosso exemplo, os dados são recuperados de uma base de dados MySQL. Para tornar a classe independente do tipo de SGBD real, utilizaremos a biblioteca pear::DB descrita no apêndice. O código da classe é o seguinte:

<?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
?>      

Um programa de teste poderia ter o seguinte aspeto:

<?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  

?>     

A execução deste programa de teste produz os seguintes resultados:

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. A classe de cálculo de impostos

Esta classe é utilizada para calcular o imposto de um contribuinte. Os dados necessários para este cálculo são fornecidos ao seu construtor. Em seguida, calcula o imposto correspondente. O código da classe é o seguinte:

<?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
?>

Um programa de teste poderia ter o seguinte aspeto:

<?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  

?>        

A execução deste programa de teste produz os seguintes resultados:

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. Como funciona a aplicação

Quando a calculadora de impostos baseada na web é iniciada, aparece a seguinte vista [v-form]:

O utilizador preenche os campos e solicita o cálculo do imposto:

Note que o formulário é regenerado no estado em que o utilizador o enviou e também apresenta o montante do imposto devido. O utilizador pode cometer erros na introdução de dados. Estes são assinalados por uma página de erro, à qual chamaremos de vista [v-errors].

O link [Voltar ao formulário de entrada] permite ao utilizador regressar ao formulário tal como o enviou.

Por fim, o botão [Limpar formulário] repõe o formulário no seu estado inicial, ou seja, tal como o utilizador o recebeu durante o pedido inicial.

4.7. Voltar à arquitetura MVC da aplicação

A aplicação tem a seguinte arquitetura MVC:

Acabámos de descrever as duas classes impots-data.php e impots-calcul.php. Vamos agora descrever os outros elementos da arquitetura.

4.8. O controlador da aplicação

O controlador main.php da aplicação é aquele descrito na primeira parte deste capítulo. Trata-se de um controlador genérico, independente da aplicação.

<?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. Ações da aplicação web

Existem quatro ações:

  • get:init: Esta é a ação acionada durante a solicitação inicial ao controlador sem parâmetros. Ela renderiza a visualização 'form' vazia.
  • post:clearform: ação acionada pelo botão [Limpar Formulário]. Ela renderiza a vista [v-form] vazia.
  • post:calculateTax: ação acionada pelo botão [Calcular imposto]. Gera a vista [v-form] com o valor do imposto a pagar ou a vista [v-errors].
  • get:returnform: ação acionada pelo link [Voltar ao formulário de entrada]. Apresenta a vista [v-form] pré-preenchida com os dados incorretos.

Estas ações são configuradas da seguinte forma no ficheiro de configuração:

<?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');          

Cada ação está associada ao script responsável pelo seu processamento. Cada ação levará a aplicação web para um estado definido pelo elemento $dSession['state']['main']. Este estado destina-se a ser guardado na sessão. Além disso, a ação armazena no dicionário $dResponse as informações necessárias para apresentar a vista associada ao novo estado em que a aplicação ficará.

4.10. Estados da Aplicação Web

Existem dois:


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

As ações permitidas nestes estados são as seguintes:

<?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'));        

Num estado, as ações permitidas correspondem aos URLs de destino dos links ou botões [submit] na vista associada a esse estado. Além disso, a ação 'get:init' é sempre permitida. Isto permite ao utilizador recuperar o URL main.php da barra de URL do seu navegador e recarregá-lo independentemente do estado da aplicação. Trata-se de uma espécie de reinicialização «manual». O estado 'sansetat' existe apenas quando a aplicação é iniciada.

Cada estado da aplicação está associado a um script responsável por gerar a vista associada a esse estado:

  • estado [e-form]: script e-formulaire.php
  • estado [e-errors]: script e-errors.php

O estado [e-form] exibirá a vista [v-form] com variações. Na verdade, a vista [v-form] pode ser exibida vazia, pré-preenchida ou com o valor do imposto. A ação que coloca a aplicação no estado [e-form] especifica o estado principal da aplicação na variável $dSession['state']['main']. O controlador utiliza apenas esta informação. Na nossa aplicação, a ação que conduz ao estado [e-form] irá adicionar informações adicionais a $dSession['state']['secondary'], permitindo que o gerador de respostas saiba se deve gerar um formulário vazio, um formulário pré-preenchido ou um formulário com ou sem o valor do imposto. Poderíamos ter adotado uma abordagem diferente, assumindo que existiam três estados diferentes e, portanto, três geradores de visualização para escrever.

4.11. O ficheiro de configuração config.php da aplicação web

<?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. Ações da aplicação web

4.12.1. Funcionamento geral dos scripts de ação

  • Um script de ação é chamado pelo controlador com base no parâmetro de ação que recebeu do cliente.
  • Após a execução, o script de ação deve indicar ao controlador o estado em que a aplicação deve ser colocada. Este estado deve ser especificado em $dSession['etat']['principal'].
  • Um script de ação pode querer armazenar informações na sessão. Para tal, coloca essas informações no dicionário $dSession, que é automaticamente guardado na sessão pelo controlador no final do ciclo de pedido-resposta.
  • Um script de ação pode conter informações a transmitir às visualizações. Este aspeto é independente do controlador. Trata-se da interface entre as ações e as visualizações, uma interface específica de cada aplicação. No exemplo aqui abordado, as ações fornecerão informações aos geradores de visualizações através de um dicionário denominado $dResponse.

4.12.2. A ação get:init

Esta é a ação que gera o formulário vazio. O ficheiro de configuração mostra que será tratada pelo script a-init.php:

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

O código do script a-init.php é o seguinte:

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

Este script define simplesmente o estado em que a aplicação deve estar — o estado [e-form] — em $dSession['etat']['principal'], e fornece uma descrição deste estado em $dSession['etat']['secondaire']. O ficheiro de configuração mostra-nos que o controlador irá executar o script e-formulaire.php para gerar a resposta para o cliente.

<?php

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

O script e-formulaire.php irá gerar a vista [v-formulaire] na sua variante [init], ou seja, o formulário vazio.

4.12.3. A ação post:calculateTax

Esta é a ação que calcula o imposto com base nos dados introduzidos no formulário. O ficheiro de configuração especifica que o script a-calculimpot.php irá tratar desta ação:

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

O código do script a-calculimpot.php é o seguinte:

<?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

O script faz o que é suposto fazer: calcular o imposto. Deixamos ao leitor a tarefa de decifrar o código de processamento. Estamos interessados nos estados que podem surgir como resultado desta ação:

  • os dados introduzidos estão incorretos ou existe um problema ao aceder aos dados: a aplicação é colocada no estado [e-errors]. O ficheiro de configuração mostra que o script e-errors.php será responsável por gerar a vista de resposta:
<?php

  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  • Em todos os outros casos, a aplicação é definida para o estado [e-form] com a variante de cálculo de impostos especificada em $dSession['state']['secondary']. O ficheiro de configuração indica que o script e-formulaire.php irá gerar a vista de resposta. Este irá utilizar o valor de $dSession['state']['secondary'] para gerar um formulário pré-preenchido com os valores introduzidos pelo utilizador e o montante do imposto.

4.12.4. A ação post:effacerformulaire

Está associada, por configuração, ao script a-init.php já descrito.

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

4.12.5. A ação get:return-form

Permite-lhe regressar ao estado [e-form] a partir do estado [e-errors]. O script a-retourformulaire.php trata desta ação:

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

O script a-retourformulaire.php é o seguinte:

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

Pedimos simplesmente que a aplicação seja definida para o estado [e-form] na sua variante [form-return]. O ficheiro de configuração mostra-nos que o controlador irá executar o script e-form.php para gerar a resposta para o cliente.

<?php

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

O script e-formulaire.php irá gerar a vista [v-formulaire] na sua variante [retourformulaire], ou seja, o formulário pré-preenchido com os valores introduzidos pelo utilizador, mas sem o montante do imposto.

4.13. Sequência de ações inválida

As ações válidas a partir de um determinado estado da aplicação são definidas pela configuração:

<?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'));

Já explicámos esta configuração. Se for detetada uma sequência inválida de ações, o script a-invalid-sequence.php é executado:

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

O código deste script é o seguinte:

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

Isto define a aplicação para o estado [e-errors]. Fornecemos informações em $dSession['state']['secondary'] que serão utilizadas pelo gerador da página de erro. Como já vimos, este gerador é o e-errors.php:

<?php

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

Analisaremos o código deste gerador mais tarde. A visualização enviada ao cliente é a seguinte:

Image

4.14. As visualizações da aplicação

4.14.1. Exibição da vista final

Vamos ver como o controlador envia a resposta ao cliente assim que a ação solicitada pelo cliente tiver sido executada:

<?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      

Ao regressar de um script de ação, o controlador recupera o estado em que deve colocar a aplicação a partir de $dSession['etat']['principal']. Este estado foi definido pela ação que acabou de ser executada. O controlador executa então o gerador de visualização associado a esse estado. Encontra o nome do gerador de visualização no ficheiro de configuração. A função do gerador de visualização é a seguinte:

  • Define o nome do modelo de resposta a ser utilizado em $dResponse['responseView']. Esta informação será passada para o controlador. Um modelo é uma composição de vistas básicas que, quando combinadas, formam a vista final.
  • Prepara as informações dinâmicas a serem exibidas na vista final. Esta etapa é independente do controlador. Funciona como a interface entre o gerador de vistas e a vista final. É específica para cada aplicação.
  • deve terminar com uma chamada à função finSession do controlador. Esta função irá
    • guardará a sessão
    • enviar a resposta

O código para a função finSession é o seguinte:

<?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      

A visualização enviada ao utilizador é definida pela entidade $dResponse['responseView'], que define o modelo a utilizar para a resposta final.

4.14.2. Modelo de resposta

A aplicação irá gerar as suas várias respostas com base no seguinte modelo único:

Este modelo está associado à chave «modele1» no dicionário $dConfig['vuesreponse']:

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

O script m-reponse.php é responsável por gerar este modelo:

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

Este script tem três elementos dinâmicos armazenados num dicionário $dReponse e associados às seguintes chaves:

  • urlstyle: URL da folha de estilos do modelo
  • view1: nome do script responsável por gerar a vista view1
  • vue2: nome do script responsável pela geração da vista vue2

Um gerador de vistas que pretenda utilizar o modelo modèle1 deve definir estes três elementos dinâmicos. Definimos agora as vistas básicas que podem substituir os elementos [vue1] e [vue2] no modelo.

4.14.3. A vista básica v-bandeau.php

O script v-bandeau.php gera uma vista que será colocada na área [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>    

O gerador de visualização deve definir dois elementos dinâmicos colocados num dicionário $dResponse e associados às seguintes chaves:


title: title to display
result: amount of tax due

4.14.4. A vista básica v-form.php

A secção [vue2] corresponde à vista [v-form] ou à vista [v-errors]. A vista [v-form] é gerada pelo script 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>                                            

As partes dinâmicas desta vista que devem ser definidas pelo gerador de vistas estão associadas às seguintes chaves no dicionário $dReponse:

  • optoui: estado do botão de opção denominado optoui
  • optnon: estado do botão de opção denominado optnon
  • children: número de filhos a colocar no campo txtenfants
  • salary: salário anual a ser colocado no campo txtsalary

4.14.5. A vista básica v-erreurs.php

A vista [v-errors] é gerada pelo script 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>

As partes dinâmicas desta vista a serem definidas pelo gerador de vistas estão associadas às seguintes chaves no dicionário $dReponse:

  • errors: matriz de mensagens de erro
  • info: mensagem informativa
  • link: texto de um link
  • href: URL de destino do link acima

4.14.6. A folha de estilo

Todas as visualizações são formatadas por uma folha de estilo. Para alterar a aparência visual da aplicação, modifique a sua folha de estilo. A seguinte folha de estilo 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. Geradores de visualização

4.15.1. Função de um gerador de vistas

Vamos rever o trecho de código do controlador que executa um gerador de visualizações:

<?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;

Um gerador de visualização está associado ao estado em que a aplicação será colocada. A ligação entre o estado e o gerador de visualização é definida através da configuração:

<?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'));

Já explicámos o papel de um gerador de visualizações. Vamos revê-lo aqui. Um gerador de visualizações:

  • define o nome do modelo de resposta a ser utilizado em $dResponse['responseView']. Esta informação será passada para o controlador. Um modelo é uma composição de vistas básicas que, quando combinadas, formam a vista final.
  • prepara as informações dinâmicas a serem exibidas na vista final. Esta etapa é independente do controlador. Funciona como a interface entre o gerador de vistas e a vista final. É específica para cada aplicação.
  • deve terminar com uma chamada à função finSession do controlador. Esta função irá
    • guardará a sessão
    • enviar a resposta

4.15.2. O gerador de visualizações associado ao estado [e-form]

O script responsável por gerar a vista associada ao estado [e-form] chama-se e-form.php:

<?php

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

O seu código é o seguinte:

<?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);
?>      

Note que o gerador de visualizações cumpre os requisitos para um gerador de visualizações:

  • definir o modelo de resposta a utilizar em $dResponse['vuereponse']
  • passe informações para este modelo. Aqui, elas são passadas através do dicionário $dResponse.
  • termine chamando a função finSession do controlador

Aqui, o modelo utilizado é «template1». Por conseguinte, o gerador de visualizações define as duas informações exigidas por este modelo: $dResponse['view1'] e $dResponse['view2'].

No caso específico da nossa aplicação, a vista associada ao estado [e-form] depende da informação armazenada na variável $dSession['etat']['secondaire']. Esta é uma escolha de design. Outra aplicação poderá optar por passar informação adicional de uma forma diferente. Além disso, aqui toda a informação necessária para apresentar a vista final é colocada no dicionário $dResponse. Mais uma vez, esta é uma escolha deixada ao critério do programador. O estado [e-form] pode ocorrer após quatro ações diferentes: init, calculateTax, returnForm, clearForm. A vista a ser apresentada não é exatamente a mesma em todos os casos. Por isso, distinguimos aqui três casos em $dSession['state']['secondary']:

  • init: o formulário é apresentado vazio
  • calculateTax: o formulário é apresentado com o montante do imposto e os dados utilizados para o calcular
  • returnform: o formulário é apresentado com os dados inicialmente introduzidos

Acima, o script e-formulaire.php utiliza esta informação para apresentar a resposta de acordo com estas três variantes.

4.15.3. A vista associada ao estado [e-errors]

O script responsável por gerar a vista associada ao estado [e-errors] chama-se e-errors.php e é o seguinte:

<?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. Exibição da vista final

Ambos os scripts que geram as duas visualizações finais terminam com uma chamada à função finSession do controlador:

<?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      

A vista enviada ao utilizador é definida pela entidade $dResponse['responseView'], que especifica o modelo a utilizar para a resposta final. Para os estados [e-form] e [e-errors], este modelo foi definido como template1:

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

Este modelo corresponde ao script m-reponse.php:

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

4.16. Modificar o modelo de resposta

Aqui, partimos do princípio de que decidimos alterar a aparência visual da resposta enviada ao cliente e estamos interessados em compreender as implicações que isso tem no código.

4.16.1. O novo modelo

A estrutura da resposta será agora a seguinte:

Este modelo será denominado template2, e o script responsável pela sua geração será denominado m-response2.php:

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

O script correspondente a este modelo é o seguinte:

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

Os elementos dinâmicos do modelo são os seguintes:

  • $dReponse['urlstyle']: a folha de estilo a utilizar
  • $dReponse['vue1']: o script a utilizar para gerar [vue1]
  • $dReponse['vue2']: o script a utilizar para gerar [vue2]
  • $dReponse['vue3']: o script a utilizar para gerar [vue3]

Estes elementos devem ser definidos pelos geradores de visualização.

4.16.2. As diferentes páginas de resposta

A aplicação irá agora apresentar as seguintes respostas ao utilizador. Na chamada inicial, a página de resposta será a seguinte:

Se o utilizador fornecer dados válidos, o imposto é calculado:

Se cometer erros na introdução de dados, é apresentada a página de erro:

Se utilizarem o link [Voltar ao formulário de introdução de dados], verão o formulário tal como o enviaram pela última vez:

Se, no cenário acima, utilizarem o link [Redefinir formulário], verão um formulário vazio:

Note que a aplicação utiliza as mesmas ações que anteriormente. Apenas a apresentação das respostas mudou.

4.16.3. Visualizações básicas

A vista básica [view1] será associada ao script v-bandeau.php, tal como no exemplo anterior:

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

Esta visualização tem dois elementos dinâmicos:

  • $dResponse['title']: título a apresentar
  • $dResponse['resultat']: montante do imposto devido

A vista básica [vue2] será associada ao seguinte script 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>

Esta vista tem os seguintes elementos dinâmicos:

  • $dResponse['links']: matriz de links a apresentar em [view2]. Cada elemento da matriz é um dicionário com duas chaves:
    • 'url': o URL de destino do link
    • 'text': texto do link

A vista básica [view3] será associada ao script v-form2.php se pretender apresentar o formulário de entrada, ou ao script v-errors2.php se pretender apresentar a página de erro. O código para o script v-form2.php é o seguinte:

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

As partes dinâmicas desta vista que devem ser definidas pelo gerador de vistas estão associadas às seguintes chaves no dicionário $dResponse:

  • optoui: estado do botão de opção denominado optoui
  • optnon: estado do botão de opção denominado optnon
  • children: número de filhos a colocar no campo txtenfants
  • salaire: salário anual a ser colocado no campo txtsalaire

O script que gera a página de erro chama-se v-erreurs2.php. O seu código é o seguinte:

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>

As partes dinâmicas desta vista a serem definidas pelo gerador de vistas estão associadas às seguintes chaves no dicionário $dReponse:


errors: array of error messages
info: information message

4.16.4. A folha de estilo

Não sofreu alterações. Continua a ser style1.css.

4.16.5. O novo ficheiro de configuração

Para implementar estas novas visualizações, precisamos de modificar certas linhas no ficheiro de configuração.

<?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"
    );
?>

A alteração principal envolve a atualização dos geradores de visualização associados aos relatórios [e-form] e [e-errors]. Depois de concluída esta tarefa, os novos geradores de visualização ficam responsáveis pela geração das novas páginas de resposta.

4.16.6. O gerador de visualização associado ao relatório [e-form]

No ficheiro de configuração, o estado [e-form] está agora associado ao gerador de visualizações e-form2.php. O código para este script é o seguinte:

<?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);
?>  

As principais alterações são as seguintes:

  • O gerador de visualização indica que pretende utilizar o modelo de resposta modele2
  • por esse motivo, preenche os elementos dinâmicos $dResponse['vue1'], $dResponse['vue2'], $dResponse['vue3'], sendo que todos os três são exigidos pelo modelo de resposta modele2.
  • O gerador de visualizações também preenche o elemento dinâmico $dResponse['links'], que define os links a serem exibidos na área [view2] da resposta.

4.16.7. O gerador de visualização associado ao estado [e-errors]

No ficheiro de configuração, o estado [e-errors] está agora associado ao gerador de visualização e-errors2.php. O código para este script é o seguinte:

<?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);
?>  

As alterações efetuadas são idênticas às realizadas no gerador de visualização e-formulaire2.php.

4.17. Conclusão

Conseguimos demonstrar, através de um exemplo, as vantagens do nosso controlador genérico. Não tivemos de o escrever nós próprios. Simplesmente escrevemos os scripts de ação, os geradores de visualização e as visualizações para a aplicação. Também demonstrámos as vantagens de separar as ações das visualizações. Isto permitiu-nos alterar a aparência das respostas sem modificar uma única linha de código nos scripts de ação. Apenas os scripts envolvidos na geração de visualizações foram modificados. Para que isto seja possível, o script de ação não deve fazer suposições sobre a vista que irá apresentar a informação que calculou. Deve simplesmente devolver esta informação ao controlador, que a passa ao gerador de vistas para a formatar. Esta é uma regra absoluta: uma ação deve estar completamente dissociada das vistas.

Neste capítulo, explorámos a filosofia Struts, bem conhecida dos programadores Java. Um projeto de código aberto chamado php.mvc permite o desenvolvimento web/PHP utilizando a filosofia Struts. Visite http://www.phpmvc.net/ para mais informações.