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

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

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

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:
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 | |
o número de filhos do contribuinte | |
seu salário anual | |
o valor do imposto a pagar | |
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:

Inserimos dados incorretos no formulário:

Por fim, inserimos valores corretos:

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:

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:

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:
- 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:
- 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.
- $urlImpots é um dicionário que contém os campos:
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:
- 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:
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¶m2=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:
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.
