Skip to content

4. Exemplos

4.1. Aplicação Impostos: Introdução

Apresentamos aqui a aplicação IMPOTS, que será utilizada em várias ocasiões daqui em diante. Trata-se de uma aplicação que permite calcular o imposto de um contribuinte. Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:

  • calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants é o número de filhos que tem.
  • se tiver pelo menos três filhos, tem mais meia quota
  • calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
  • calcula-se o seu coeficiente familiar QF = R / nbParts
  • calcula-se o seu imposto I. Consideremos a seguinte tabela:
12620,0
0
0
13 190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31 810
0,2
3309,5
39 970
0,25
4900
48 360
0,3
6898,5
55 790
0,35
9316,5
92 970
0,4
12106
127 860
0,45
16 754,5
151 250
0,50
23 147,5
172 040
0,55
30710
195 000
0,60
39312
0
0,65
49062

Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 23000, encontrar-se-á a linha

    24740        0.15        2072.5

O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então são utilizados os coeficientes da última linha. Neste caso:

    0                0.65        49062

o que resulta no imposto I = 0,65*R - 49062*nbParts.

Os dados que definem as diferentes faixas de imposto encontram-se numa base de dados ODBC-MySQL. O MySQL é um SGBD de domínio público que pode ser utilizado em várias plataformas, incluindo o Windows e o Linux. Com este SGBD, foi criada uma base de dados dbimpots que contém uma única tabela denominada impots. O acesso à base de dados é controlado por um nome de utilizador e palavra-passe, neste caso admimpots/mdpimpots. A captura de ecrã seguinte mostra como utilizar a base de dados dbimpots com o MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use dbimpots;
Database changed

mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)

mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)

mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   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 |
+---------+--------+---------+
14 rows in set (0.00 sec)

mysql>quit

A base de dados dbimpots é transformada na fonte de dados ODBC da seguinte forma:

  • é iniciado o gestor de fontes de dados ODBC de 32 bits

Image

  • utiliza-se o botão [Add] para adicionar uma nova fonte de dados ODBC

Image

  • seleciona-se o controlador MySQL e executa-se [Terminer]
  • o controlador MySQL solicita uma série de informações:
1
o nome DSN a atribuir à fonte de dados ODBC — pode ser qualquer um
2
a máquina na qual o SGBD MySQL está a ser executado — neste caso, localhost. É importante referir que a base de dados pode ser uma base de dados remota. As aplicações locais que utilizam a fonte de dados ODBC não se aperceberiam disso. Seria esse o caso, nomeadamente, da nossa aplicação PHP.
3
a base de dados MySQL a utilizar. MySQL é um SGBD que gere bases de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, indica-se o nome da base de dados gerida.
4
o nome de um utilizador com direitos de acesso a esta base de dados
5
a sua palavra-passe

4.2. Aplicação Impostos: a classe ImpotsDSN

A nossa aplicação basear-se-á na classe PHP ImpotsDSN, que terá os seguintes atributos, construtor e método:

<?php
...
     // atributos
    var $limites;        // tabela de limites
    var $coeffR;        // tabela de coeffR
    var $coeffN;        // tabela de coeffN
    var $erreurs;        // tabela de erros
     // fabricante
    function ImpotsDSN($impots){...}
     // método
    function calculer($personne){...}
  • Vimos na exposição do problema que precisávamos de três conjuntos de dados
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31 810
0,2
3309,5
39 970
0,25
4900
48 360
0,3
6898,5
55 790
0,35
9316,5
92 970
0,4
12106
127 860
0,45
16 754,5
151 250
0,50
23 147,5
172 040
0,55
30710
195 000
0,60
39312
0
0,65
49062

A primeira coluna será colocada no atributo $limites da classe ImotsDSN, a segunda no atributo $coeffR e a terceira no $coeffN. Estes três atributos são inicializados pelo construtor da classe. Este irá buscar os dados numa fonte de dados ODBC. Podem ocorrer vários erros durante a construção do objeto. Estes erros são relatados no atributo $erreurs sob a forma de uma tabela de mensagens de erro.

  • O construtor recebe como parâmetro um dicionário $impots com os seguintes campos:
// dsn: nome DSN da fonte de dados ODBC que contém os valores das tabelas de limites, coeffR, coeffN
// user: nome de um utilizador com direitos de leitura na fonte ODBC
// pwd: a sua palavra-passe
// tabela: nome da tabela que contém os valores (limites, coeffR, coeffN)
// limites: nome da coluna que contém os valores-limite
// coeffR: nome da coluna que contém os valores coeffR       
// coeffN: nome da coluna que contém os valores coeffN

O parâmetro do construtor fornece todas as informações necessárias para ler os dados na fonte de dados ODBC

  • assim que o objeto ImpotsDSN for criado, os utilizadores da classe poderão chamar o método calculerImpots do objeto. Este recebe como parâmetro um dicionário $personne:
       // $pessoa["marié"]: sim, não
       // $pessoa["enfants"]: número de filhos
       // $pessoa["salaire"]: salário anual

A classe completa é a seguinte:

<?php
   // definição de uma classe objImpots
  class ImpotsDSN{

     // atributos: as 3 tabelas de dados
    var $limites;        // tabela de limites
    var $coeffR;        // tabela de coeffR
    var $coeffN;        // tabela de coeffN
    var $erreurs;        // tabela de erros

     // fabricante
    function ImpotsDSN($impots){
       // $impots: dicionário que contém os seguintes campos
       // dsn: nome DSN da fonte de dados ODBC que contém os valores das tabelas de limites, coeffR, coeffN
       // user: nome de um utilizador com direitos de leitura na fonte ODBC
       // pwd: a sua palavra-passe
       // tabela: nome da tabela que contém os valores (limites, coeffR, coeffN)
       // limites: nome da coluna que contém os valores-limite
       // coeffR: nome da coluna que contém os valores coeffR       
       // coeffN: nome da coluna que contém os valores coeffN

       // inicialmente sem erros
      $this->erreurs=array();

             // verificação da chamada
      if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
          ! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
         // erro
        $this->erreurs[]="Appel incorrect";
         // fim
        return;
      }//if

       // abertura da base de dados DSN
      $connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
       // erro?
      if(! $connexion){
         // erro
        $this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
         // fim
        return;
      }//if

             // envio de uma consulta à base de dados
      $requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
            if(! odbc_execute($requête)){
         // erro
        $this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
         // fim
        odbc_close($connexion);
        return;
      }//se

       // análise dos resultados da consulta
      $this->limites=array();
      $this->coeffR=array();
      $this->coeffN=array();
      while(odbc_fetch_row($requête)){     
          // mais uma linha
        $this->limites[]=odbc_result($requête,$impots[limites]);
        $this->coeffR[]=odbc_result($requête,$impots[coeffR]);
        $this->coeffN[]=odbc_result($requête,$impots[coeffN]);
      }//enquanto

       // fecho da base
      odbc_close($connexion);
    }//construtor                

    // --------------------------------------------------------------------------
    function calculer($personne){
       // $pessoa["marié"]: sim, não
       // $pessoa["enfants"]: número de filhos
       // $pessoa["salaire"]: salário anual

       // o objeto está em bom estado?
      if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;

            // verificação da chamada
      if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
         // erro
        $this->erreurs[]="Appel incorrect";
         // fim
        return -1;
      }//if

       // Os parâmetros estão corretos?
      $personne[marié]=strtolower($personne[marié]);
      if($personne[marié]!=oui && $personne[marié]!=non){
           // erro
        $this->erreurs[]="Statut marital [$personne[marié]] incorrect";
      }//if
      if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
          // erro
        $this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
      }//if
      if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
          // erro
        $this->erreurs[]="salaire [$personne[salaire]] incorrect";
      }//if
       // erros?
      if(count($this->erreurs)!=0) return -1;

      // número de porções
      if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
        else $nbParts=$personne[enfants]/2+1;
       // mais meia quota se houver pelo menos 3 filhos
      if($personne[enfants]>=3) $nbParts+=0.5;
       // rendimento tributável
      $revenuImposable=0.72*$personne[salaire];
       // quociente familiar
      $quotient=$revenuImposable/$nbParts;
       // é colocado no final da tabela de limites para interromper o ciclo seguinte
      $this->limites[$this->nbLimites]=$quotient;
       // cálculo do imposto
      $i=0;
      while($quotient>$this->limites[$i]) $i++;
      // uma vez que se colocou $quotient no final da tabela $limites, o ciclo anterior
       // não pode ultrapassar os limites da tabela $limites
       // agora já é possível calcular o imposto
      return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
    }//calculImpots
  }//classe de impostos
?>

Um programa de teste poderia ser o seguinte:

<?php
     // bibliotecas
  include "ImpotsDSN.php";

     // criação de um objeto de impostos
  $conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

  $objImpots=new ImpotsDSN($conf);

  // erros?
  if(count($objImpots->erreurs)!=0){
       // mensagem
    echo "Les erreurs suivantes se sont produites :\n";
    $erreurs=$objImpots->erreurs;
    for($i=0;$i<count($erreurs);$i++){
        echo "$erreurs[$i]\n";
    }//para
    // fim
    exit(1);
  }//se

   // testes
  calculerImpots($objImpots,oui,2,200000);
  calculerImpots($objImpots,non,2,200000);
  calculerImpots($objImpots,oui,3,200000);
  calculerImpots($objImpots,non,3,200000);
  calculerImpots($objImpots,array(),array(),array());

  // fim
  exit(0);

  // -----------------------------------------------------------
  function calculerImpots($objImpots,$marié,$enfants,$salaire){
       // echo
        echo "impots($marié,$enfants,$salaire)\n";    

       // cálculo do imposto
    $personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);        
      $montant=$objImpots->calculer($personne);
     // erros?
    if(count($objImpots->erreurs)!=0){
         // mensagem
      echo "Les erreurs suivantes se sont produites :\n";
      $erreurs=$objImpots->erreurs;
      for($i=0;$i<count($erreurs);$i++){
          echo "$erreurs[$i]\n";
      }//para
    }else echo "montant=$montant\n";
  }//calculerImp
  • o programa de teste encarrega-se de «incluir» o ficheiro que contém a classe ImpotsDSN
  • e, em seguida, cria um objeto objImpots da classe ImpotsDSN, passando ao construtor do objeto as informações de que este necessita
  • assim que este objeto for criado, o método calculer do mesmo será chamado cinco vezes

Os resultados obtidos são os seguintes:


dos>e:\php43\php.exe test.php
impots(oui,2,200000)
montant=22504
impots(non,2,200000)
montant=33388
impots(oui,3,200000)
montant=16400
impots(non,3,200000)
montant=22504
impots(Array,Array,Array)
Les erreurs suivantes se sont produites :
Statut marital [array] incorrect
Nombre d'enfants [Array] incorrect
salaire [Array] incorrect

A partir de agora, utilizaremos a classe ImpotsDSN sem voltar a apresentar a sua definição.

4.3. Aplicação Impostos: versão 1

Apresentamos agora a versão 1 da aplicação IMPOTS. Estamos no contexto de uma aplicação web que apresentaria uma interface HTML a um utilizador, a fim de obter os três parâmetros necessários para o cálculo do imposto:

  • o estado civil (casado ou solteiro)
  • o número de filhos
  • o salário anual

Image

A apresentação do formulário é efetuada pela seguinte página PHP:

<?php //formulário de impostos ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
           // limpeza do formulário
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//com
      }//apagar      
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                <input type="radio" name="optMarie" value="oui" <?php echo $requête->chkoui ?>>oui
              <input type="radio" name="optMarie" value="non" <?php echo $requête->chknon ?>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête->enfants ?>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête->salaire ?>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<?php echo $requête->impots ?>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculer"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <?php
         // Existem erros?
      if(count($requête->erreurs)!=0){
           // exibição de erros
        echo "<hr>\n<font color=\"red\">\n";
        echo "Les erreurs suivantes se sont produites<br>";
        echo "<ul>";
        for($i=0;$i<count($requête->erreurs);$i++){
            echo "<li>".$requête->erreurs[$i]."</li>\n";
        }
        echo "</ul>\n</font>\n";
      }//se 
   ?>
 </body>
</html>

A página PHP limita-se a apresentar as informações que lhe são transmitidas pelo programa principal da aplicação na variável $requête, que é um objeto com os seguintes campos:

chkoui, chknon
atributos dos botões de opção «sim» e «não» — têm como valores possíveis "checked" ou "", para ativar ou desativar o botão de opção correspondente
enfants
o número de filhos do contribuinte
salaire
o seu salário anual
impots
o montante do imposto a pagar
erreurs
uma eventual lista de erros — pode estar vazia.

A página enviada ao cliente contém um script JavaScript com uma função effacer associada ao botão «Effacer», cuja função é repor o formulário ao seu estado inicial: botão desmarcado, campos de preenchimento vazios. Note-se que este resultado não poderia ser obtido com um botão HTML do tipo «reset». Com efeito, quando este tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. Ora, na nossa aplicação, o navegador recebe formulários que podem não estar vazios.

A aplicação que processa o formulário anterior é a seguinte:

<?php
     // processa o formulário de impostos

     // bibliotecas
  include "ImpotsDSN.php";

  // configuração da aplicação
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  $formulaireImpots="impots_form.php";
  $erreursImpots="impots_erreurs.php";
  $bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

   // recuperam-se os parâmetros
  $requête->marié=$_POST["optMarie"];
  $requête->enfants=$_POST["txtEnfants"];
  $requête->salaire=$_POST["txtSalaire"];

   // temos todos os parâmetros?
  if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
       // preparação do formulário em branco
    $requête->chkoui="";
    $requête->chknon="checked";
    $requête->enfants="";
    $requête->salaire="";
    $requête->impots="";
    $requête->erreurs=array();
     // exibição do formulário
    include $formulaireImpots;
     // fim
    exit(0);
  }//if

   // verificação dos parâmetros
  $requête=vérifier($requête);

   // erros?
  if(count($requête->erreurs)!=0){
       // exibição do formulário
    include "$formulaireImpots";
     // fim
    exit(0);
  }//if

   // cálculo da resposta
  $requête=calculerImpots($bdImpots,$requête);

   // erros?
  if(count($requête->erreurs)!=0){
       // exibição do formulário de erros
    include "$erreursImpots";
     // fim
    exit(0);
  }//if

   // exibição do formulário
  include "$formulaireImpots";
   // fim
  exit(0);

  // --------------------------------------------------------
  function vérifier($requête){
       // verifica a validade dos parâmetros da consulta

     // inicialmente sem erros
    $requête->erreurs=array();

     // estado válido?
    $requête->marié=strtolower($requête->marié);
    if($requête->marié!="oui" && $requête->marié!="non"){
         // um erro
      $requête->erreurs[]="Statut marital [$requête->marié] incorrect";
    }

     // número de filhos válido?
    if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
        // um erro
      $requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
    }

     // salário válido?
    if(! preg_match("/^\s*\d+\s*$/",$requête->salaire)){
        // erro
      $requête->erreurs[]="Salaire [$requête->salaire] incorrect";
    }

     // estado dos botões de opção
    if($requête->marié=="oui"){
        $requête->chkoui="checked";
      $requête->chknon="";
    }else{
        $requête->chknon="checked";
      $requête->chkoui="";
    }

     // enviar a consulta
    return $requête;
  }//verificar

  // --------------------------------------------------------
  function calculerImpots($bdImpots,$requête){
       // calcula o montante do imposto

     // $bdImpots: dicionário que contém as informações necessárias para ler a fonte de dados ODBC
     // $requête: a consulta que contém as informações sobre o contribuinte

     // constrói-se um objeto ImpotsDSN 
    $objImpots=new ImpotsDSN($bdImpots);

    // erros?
    if(count($objImpots->erreurs)!=0){
         // colocam-se os erros na consulta
      $requête->erreurs=$objImpots->erreurs;
      // concluído
      return $requête;
    }//if

    // cálculo do imposto
    $personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
        $requête->impots=$objImpots->calculer($personne);

     // retornamos o resultado
    return $requête;
  }//calculerImpots

Comentários:

  • Em primeiro lugar, a classe ImpotsDSN está incluída. É esta classe que serve de base ao cálculo dos impostos e que nos irá ocultar o acesso à base de dados
  • São efetuadas algumas inicializações com o objetivo de facilitar a manutenção da aplicação. Se os parâmetros se alterarem, os seus valores serão alterados nesta secção. Em geral, estes valores de configuração são normalmente armazenados num ficheiro ou numa base de dados.
  • Recuperam-se os três parâmetros do formulário correspondentes aos campos HTML, optMarie, txtEnfants e txtSalaire.
  • Estes parâmetros podem estar total ou parcialmente ausentes. Este será, nomeadamente, o caso aquando do pedido inicial do formulário. Nesse caso, limita-se a enviar um formulário vazio.
  • Em seguida, verifica-se a validade dos três parâmetros recuperados. Se estes se revelarem incorretos, o formulário é reenviado tal como foi introduzido, mas acompanhado de uma lista de erros.
  • Uma vez verificada a validade dos três parâmetros, é possível efetuar o cálculo do imposto. Este é calculado pela função calculerImpots. Esta função cria um objeto do tipo ImpotsDSN e utiliza o método calculer desse objeto.
  • A criação do objeto ImpotsDSN pode falhar se a base de dados estiver indisponível. Nesse caso, a aplicação apresenta uma página de erro específica indicando os erros que ocorreram.
  • Se tudo correr bem, o formulário é devolvido tal como foi preenchido, mas com a adição do montante do imposto a pagar.

A página de erros é a seguinte:

<?php    // página de erro ?>
<html>
    <head>
      <title>Application impôts indisponible</title>
  </head>
  <body background="/poly/impots/images/standard.jpg">
      <h3>Calcul d'impôts</h3>
    <hr>
    Application indisponible. Recommencez ultérieurement.<br><br>
    <font color="red">
           Les erreurs suivantes se sont produites<br>
        <ul>
      <?php
           // exibição de erros
        for($i=0;$i<count($requête->erreurs);$i++){
            echo "<li>".$requête->erreurs[$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Eis alguns exemplos de erros. Em primeiro lugar, introduz-se uma palavra-passe incorreta na base de dados:

Image

Introduzem-se dados incorretos no formulário:

Image

Por fim, introduzem-se valores corretos:

Image

4.4. Aplicação Impostos: versão 2

No exemplo anterior, a validade dos parâmetros txtEnfants e txtSalaire do formulário é verificada pelo servidor. Propõe-se aqui verificá-la através de um script JavaScript incluído na página do formulário. É então o navegador que efetua a verificação dos parâmetros. O servidor só é solicitado se estes forem válidos. Desta forma, poupa-se «largura de banda». A página de visualização impots-form.php passa a ter o seguinte aspeto:

<?php //formulário de impostos ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
........
      }//apagar

      //------------------------------
      function calculer(){
           // verificação dos parâmetros antes de os enviar para o servidor
        with(document.frmImpots){
          //número de filhos
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // o modelo não foi verificado
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
           //salário
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // o modelo não foi verificado
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//se
          // Está tudo bem — enviamos o formulário para o servidor
          submit();
        }//com
      }//calcular            
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
............
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
........
 </body>
</html>

De notar as seguintes alterações:

  • o botão Calculer já não é do tipo submit, mas sim do tipo button, associado a uma função denominada calculer. É esta função que irá analisar os campos txtEnfants e txTsalaire. Se estiverem corretos, os valores do formulário serão enviados para o servidor (submit); caso contrário, será exibida uma mensagem de erro.

Eis um exemplo de exibição em caso de erro:

Image

4.5. Aplicação de Impostos: versão 3

Alteramos ligeiramente a aplicação para introduzir o conceito de sessão. Consideramos agora que a aplicação é uma simulação de cálculo de impostos. Um utilizador pode, assim, simular diferentes «configurações» de contribuinte e ver qual seria, para cada uma delas, o imposto a pagar. A página web abaixo apresenta um exemplo do que poderia ser obtido:

Image

O programa de visualização da página acima é o seguinte:

<?php //formulário de impostos ?>

<html>
    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...
      }//apagar

      //------------------------------
      function calculer(){
...
      }//calcular            
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                <input type="radio" name="optMarie" value="oui" <?php echo $requête[chkoui] ?>>oui
              <input type="radio" name="optMarie" value="non" <?php echo $requête[chknon] ?>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête[enfants] ?>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête[salaire] ?>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<?php echo $requête[impots] ?>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <?php
         // existem erros?
      if(count($requête[erreurs])!=0){
           // exibição de erros
        echo "<hr>\n<font color=\"red\">\n";
        echo "Les erreurs suivantes se sont produites<br>";
        echo "<ul>";
        for($i=0;$i<count($requête[erreurs]);$i++){
            echo "<li>".$requête[erreurs][$i]."</li>\n";
        }
        echo "</ul>\n</font>\n";
      }//if 
         // existem simulações?
      else if(count($requête[simulations])!=0){
          // exibição das simulações
        echo "<hr>\n<h2>Résultats des simulations</h2>\n";
        echo "<table border=\"1\">\n";
        echo "<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>\n";
        for($i=0;$i<count($requête[simulations]);$i++){
            echo "<tr>".
              "<td>".$requête[simulations][$i][0]."</td>".
              "<td>".$requête[simulations][$i][1]."</td>".
              "<td>".$requête[simulations][$i][2]."</td>".
              "<td>".$requête[simulations][$i][3]."</td>".
            "</tr>\n";                                    
        }//for
        echo "</table>\n";
      }//if 
   ?>
 </body>
</html>

O programa de visualização apresenta pequenas diferenças em relação à sua versão anterior:

  • o dicionário recebe um dicionário $requête em vez de um objeto $requête. Por isso, escreve-se $requête[enfants] e não $requête->filhos.
  • Este dicionário tem um campo simulations, que é uma matriz bidimensional. $requête[simulations][i] é a simulação n.º i. Esta é, por sua vez, uma matriz de quatro cadeias de caracteres: estado civil, número de filhos, salário anual, imposto a pagar.

O programa principal, por sua vez, sofreu alterações mais profundas:

<?php
     // processa o formulário de impostos

     // bibliotecas
  include "ImpotsDSN.php";

   // início da sessão
  session_start();

   // configuração da aplicação
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  $formulaireImpots="impots_form.php";
  $erreursImpots="impots_erreurs.php";
  $bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
      table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);

     // recuperar os parâmetros da sessão
  $session=$_SESSION["session"];
   // sessão válida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // inicia-se uma nova sessão
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // Existem erros?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // exibição da página de erros
        include $erreursImpots;
       // fim
      $session=array();
      terminerSession($session);
    }//se
  }//se

  // recuperam-se os parâmetros da troca em curso
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // temos todos os parâmetros?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // exibição do formulário vazio
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fim
    terminerSession($session);
  }//if

   // verificação dos parâmetros
  $requête=vérifier($requête);

   // há erros?
  if(count($requête[erreurs])!=0){
       // exibição do formulário
    include "$formulaireImpots";
     // fim
    terminerSession($session);
  }//if

   // cálculo do imposto a pagar
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));

   // mais uma simulação
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // exibição do formulário
  include "$formulaireImpots";

   // fim
  terminerSession($session);

  // --------------------------------------------------------
  function vérifier($requête){
       // verifica a validade dos parâmetros da consulta

     // Inicialmente, sem erros
    $requête[erreurs]=array();

     // estado válido?
    $requête[marié]=strtolower($requête[marié]);
    if($requête[marié]!="oui" && $requête[marié]!="non"){
         // um erro
      $requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
    }

     // número de filhos válido?
    if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
        // um erro
      $requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
    }

     // salário válido?
    if(! preg_match("/^\s*\d+\s*$/",$requête[salaire])){
        // erro
      $requête[erreurs][]="Salaire [$requête[salaire]] incorrect";
    }

     // estado dos botões de opção
    if($requête[marié]=="oui"){
        $requête[chkoui]="checked";
      $requête[chknon]="";
    }else{
        $requête[chknon]="checked";
      $requête[chkoui]="";
    }

     // enviar a consulta
    return $requête;
  }//verificar

  // --------------------------------------------------------
  function terminerSession($session){
       // guardamos a sessão
    $_SESSION[session]=$session;
     // fim do script
    exit(0);
  }//terminerSession
  • Em primeiro lugar, esta aplicação gere uma sessão. Por isso, inicia-se uma sessão no início do script:
  session_start();
  • A sessão armazena uma única variável: o dicionário $session. Este dicionário tem dois campos:
    • objImpots: um objeto ImpotsDSN. Este objeto é armazenado na sessão do cliente para evitar acessos sucessivos desnecessários à base de dados ODBC.
    • simulações: a tabela de simulações para reter, de uma transação para outra, as simulações das transações anteriores.
  • Assim que a sessão for iniciada, recupera-se a variável $session. Se esta ainda não existir, significa que a sessão está a ser iniciada. Cria-se então um objeto ImpotsDSN e uma tabela de simulações vazia. Se necessário, são sinalizados erros.
<?php
...
     // recuperar os parâmetros da sessão
  $session=$_SESSION["session"];
   // sessão válida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // inicia-se uma nova sessão
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // Existem erros?
    if(count($session[objImpots]->erreurs)!=0){
      $requête=array(erreurs=>$session[objImpots]->erreurs);
         // exibição da página de erros
        include $erreursImpots;
       // fim
      $session=array();
      terminerSession($session);
    }//se
  }//if
  • Assim que a sessão for corretamente inicializada, os parâmetros da troca são recuperados. Se não estiverem presentes todos os parâmetros esperados, é enviado um formulário vazio.
<?php
...
   // recuperam-se os parâmetros da troca em curso
  $requête[marié]=$_POST["optMarie"];
  $requête[enfants]=$_POST["txtEnfants"];
  $requête[salaire]=$_POST["txtSalaire"];

   // temos todos os parâmetros?
  if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
       // exibição do formulário vazio
    $requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
            erreurs=>array(),simulations=>array());    
    include $formulaireImpots;
     // fim
    terminerSession($session);
  }//if
  • Se todos os parâmetros estiverem corretos, são verificados. Se houver erros, estes são assinalados:
<?php
...
   // verificação dos parâmetros
  $requête=vérifier($requête);

   // há erros?
  if(count($requête[erreurs])!=0){
       // exibição do formulário
    include "$formulaireImpots";
     // fim
    terminerSession($session);
  }//if
  • Se os parâmetros estiverem corretos, o imposto é calculado:
<?php
...
   // cálculo do imposto a pagar
  $requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
        enfants=>$requête[enfants],salaire=>$requête[salaire]));
  • A simulação em curso é adicionada à tabela de simulações, que é enviada ao cliente juntamente com o formulário:
<?php
...
   // mais uma simulação
  $session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
  $requête[simulations]=$session[simulations];

   // exibição do formulário
  include "$formulaireImpots";

   // fim
  terminerSession($session);
  • Em todos os casos, o script termina gravando a variável $session na sessão. Isto é feito no procedimento terminerSession.
<?php
...

  function terminerSession($session){
       // guardar a sessão
    $_SESSION[session]=$session;
     // fim do script
    exit(0);
  }//terminerSession

Concluímos apresentando o programa de visualização de erros de acesso à base de dados (impots-erreurs.php):

<?php    // página de erro ?>
<html>
    <head>
      <title>Application impôts indisponible</title>
  </head>
  <body background="/poly/impots/images/standard.jpg">
      <h3>Calcul d'impôts</h3>
    <hr>
    Application indisponible. Recommencez ultérieurement.<br><br>
    <font color="red">
           Les erreurs suivantes se sont produites<br>
        <ul>
      <?php
           // exibição de erros
        for($i=0;$i<count($requête[erreurs]);$i++){
            echo "<li>".$requête[erreurs][$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Também aqui, o objeto $requête foi substituído por um dicionário $requête.

4.6. Aplicação Impostos: versão 4

Vamos agora criar uma aplicação autónoma que será um cliente web da aplicação web impots anterior. A aplicação será uma aplicação de consola iniciada a partir de uma janela DOS:


dos>e:\php43\php.exe cltImpots.php
Syntaxe cltImpots.php urlImpots marié enfants salaire [jeton]

Os parâmetros do cliente web cltImpots são os seguintes:

     // sintaxe $0 urlImpots casado filhos salário token
   // cliente de um serviço fiscal que funciona em urlImpots
   // envia a este serviço as três informações: casado, filhos, salário
   // eventualmente com o token de sessão, caso este tenha sido transmitido
   // exibe a tabela de simulações da sessão

Eis alguns exemplos de utilização. Em primeiro lugar, um primeiro exemplo sem token de sessão.

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php sim 2 200000

Jeton de session=[a6297317667bc981c462120987b8dd18]

Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]

Recuperámos corretamente o montante do imposto a pagar (22504 f). Também recuperámos o token de sessão. Podemos agora utilizá-lo para uma segunda consulta:

dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php sim 3 200000 a6297317667bc981c462120987b8dd18

Jeton de session=[a6297317667bc981c462120987b8dd18]

Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
[oui,3,200000,16400]

Conseguimos, de facto, obter a tabela das simulações enviadas pelo servidor web. O token recuperado mantém-se, naturalmente, o mesmo. Se consultarmos o serviço web quando a base de dados ainda não foi iniciada, obtemos o seguinte resultado:

dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php sim 3 2 000 000

Jeton de session=[8369014d5053212bc42f64bbdfb152ee]

Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)

Ao programar um cliente web, é necessário saber exatamente o que o servidor envia em resposta às diferentes solicitações possíveis de um cliente. O servidor envia um conjunto de linhas HTML que inclui informações úteis e outras que servem apenas para a formatação HTML. As expressões regulares de PHP podem ajudar-nos a identificar as informações úteis no fluxo de linhas enviadas pelo servidor. Para tal, precisamos de conhecer o formato exato das diferentes respostas do servidor. Para isso, podemos consultar o serviço web através de um navegador e analisar o código-fonte que foi enviado. Note-se que este método não permite conhecer os cabeçalhos HTTP da resposta do servidor web. Por vezes, é útil conhecê-los. Nesse caso, podemos utilizar um dos dois clientes web genéricos apresentados no capítulo anterior.

Se repetirmos os exemplos anteriores, eis o código HTML recebido pelo navegador para a tabela de simulações:

<h2>Résultats des simulations</h2>
<table border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
<tr><td>oui</td><td>3</td><td>200000</td><td>16400</td></tr>
</table>

Trata-se de uma tabela HTML, a única presente no documento enviado. Assim, a expressão regular "|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|" deve permitir localizar no documento as diferentes simulações recebidas. Quando a base de dados está indisponível, recebe-se o seguinte documento:

    Application indisponible. Recommencez ultérieurement.<br><br>
    <font color="red">
           Les erreurs suivantes se sont produites<br>
        <ul>
      <li>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</li>
        </ul>
    </font>

Para recuperar o erro, o cliente web deverá procurar na resposta do servidor web as linhas que correspondam ao modelo: "|<li>(.+?)</li>|".

Temos agora as orientações sobre o que fazer quando o utilizador solicitar o cálculo do imposto a partir do cliente de consola anterior:

  • verificar se todos os parâmetros são válidos e, se necessário, sinalizar os erros.
  • ligar-se ao URL fornecido como primeiro parâmetro. Para tal, seguiremos o modelo do cliente web genérico já apresentado e analisado
  • no fluxo da resposta do servidor, utilizar expressões regulares para:
    • identificar as mensagens de erro
    • encontrar os resultados das simulações

O código do cliente cltImpots.php é o seguinte:

<?php
     // sintaxe $0 urlImpots casado filhos salário token
   // cliente de um serviço fiscal que funciona no urlImpots
   // envia a este serviço as três informações: estado civil, filhos, salário
   // eventualmente com o token de sessão, caso este tenha sido transmitido
   // exibe a tabela de simulações da sessão

   // verificação do número de argumentos
  if(count($argv)!=5 && count($argv)!=6){
       // erro
    fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
     // fim
    exit(1);
  }//se

   // regista-se o URL
  $urlImpots=analyseURL($argv[1]);
  if(isset($urlImpots[erreur])){
       // erro
    fwrite(STDERR,"$urlImpots[erreur]\n");
     // fim
    exit(1);
  }//se

   // análise do pedido do utilizador
  $demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
   // erros?
  if($demande[erreur]){
       // mensagem
    echo "$demande[erreur]\n";
     // terminado
    exit(1);
  }//if

   // é possível efetuar o pedido — utiliza-se o token de sessão
  $impots=getImpots($urlImpots,$demande,$argv[5]);

   // exibimos o token de sessão
  echo "Jeton de session=[$impots[jeton]]\n";

   // erros?
  if(count($impots[erreurs])!=0){
       //mensagens de erro
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//para
  }else{
       // sem erros — exibição das simulações
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//para
  }//se

   // fim do programa
  exit(0);

  // --------------------------------------------------------------
  function analyseURL($URL){  
       // analisa a validade do URL $URL
    $url=parse_url($URL);
    // o protocolo    
    if(strtolower($url[scheme])!="http"){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//se
     // a máquina
    if(! isset($url[host])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//se
     // a porta
    if(! isset($url[port])) $url[port]=80;
     // o pedido
    if(isset($url["query"])){
        $url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
      return $url;
    }//if
     // retorno
    return $url;
  }//analyseURL

  // -----------------------------------------------------------
  function analyseDemande($demande){
       // $demande: cadeia a analisar
     // deve ter o formato «casado, filhos, salário»
    // formato válido
    if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
        // formato inválido
      return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
    }    
     // está correto
    $marié=strtolower($champs[1]);
    return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
  }//analyseDemande

  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots: URL a verificar
     // $demande: dicionário que contém os campos «marie», «enfants» e «salaire»
     // $jeton: um eventual token de sessão

     // abertura de uma ligação na porta $urlImpots[port] de $urlImpots[host]
    $connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
     // retorno em caso de erro
    if(! $connexion){
      return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
    }//se

         // envia-se os cabeçalhos HTTP para o servidor
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));

         // lê-se a resposta do servidor — primeiro os cabeçalhos HTTP
     // a primeira linha
    $ligne=fgets($connexion,10000);

    // URL encontrada?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
        // URL inacessível — resposta com erro
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//se

     // leem-se os restantes cabeçalhos HTTP
    while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
       // procura do token, caso ainda não tenha sido encontrado      
      if(! $jeton){
           // procura da linha «set-cookie»
        if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
            // encontrou-se o token — guarda-se          
          $jeton=$champs[1];            
        }//se
      }//se
    }//linha seguinte

     // lê-se o documento seguinte
    $document="";
    while($ligne=fread($connexion,10000)){
        $document.=$ligne;
    }//enquanto

     // encerra-se a ligação
    fclose($connexion);

     // analisa-se o documento
    $impots=getInfos($document);

     // retorna-se o resultado
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
    }//getImpots

  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: ligação ao servidor web
     // $url: o URL a consultar
     // $paramètres: dicionário dos parâmetros a enviar

     // prepara-se o POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//enquanto
     // remove-se o último carácter
    $post=substr($post,0,-1);

     // prepara-se a solicitação HTTP no formato HTTP/1.0
    $HTTP="POST $url[path] HTTP/1.0\n";
    $HTTP.="Content-type: application/x-www-form-urlencoded\n";
    $HTTP.="Content-length: ".strlen($post)."\n";
    $HTTP.="Connection: close\n";
    if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
    $HTTP.="\n";
    $HTTP.=$post;

    // envia-se a solicitação HTTP
    fwrite($connexion,$HTTP);
    }//POST

  // --------------------------------------------------------------
  function getInfos($document){
       // $document: documento HTML
     // procura-se, ou a lista de erros
     // ou a tabela de simulações

     // preparação do resultado
    $impots[erreurs]=array();
    $impots[simulations]=array();

     // existe algum erro?
     // modelo da linha de erro
    $modErreur="|<li>(.+?)</li>|";
     // pesquisa no documento
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // recuperação de erros
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//para
       // concluído
      return $impots;
    }//se

     // modelo de uma linha da tabela de simulações
     // <tr><td>sim</td><td>2</td><td>200000</td><td>22504</td></tr>
    $modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
    // pesquisa no documento
    if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){    
         // recuperação das simulações
      for($i=0;$i<count($champs);$i++){
          $impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
      }//para
       // retorno do resultado
      return $impots;
    }//if

    // não é normal chegar aqui
    return $impots;
    }//getInfos
?>

Comentários:

  • o programa começa por verificar a validade dos parâmetros que recebeu. Para tal, utiliza duas funções: a analyseURL, que verifica a validade da URL, e a analyseDemande, que verifica os restantes parâmetros.
  • O cálculo do imposto é efetuado pela função getImpots:
  $impots=getImpots($urlImpots,$demande,$argv[5]);
  • A função getImpots é declarada da seguinte forma:
<?php
...
  // --------------------------------------------------------------
  function getImpots($urlImpots,$demande,$jeton){
       // $urlImpots: URL a consultar
     // $demande: dicionário que contém os campos «marie», «enfants» e «salaire»
     // $jeton: um eventual token de sessão
  • (continuação)
    • $urlImpots é um dicionário que contém os seguintes campos:
      • host: máquina onde reside o serviço web
      • port: a sua porta de serviço
      • path: o caminho do recurso solicitado
    • $demande é um dicionário que contém os seguintes campos:
      • casado: sim/não: estado civil
      • filhos: número de filhos
      • salário: salário anual
    • $jeton é o token de sessão.

A função devolve como resultado um dicionário com os seguintes campos:

  • erros: matriz de mensagens de erro
  • simulações: matriz de simulações, sendo que cada simulação é, por sua vez, uma matriz de quatro elementos (casado, filhos, salário, imposto)
  • token: o token de sessão
  • Assim que o resultado de getImpots for obtido, o programa apresenta os resultados e termina:
<?php
...

   // é apresentado o token de sessão
  echo "Jeton de session=[$impots[jeton]]\n";

   // erros?
  if(count($impots[erreurs])!=0){
       //mensagens de erro
    echo "Les erreurs suivantes se sont produites :\n";
    for($i=0;$i<count($impots[erreurs]);$i++){
        echo $impots[erreurs][$i]."\n";
    }//para
  }else{
       // sem erros — exibição das simulações
    echo "Simulations :\n";
    for($i=0;$i<count($impots[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//para
  }//se

   // fim do programa
  exit(0);
  • Vamos agora analisar a função getImpots($urlImpots,$demande,$jeton), que deve
    • criar uma ligação TCP-IP na porta $urlImpots[port] da máquina $urlImpots[host]
    • enviar ao servidor web os cabeçalhos HTTP que este espera, nomeadamente o token de sessão, caso exista
    • enviar ao servidor web um pedido POST com os parâmetros contidos no dicionário $demande
    • analisar a resposta do servidor web para encontrar nela uma lista de erros ou uma tabela de simulações.
  • O envio dos cabeçalhos HTTP é efetuado através da função POST:
<?php
...
         // envio dos cabeçalhos HTTP para o servidor
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
  • Depois de enviados os cabeçalhos HTTP, a função getImpots lê a resposta do servidor na íntegra e coloca-a em $document. A resposta é lida sem ser analisada, exceto a primeira linha, que nos permite saber se o servidor web encontrou ou não o URL que lhe foi solicitado. Com efeito, se o URL tiver sido encontrado, o servidor web responde HTTP/1.X 200 OK, em que X depende da versão do HTTP utilizada.
<?php
...
    // URL encontrada?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
 <?php
...
       // URL inacessível — retorno com erro
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//se
  • O documento é analisado através da função getInfos:
<?php
...
     // se analisarmos o documento
    $impots=getInfos($document);
  • O resultado obtido é um dicionário com dois campos:
    • erros: lista de erros — pode estar vazia
    • simulações: lista de simulações — pode estar vazia
  • Assim, a função getImpots pode apresentar o seu resultado sob a forma de um dicionário.
<?php
...
     // retornamos o resultado
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
  • Analisemos agora a função POST:
<?php
...
  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: a ligação ao servidor web
     // $url: o URL a consultar
     // $paramètres: dicionário dos parâmetros a enviar

     // prepara-se o POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//enquanto
     // remove-se o último carácter
    $post=substr($post,0,-1);

     // prepara-se a solicitação HTTP no formato HTTP/1.0
    $HTTP="POST $url[path] HTTP/1.0\n";
    $HTTP.="Content-type: application/x-www-form-urlencoded\n";
    $HTTP.="Content-length: ".strlen($post)."\n";
    $HTTP.="Connection: close\n";
    if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
    $HTTP.="\n";
    $HTTP.=$post;

    // envia-se a solicitação HTTP
    fwrite($connexion,$HTTP);
    }//POST

Embora, no âmbito do programa, já tivéssemos tido a oportunidade de solicitar um recurso web através de um GET, ainda não tínhamos tido a oportunidade de o fazer com um POST. O POST difere do GET na forma como envia parâmetros ao servidor. Deve enviar os seguintes cabeçalhos HTTP:

POST chemin HTTP/1.X
Content-type: application/x-www-form-urlencoded
Content-length: N

com

  • chemin: caminho do recurso web solicitado, neste caso $url[path]
  • HTTP/1.X: o protocolo HTTP pretendido. Aqui, optou-se por HTTP/1.0 para obter a resposta num único bloco. HTTP/1.1 permite o envio da resposta em vários blocos (chunked).
  • N indica o número de caracteres que o cliente se prepara para enviar ao servidor

Os N caracteres que constituem os parâmetros da consulta são enviados imediatamente a seguir à linha em branco que encerra os cabeçalhos HTTP enviados ao servidor. Estes parâmetros têm o formato param1=val1&param2=val2&..., em que parami é o nome do parâmetro e vali é o seu valor. Os valores válidos podem conter caracteres «incómodos», tais como espaços, o caractere &, o caractere =, etc. Estes caracteres devem ser substituídos por uma cadeia %XX, em que XX é o seu código hexadecimal. A função PHP urlencode realiza esta tarefa. O processo inverso é realizado pela função urldecode.

Por fim, note-se que, se houver um token, este é enviado com um cabeçalho HTTP Cookie: PHPSESSID=token.

  • Resta-nos agora analisar a função que analisa a resposta do servidor:
<?php
...
  // --------------------------------------------------------------
  function getInfos($document){
       // $document: documento HTML
     // procura-se, ou a lista de erros
     // ou a tabela de simulações

     // preparação do resultado
    $impots[erreurs]=array();
    $impots[simulations]=array();
  • Recorde-se que os erros são enviados pelo servidor na forma <li>mensagem de erro</li>. A expressão regular «|<li>(.+?)</li>|» deve permitir recuperar estas informações. Aqui, utilizámos o sinal | para delimitar a expressão regular, em vez do sinal /, uma vez que este também existe na própria expressão regular. A função preg_match_all ($modèle, $document, $champs, PREG_SET_ORDER) permite recuperar todas as ocorrências de $modèle encontradas em $document. Estas são colocadas na tabela $champs. Assim, $champs[i] representa a ocorrência n.º i de $modèle em $document. Em $champs[$i][0] é colocada a cadeia correspondente ao modelo. Se este tivesse parênteses, a cadeia correspondente ao primeiro parêntese é colocada em $champs[$i][1], a segunda em $champs[$i][2] e assim sucessivamente. O código para recuperar os erros será, portanto, o seguinte:
<?php
...

     // existe algum erro?
     // modelo da linha de erro
    $modErreur="|<li>(.+?)</li>|";
     // pesquisa no documento
    if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
        // recuperação de erros
      for($i=0;$i<count($champs);$i++){
          $impots[erreurs][]=$champs[$i][1];
      }//para
       // concluído
      return $impots;
    }//if

4.7. Aplicação de Impostos: Conclusão

Mostrámos diferentes versões da nossa aplicação cliente-servidor para o cálculo de impostos:

  • versão 1: o serviço é prestado por um conjunto de programas PHP; o cliente é um navegador. Realiza uma única simulação e não guarda um registo das anteriores.
  • versão 2: adicionam-se algumas funcionalidades do lado do navegador, inserindo scripts JavaScript no documento HTML carregado pelo mesmo. Este controla a validade dos parâmetros do formulário.
  • versão 3: permite-se que o serviço se lembre das diferentes simulações realizadas por um cliente através da gestão de uma sessão. A interface HTML é alterada em conformidade para as apresentar.
  • versão 4: o cliente é agora uma aplicação de consola autónoma. Isto permite-nos voltar à programação de clientes web.

Nesta altura, podemos fazer algumas observações:

  • as versões 1 a 3 permitem a utilização de navegadores sem outras capacidades além da de executar scripts JavaScript. Note-se que um utilizador tem sempre a possibilidade de inibir a execução destes. A aplicação funcionará então apenas parcialmente na sua versão 1 (a opção «Apagar» não funcionará) e de todo nas suas versões 2 e 3 (as opções «Apagar» e «Calcular» não funcionarão). Poderia ser interessante prever uma versão do serviço que não utilize scripts JavaScript.

Ao desenvolver um serviço Web, é necessário questionar-se sobre os tipos de clientes a que se destina. Se se pretender atingir o maior número possível de clientes, deve-se criar uma aplicação que envie aos navegadores apenas HTML (sem JavaScript nem applets). Se estivermos a trabalhar numa intranet e tivermos controlo sobre a configuração dos terminais da mesma, podemos então dar-nos ao luxo de ser mais exigentes em relação ao cliente.

A versão 4 é um cliente web que recupera a informação de que necessita no fluxo HTML enviado pelo servidor. Muitas vezes, não temos controlo sobre esse fluxo. É o que acontece quando se desenvolve um cliente para um serviço web existente na rede e gerido por outra entidade. Vejamos um exemplo. Suponhamos que o nosso serviço de simulações de cálculo de impostos tenha sido desenvolvido pela empresa X. Atualmente, o serviço envia as simulações numa tabela HTML e o nosso cliente aproveita esse facto para as recuperar. Assim, compara cada linha da resposta do servidor com a expressão regular:

     // modelo de uma linha da tabela de simulações
     // <tr><td>sim</td><td>2</td><td>200000</td><td>22504</td></tr>
    $modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";

Suponhamos agora que o programador da aplicação altere a apresentação visual da resposta, colocando as simulações não numa tabela, mas numa lista com o seguinte formato:

Résultat des simulations
<ul>
    <li>oui,2,200000,22504
  <li>non,2,200000,33388
</ul>  

Neste caso, o nosso cliente web terá de ser reescrito. É esta a ameaça permanente que paira sobre os clientes web de aplicações que não controlamos diretamente. O XML pode oferecer uma solução para este problema:

  • em vez de gerar HTML, o serviço de simulações irá gerar XML. No nosso exemplo, isso poderia ser
<simulations>
    <entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
  <simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
  • pode ser associada a esta resposta uma folha de estilo que indique aos navegadores a forma visual a atribuir a esta resposta XML
  • os clientes web programados ignorariam essa folha de estilo e recuperariam a informação diretamente do fluxo XML da resposta

Se o criador do serviço pretender alterar a apresentação visual dos resultados que fornece, modificará a folha de estilo e não o XML. Graças à folha de estilo, os navegadores apresentarão a nova apresentação visual e os clientes web programados não terão de ser alterados.