Skip to content

4. Exemplos

4.1. Aplicativo de Impostos: Introdução

Apresentamos aqui o aplicativo IMPOTS, que será utilizado várias vezes a seguir. Trata-se de um aplicativo que permite calcular o imposto de um contribuinte. Consideramos o caso simplificado de um contribuinte que tem apenas seu salário para declarar:

  • calcula-se o número de quotas do empregado nbParts = nbEnfants/2 + 1 se ele for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
  • se tiver pelo menos três filhos, ele tem mais meia parcela
  • calcula-se sua renda tributável R = 0,72 * S, onde S é seu salário anual
  • calcula-se seu coeficiente familiar QF = R / nbParts
  • calcula-se seu imposto I. Consideremos a tabela a seguir:
12620,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 possui 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF ≤ campo1. Por exemplo, se QF = 23000, será encontrada 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 serão utilizados os coeficientes da última linha. Aqui:

    0                0.65        49062

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

Os dados que definem as diferentes faixas de imposto estão em um banco de dados ODBC-MySQL. O MySQL é um SGBD de domínio público que pode ser utilizado em diversas plataformas, incluindo Windows e Linux. Com este SGBD, foi criado um banco de dados chamado dbimpots, contendo uma única tabela denominada impots. O acesso ao banco de dados é controlado por um login/senha, neste caso admimpots/mdpimpots. A captura de tela a seguir mostra como utilizar o banco 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

O banco de dados dbimpots é transformado na fonte de dados ODBC da seguinte maneira:

  • inicia-se o gerenciador 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 driver MySQL e executa-se [Terminer]
  • o driver MySQL solicita algumas informações:
1
o nome DSN a ser atribuído à fonte de dados ODBC — pode ser qualquer um
2
a máquina na qual o SGBD MySQL é executado — neste caso, localhost. Vale ressaltar que o banco de dados pode ser remoto. Os aplicativos locais que utilizam a fonte de dados ODBC não perceberiam isso. Esse seria o caso, notadamente, do nosso aplicativo PHP.
3
o banco de dados MySQL a ser utilizado. MySQL é um SGBD que gerencia bancos de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, é fornecido o nome do banco de dados gerenciado.
4
o nome de um usuário com direito de acesso a essa base de dados
5
sua senha

4.2. Aplicativo Impostos: a classe ImpotsDSN

Nosso aplicativo se baseará 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
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

A primeira coluna será colocada no atributo $limites da classe ImotsDSN, a segunda no atributo $coeffR e a terceira no $coeffN. Esses três atributos são inicializados pelo construtor da classe. Este busca os dados em uma fonte de dados ODBC. Podem ocorrer diversos erros durante a construção do objeto. Esses erros são relatados no atributo $erreurs na 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 usuário com permissão de leitura na fonte ODBC
// pwd: sua senha
// 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 usuários da classe poderão chamar o método calculerImpots do objeto. Esse método 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 contendo os seguintes campos
       // dsn: nome DSN da fonte de dados ODBC contendo os valores das tabelas de limites, coeffR, coeffN
       // user: nome de um usuário com permissão de leitura na fonte ODBC
       // pwd: sua senha
       // 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;
      }//se

       // abertura do banco 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 ao banco 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]);
      }//while

       // fechamento 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";
      }//se
       // 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 1/2 parte se houver pelo menos 3 filhos
      if($personne[enfants]>=3) $nbParts+=0.5;
       // renda tributável
      $revenuImposable=0.72*$personne[salaire];
       // quociente familiar
      $quotient=$revenuImposable/$nbParts;
       // é inserido no final da tabela de limites para interromper o ciclo a seguir
      $this->limites[$this->nbLimites]=$quotient;
       // cálculo do imposto
      $i=0;
      while($quotient>$this->limites[$i]) $i++;
      // como colocamos $quotient no final da tabela $limites, o loop anterior
       // não pode ultrapassar os limites da tabela $limites
       //; agora podemos calcular o imposto
      return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
    }//calculImpots
  }//classificação 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){
       // saída
        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 se encarrega de “incluir” o arquivo que contém a classe ImpotsDSN
  • e, em seguida, cria um objeto objImpots da classe ImpotsDSN, passando ao construtor do objeto as informações necessárias
  • assim que esse objeto for criado, o método calculer desse objeto 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 repetir sua definição.

4.3. Aplicativo Impostos: versão 1

Apresentamos agora a versão 1 do aplicativo IMPOTS. Estamos no contexto de um aplicativo web que apresentaria uma interface HTML a um usuário, 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 exibição do formulário é realizada 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
         // há 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 exibir informações que lhe são passadas pelo programa principal do aplicativo 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
seu salário anual
impots
o valor 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 é restaurar o formulário ao seu estado inicial: botão desmarcado, campos de preenchimento vazios. Vale ressaltar que esse resultado não poderia ser obtido com um botão HTML do tipo “reset”. De fato, quando esse tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. No entanto, em nosso aplicativo, 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 do aplicativo
  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);

   // recuperação dos 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 de 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();

     // status 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="";
    }

     // enviando a solicitação
    return $requête;
  }//verificar

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

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

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

    // erros?
    if(count($objImpots->erreurs)!=0){
         // colocamos 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. É ela que serve de base para o cálculo de impostos e que nos ocultará o acesso ao banco de dados
  • São realizadas algumas inicializações com o objetivo de facilitar a manutenção do aplicativo. Se algum parâmetro for alterado, seus valores serão modificados nesta seção. Geralmente, esses valores de configuração são armazenados em um arquivo ou em um banco de dados.
  • Recuperamos os três parâmetros do formulário correspondentes aos campos HTML, optMarie, txtEnfants e txtSalaire.
  • Esses parâmetros podem estar total ou parcialmente ausentes. Esse será o caso, principalmente, na solicitação inicial do formulário. Nesse caso, basta enviar um formulário vazio.
  • Em seguida, verifica-se a validade dos três parâmetros recuperados. Se estiverem incorretos, o formulário é reenviado tal como foi preenchido, mas acompanhado de uma lista de erros.
  • Uma vez verificada a validade dos três parâmetros, é possível calcular o imposto. Este é calculado pela função calculerImpots. Essa 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 o banco de dados estiver indisponível. Nesse caso, o aplicativo exibe uma página de erro específica indicando os erros que ocorreram.
  • Se tudo correr bem, o formulário é retornado exatamente como foi preenchido, mas com o valor do imposto a pagar adicionado.

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>

Aqui estão alguns exemplos de erros. Primeiro, é inserida uma senha incorreta no banco de dados:

Image

Inserimos dados incorretos no formulário:

Image

Por fim, inserimos valores corretos:

Image

4.4. Aplicativo Impostos: versão 2

No exemplo anterior, a validade dos parâmetros txtEnfants e txtSalaire do formulário é verificada pelo servidor. Propomos aqui verificá-la por meio de um script JavaScript incluído na página do formulário. Assim, é o navegador que realiza a verificação dos parâmetros. O servidor só é acionado se esses parâmetros forem válidos. Dessa forma, economiza-se “largura de banda”. A página de exibição impots-form.php fica da seguinte forma:

<?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 enviá-los ao 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
          // tudo certo — o formulário será enviado ao 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>

Observe as seguintes alterações:

  • o botão Calculer não é mais do tipo submit, mas sim do tipo button, associado a uma função chamada calculer. É essa função que fará a análise dos campos txtEnfants e txTsalaire. Se estiverem corretos, os valores do formulário serão enviados ao servidor (submit); caso contrário, será exibida uma mensagem de erro.

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

Image

4.5. Aplicativo de Impostos: versão 3

Modificamos ligeiramente o aplicativo para introduzir o conceito de sessão. Consideramos agora que o aplicativo é um simulador de cálculo de impostos. Um usuário pode, então, simular diferentes “configurações” de contribuinte e verificar qual seria, para cada uma delas, o imposto a pagar. A página da web abaixo apresenta um exemplo do que poderia ser obtido:

Image

O programa de exibiçã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
         // há 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 
         // há 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 exibição apresenta pequenas diferenças em relação à versão anterior:

  • o dicionário recebe um dicionário $requête em vez de um objeto $requête. Portanto, escreve-se $requête[enfants] e não $requête->filhos.
  • Esse dicionário possui um campo simulations, que é uma matriz bidimensional. $requête[simulations][i] é a simulação nº i. Esta, por sua vez, é uma matriz composta por quatro cadeias de caracteres: estado civil, número de filhos, salário anual e 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 do aplicativo
  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);

     // recuperação dos parâmetros da sessão
  $session=$_SESSION["session"];
   // sessão válida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // iniciando uma nova sessão
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // houve 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

  // recuperamos os parâmetros da troca em andamento
  $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();

     // status 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]="";
    }

     // enviando a solicitação
    return $requête;
  }//verificar

  // --------------------------------------------------------
  function terminerSession($session){
       // armazenando a sessão
    $_SESSION[session]=$session;
     // fim do script
    exit(0);
  }//terminerSession
  • Em primeiro lugar, esta aplicação gerencia uma sessão. Portanto, iniciamos uma sessão no início do script:
  session_start();
  • A sessão armazena uma única variável: o dicionário $session. Esse dicionário possui dois campos:
    • objImpots: um objeto ImpotsDSN. Esse objeto é armazenado na sessão do cliente para evitar acessos sucessivos desnecessários ao banco 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 é iniciada, recupera-se a variável $session. Se ela ainda não existir, significa que a sessão está sendo iniciada. Cria-se, então, um objeto ImpotsDSN e uma tabela de simulações vazia. Se necessário, são sinalizados erros.
<?php
...
     // recuperando os parâmetros da sessão
  $session=$_SESSION["session"];
   // sessão válida?
  if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
       // iniciando uma nova sessão
    $session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
    // houve 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
  • Assim que a sessão for inicializada corretamente, os parâmetros da troca são recuperados. Caso não haja todos os parâmetros esperados, um formulário vazio é enviado.
<?php
...
   // recuperamos os parâmetros da troca em andamento
  $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, eles serão verificados. Caso haja erros, eles serão sinalizados:
<?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 andamento é 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 é encerrado com o registro da variável $session na sessão. Isso é feito no procedimento terminerSession.
<?php
...

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

Vamos encerrar apresentando o programa de exibição de erros de acesso ao banco 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>

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

4.6. Aplicativo Impostos: versão 4

Agora, vamos criar um aplicativo autônomo que será um cliente web do aplicativo web impots anterior. O aplicativo será um aplicativo de console iniciado 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 de impostos operando em urlImpots
   // envia a esse serviço as três informações: casado, filhos, salário
   // eventualmente com o token de sessão, caso este tenha sido passado
   // exibe a tabela de simulações da sessão

Aqui estão alguns exemplos de uso. Primeiro, um 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]

Conseguimos recuperar o valor do imposto a pagar (22.504 f). Também recuperamos o token de sessão. Agora podemos usá-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]

Recebemos, de fato, a tabela das simulações enviadas pelo servidor web. O token recuperado permanece, obviamente, o mesmo. Se consultarmos o serviço web enquanto o banco de dados não tiver sido iniciado, 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 desenvolver um cliente web programado, é necessário saber exatamente o que o servidor envia em resposta às diversas 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 nos ajudar a identificar as informações úteis no fluxo de linhas enviadas pelo servidor. Para isso, precisamos conhecer o formato exato das diferentes respostas do servidor. Para tal, podemos consultar o serviço web com um navegador e examinar o código-fonte que foi enviado. Vale ressaltar que esse método não permite identificar os cabeçalhos da resposta do servidor web. Às 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 o banco 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 correspondentes ao modelo: "|<li>(.+?)</li>|".

Agora temos as orientações sobre o que fazer quando o usuário solicitar o cálculo do imposto a partir do cliente de console anterior:

  • verificar se todos os parâmetros são válidos e, se necessário, sinalizar os erros.
  • conectar-se ao URL fornecido como primeiro parâmetro. Para isso, seguiremos o modelo do cliente web genérico já apresentado e estudado
  • no fluxo da resposta do servidor, usar expressões regulares para:
    • identificar as mensagens de erro
    • localizar 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 opera no urlImpots
   // envia a esse serviço as três informações: estado civil, filhos, salário
   // eventualmente com o token de sessão, caso este tenha sido passado
   // 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

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

   // análise da solicitação do usuário
  $demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
   // erros?
  if($demande[erreur]){
       // mensagem
    echo "$demande[erreur]\n";
     // concluído
    exit(1);
  }//if

   // é possível fazer a solicitação — usa-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";

   // algum erro?
  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;
     // a solicitação
    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: string a ser analisada
     // 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 ser verificado
     // $demande: dicionário contendo os campos “esposa”, “filhos” e “salário”
     // $jeton: um possível token de sessão

     // abertura de uma conexã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 ao 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 — retorno com erro
      return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
    }//se

     // lê-se os demais cabeçalhos HTTP
    while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
       // procura o token caso ainda não tenha sido encontrado      
      if(! $jeton){
           // busca pela linha “set-cookie”
        if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
            // encontramos o token — o armazenamos          
          $jeton=$champs[1];            
        }//se
      }//se
    }//linha seguinte

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

     // fecha-se a conexã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: conexão com o servidor web
     // $url: o URL a ser consultado
     // $paramètres: dicionário dos parâmetros a serem enviados

     // preparando o POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//enquanto
     // remove-se o último caractere
    $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 a lista de erros
     // ou a tabela de simulações

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

     // há 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>|";
    // busca 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 até aqui
    return $impots;
    }//getInfos
?>

Comentários:

  • o programa começa verificando a validade dos parâmetros que recebeu. Para isso, ele utiliza duas funções: analyseURL, que verifica a validade do URL, e analyseDemande, que verifica os demais parâmetros.
  • O cálculo do imposto é feito 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 ser consultado
     // $demande: dicionário contendo os campos “esposa”, “filhos” e “salário”
     // $jeton: um possível token de sessão
  • (continuação)
    • $urlImpots é um dicionário que contém os campos:
      • host: máquina onde reside o serviço web
      • port: sua porta de serviço
      • path: o caminho do recurso solicitado
    • $demande é um dicionário que contém os 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 retorna 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 exibe os resultados e é encerrado:
<?php
...

   // exibe-se 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 conexão TCP-IP na porta $urlImpots[port] da máquina $urlImpots[host]
    • enviar ao servidor web os cabeçalhos HTTP que ele espera, incluindo, em particular, o token de sessão, caso haja um
    • enviar ao servidor web uma solicitação 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 é feito por meio da função POST:
<?php
...
         // os cabeçalhos HTTP são enviados ao servidor
    POST($connexion,$urlImpots,$jeton,
            array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
  • Após o envio dos cabeçalhos HTTP, a função getImpots lê a resposta completa do servidor e a armazena em $document. A resposta é lida sem ser analisada, exceto pela primeira linha, que nos permite saber se o servidor web encontrou ou não o URL que foi solicitado. De fato, se o URL foi 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 por meio da função getInfos:
<?php
...
     // analisamos o documento
    $impots=getInfos($document);
  • O resultado gerado é 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 retornar seu resultado na forma de um dicionário.
<?php
...
     // retornamos o resultado
    return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
  • Vamos agora analisar a função POST:
<?php
...
  // --------------------------------------------------------------
  function POST($connexion,$url,$jeton,$paramètres){
       // $connexion: a conexão com o servidor web
     // $url: o URL a ser consultado
     // $paramètres: dicionário dos parâmetros a serem enviados

     // preparando o POST
    $post="";
    while(list($paramètre,$valeur)=each($paramètres)){
        $post.=$paramètre."=".urlencode($valeur)."&";
    }//enquanto
     // removemos o último caractere
    $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 programa, já tivéssemos tido a oportunidade de solicitar um recurso da web por meio de um GET, ainda não tínhamos tido a oportunidade de fazê-lo com um POST. O POST difere do GET na forma de enviar parâmetros ao servidor. Ele 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 da web solicitado, neste caso $url[path]
  • HTTP/1.X: o protocolo HTTP desejado. Aqui, escolheu-se HTTP/1.0 para receber a resposta em um ú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 está prestes a enviar ao servidor

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

Por fim, observe-se que, se houver um token, ele é 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 a lista de erros
     // ou a tabela de simulações

     // preparação do resultado
    $impots[erreurs]=array();
    $impots[simulations]=array();
  • Vale lembrar que os erros são enviados pelo servidor no formato <li>mensagem de erro</li>. A expressão regular “|<li>(.+?)</li>|” deve permitir recuperar essas informações. Aqui, utilizamos o sinal | para delimitar a expressão regular, em vez do sinal /, pois este também aparece 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. Essas ocorrências são colocadas na tabela $champs. Assim, $champs[i] representa a ocorrência nº i de $modèle em $document. Em $champs[$i][0] é inserida a sequência correspondente ao modelo. Se este tivesse parênteses, a sequência correspondente ao primeiro parêntese é colocada em $champs[$i][1], a segunda em $champs[$i][2] e assim por diante. O código para recuperar os erros será, portanto, o seguinte:
<?php
...

     // há 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. Aplicativo de Impostos: Conclusão

Mostramos diferentes versões do nosso aplicativo cliente-servidor para cálculo de impostos:

  • versão 1: o serviço é prestado por um conjunto de programas PHP; o cliente é um navegador. Ele realiza uma única simulação e não mantém registro das anteriores.
  • versão 2: adicionamos algumas funcionalidades no lado do navegador, inserindo scripts JavaScript no documento HTML carregado por ele. O navegador verifica a validade dos parâmetros do formulário.
  • versão 3: permite-se que o serviço lembre-se das diferentes simulações realizadas por um cliente por meio do gerenciamento de uma sessão. A interface HTML é modificada de acordo para exibi-las.
  • versão 4: o cliente agora é um aplicativo de console autônomo. Isso nos permite voltar a desenvolver clientes web programados.

Neste ponto, podemos fazer algumas observações:

  • as versões 1 a 3 permitem o uso de navegadores sem nenhuma capacidade além da de executar scripts JavaScript. Vale ressaltar que o usuário sempre tem a possibilidade de desativar a execução desses scripts. O aplicativo funcionará, então, apenas parcialmente na versão 1 (a opção “Apagar” não funcionará) e não funcionará de forma alguma nas 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, é preciso definir quais tipos de clientes se deseja atingir. Se o objetivo for atingir o maior número possível de clientes, deve-se criar um aplicativo que envie aos navegadores apenas HTML (sem JavaScript nem applets). Se estivermos trabalhando em uma intranet e tivermos controle sobre a configuração dos computadores dela, podemos então nos dar ao luxo de ser mais exigentes em relação ao cliente.

A versão 4 é um cliente web que recupera as informações de que precisa dentro do fluxo HTML enviado pelo servidor. Muitas vezes, não temos controle sobre esse fluxo. Esse é o caso quando criamos um cliente para um serviço web existente na rede e gerenciado por outra pessoa. Vejamos um exemplo. Suponhamos que 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 em uma tabela HTML e nosso cliente aproveita esse fato para recuperá-las. Assim, ele 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 desenvolvedor do aplicativo altere a aparência visual da resposta, colocando as simulações não em uma tabela, mas em uma lista no formato:

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

Nesse caso, nosso cliente web precisará ser reescrito. Essa é a ameaça constante que paira sobre os clientes web de aplicativos que não controlamos diretamente. O XML pode oferecer uma solução para esse 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>
  • uma folha de estilo poderia ser associada a essa resposta, indicando aos navegadores a aparência que devem dar a essa resposta XML
  • os clientes web programados ignorariam essa folha de estilo e recuperariam as informações diretamente do fluxo XML da resposta

Se o desenvolvedor do serviço desejar alterar a apresentação visual dos resultados fornecidos, ele modificará a folha de estilo e não o XML. Graças à folha de estilo, os navegadores exibirão a nova apresentação visual e os clientes web programados não precisarão ser alterados.