4. Exemplos
4.1. Aplicação Impostos: Introdução
Apresentamos aqui a aplicação IMPOTS, que será utilizada em várias ocasiões daqui em diante. Trata-se de uma aplicação que permite calcular o imposto de um contribuinte. Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:
- calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants é o número de filhos que tem.
- se tiver pelo menos três filhos, tem mais meia quota
- calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- calcula-se o seu coeficiente familiar QF = R / nbParts
- calcula-se o seu imposto I. Consideremos a seguinte tabela:
12620,0 | 0 | 0 |
13 190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24 740 | 0,15 | 2072,5 |
31 810 | 0,2 | 3309,5 |
39 970 | 0,25 | 4900 |
48 360 | 0,3 | 6898,5 |
55 790 | 0,35 | 9316,5 |
92 970 | 0,4 | 12106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23 147,5 |
172 040 | 0,55 | 30710 |
195 000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 23000, encontrar-se-á a linha
O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então são utilizados os coeficientes da última linha. Neste caso:
o que resulta no imposto I = 0,65*R - 49062*nbParts.
Os dados que definem as diferentes faixas de imposto encontram-se numa base de dados ODBC-MySQL. O MySQL é um SGBD de domínio público que pode ser utilizado em várias plataformas, incluindo o Windows e o Linux. Com este SGBD, foi criada uma base de dados dbimpots que contém uma única tabela denominada impots. O acesso à base de dados é controlado por um nome de utilizador e palavra-passe, neste caso admimpots/mdpimpots. A captura de ecrã seguinte mostra como utilizar a base de dados dbimpots com o MySQL:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
A base de dados dbimpots é transformada na fonte de dados ODBC da seguinte forma:
- é iniciado o gestor de fontes de dados ODBC de 32 bits

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

- seleciona-se o controlador MySQL e executa-se [Terminer]
![]() |
- o controlador MySQL solicita uma série de informações:
1 | o nome DSN a atribuir à fonte de dados ODBC — pode ser qualquer um |
2 | a máquina na qual o SGBD MySQL está a ser executado — neste caso, localhost. É importante referir que a base de dados pode ser uma base de dados remota. As aplicações locais que utilizam a fonte de dados ODBC não se aperceberiam disso. Seria esse o caso, nomeadamente, da nossa aplicação PHP. |
3 | a base de dados MySQL a utilizar. MySQL é um SGBD que gere bases de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, indica-se o nome da base de dados gerida. |
4 | o nome de um utilizador com direitos de acesso a esta base de dados |
5 | a sua palavra-passe |
4.2. Aplicação Impostos: a classe ImpotsDSN
A nossa aplicação basear-se-á na classe PHP ImpotsDSN, que terá os seguintes atributos, construtor e método:
<?php
...
// atributos
var $limites; // tabela de limites
var $coeffR; // tabela de coeffR
var $coeffN; // tabela de coeffN
var $erreurs; // tabela de erros
- Vimos na exposição do problema que precisávamos de três conjuntos de dados
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24 740 | 0,15 | 2072,5 |
31 810 | 0,2 | 3309,5 |
39 970 | 0,25 | 4900 |
48 360 | 0,3 | 6898,5 |
55 790 | 0,35 | 9316,5 |
92 970 | 0,4 | 12106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23 147,5 |
172 040 | 0,55 | 30710 |
195 000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
A primeira coluna será colocada no atributo $limites da classe ImotsDSN, a segunda no atributo $coeffR e a terceira no $coeffN. Estes três atributos são inicializados pelo construtor da classe. Este irá buscar os dados numa fonte de dados ODBC. Podem ocorrer vários erros durante a construção do objeto. Estes erros são relatados no atributo $erreurs sob a forma de uma tabela de mensagens de erro.
- O construtor recebe como parâmetro um dicionário $impots com os seguintes campos:
// dsn: nome DSN da fonte de dados ODBC que contém os valores das tabelas de limites, coeffR, coeffN
// user: nome de um utilizador com direitos de leitura na fonte ODBC
// pwd: a sua palavra-passe
// tabela: nome da tabela que contém os valores (limites, coeffR, coeffN)
// limites: nome da coluna que contém os valores-limite
// coeffR: nome da coluna que contém os valores coeffR
// coeffN: nome da coluna que contém os valores coeffN
O parâmetro do construtor fornece todas as informações necessárias para ler os dados na fonte de dados ODBC
- assim que o objeto ImpotsDSN for criado, os utilizadores da classe poderão chamar o método calculerImpots do objeto. Este recebe como parâmetro um dicionário $personne:
// $pessoa["marié"]: sim, não
// $pessoa["enfants"]: número de filhos
// $pessoa["salaire"]: salário anual
A classe completa é a seguinte:
<?php
// definição de uma classe objImpots
class ImpotsDSN{
// atributos: as 3 tabelas de dados
var $limites; // tabela de limites
var $coeffR; // tabela de coeffR
var $coeffN; // tabela de coeffN
var $erreurs; // tabela de erros
// fabricante
function ImpotsDSN($impots){
// $impots: dicionário que contém os seguintes campos
// dsn: nome DSN da fonte de dados ODBC que contém os valores das tabelas de limites, coeffR, coeffN
// user: nome de um utilizador com direitos de leitura na fonte ODBC
// pwd: a sua palavra-passe
// tabela: nome da tabela que contém os valores (limites, coeffR, coeffN)
// limites: nome da coluna que contém os valores-limite
// coeffR: nome da coluna que contém os valores coeffR
// coeffN: nome da coluna que contém os valores coeffN
// inicialmente sem erros
$this->erreurs=array();
// verificação da chamada
if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
// erro
$this->erreurs[]="Appel incorrect";
// fim
return;
}//if
// abertura da base de dados DSN
$connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
// erro?
if(! $connexion){
// erro
$this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
// fim
return;
}//if
// envio de uma consulta à base de dados
$requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
if(! odbc_execute($requête)){
// erro
$this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
// fim
odbc_close($connexion);
return;
}//se
// análise dos resultados da consulta
$this->limites=array();
$this->coeffR=array();
$this->coeffN=array();
while(odbc_fetch_row($requête)){
// mais uma linha
$this->limites[]=odbc_result($requête,$impots[limites]);
$this->coeffR[]=odbc_result($requête,$impots[coeffR]);
$this->coeffN[]=odbc_result($requête,$impots[coeffN]);
}//enquanto
// fecho da base
odbc_close($connexion);
}//construtor
// --------------------------------------------------------------------------
function calculer($personne){
// $pessoa["marié"]: sim, não
// $pessoa["enfants"]: número de filhos
// $pessoa["salaire"]: salário anual
// o objeto está em bom estado?
if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;
// verificação da chamada
if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
// erro
$this->erreurs[]="Appel incorrect";
// fim
return -1;
}//if
// Os parâmetros estão corretos?
$personne[marié]=strtolower($personne[marié]);
if($personne[marié]!=oui && $personne[marié]!=non){
// erro
$this->erreurs[]="Statut marital [$personne[marié]] incorrect";
}//if
if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
// erro
$this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
}//if
if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
// erro
$this->erreurs[]="salaire [$personne[salaire]] incorrect";
}//if
// erros?
if(count($this->erreurs)!=0) return -1;
// número de porções
if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
else $nbParts=$personne[enfants]/2+1;
// mais meia quota se houver pelo menos 3 filhos
if($personne[enfants]>=3) $nbParts+=0.5;
// rendimento tributável
$revenuImposable=0.72*$personne[salaire];
// quociente familiar
$quotient=$revenuImposable/$nbParts;
// é colocado no final da tabela de limites para interromper o ciclo seguinte
$this->limites[$this->nbLimites]=$quotient;
// cálculo do imposto
$i=0;
while($quotient>$this->limites[$i]) $i++;
// uma vez que se colocou $quotient no final da tabela $limites, o ciclo anterior
// não pode ultrapassar os limites da tabela $limites
// agora já é possível calcular o imposto
return floor($revenuImposable*$this->coeffR[$i]-$nbParts*$this->coeffN[$i]);
}//calculImpots
}//classe de impostos
?>
Um programa de teste poderia ser o seguinte:
<?php
// bibliotecas
include "ImpotsDSN.php";
// criação de um objeto de impostos
$conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
$objImpots=new ImpotsDSN($conf);
// erros?
if(count($objImpots->erreurs)!=0){
// mensagem
echo "Les erreurs suivantes se sont produites :\n";
$erreurs=$objImpots->erreurs;
for($i=0;$i<count($erreurs);$i++){
echo "$erreurs[$i]\n";
}//para
// fim
exit(1);
}//se
// testes
calculerImpots($objImpots,oui,2,200000);
calculerImpots($objImpots,non,2,200000);
calculerImpots($objImpots,oui,3,200000);
calculerImpots($objImpots,non,3,200000);
calculerImpots($objImpots,array(),array(),array());
// fim
exit(0);
// -----------------------------------------------------------
function calculerImpots($objImpots,$marié,$enfants,$salaire){
// echo
echo "impots($marié,$enfants,$salaire)\n";
// cálculo do imposto
$personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);
$montant=$objImpots->calculer($personne);
// erros?
if(count($objImpots->erreurs)!=0){
// mensagem
echo "Les erreurs suivantes se sont produites :\n";
$erreurs=$objImpots->erreurs;
for($i=0;$i<count($erreurs);$i++){
echo "$erreurs[$i]\n";
}//para
}else echo "montant=$montant\n";
}//calculerImp
- o programa de teste encarrega-se de «incluir» o ficheiro que contém a classe ImpotsDSN
- e, em seguida, cria um objeto objImpots da classe ImpotsDSN, passando ao construtor do objeto as informações de que este necessita
- assim que este objeto for criado, o método calculer do mesmo será chamado cinco vezes
Os resultados obtidos são os seguintes:
dos>e:\php43\php.exe test.php
impots(oui,2,200000)
montant=22504
impots(non,2,200000)
montant=33388
impots(oui,3,200000)
montant=16400
impots(non,3,200000)
montant=22504
impots(Array,Array,Array)
Les erreurs suivantes se sont produites :
Statut marital [array] incorrect
Nombre d'enfants [Array] incorrect
salaire [Array] incorrect
A partir de agora, utilizaremos a classe ImpotsDSN sem voltar a apresentar a sua definição.
4.3. Aplicação Impostos: versão 1
Apresentamos agora a versão 1 da aplicação IMPOTS. Estamos no contexto de uma aplicação web que apresentaria uma interface HTML a um utilizador, a fim de obter os três parâmetros necessários para o cálculo do imposto:
- o estado civil (casado ou solteiro)
- o número de filhos
- o salário anual

A apresentação do formulário é efetuada pela seguinte página PHP:
<?php //formulário de impostos ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// limpeza do formulário
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//com
}//apagar
</script>
</head>
<body background="/poly/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <?php echo $requête->chkoui ?>>oui
<input type="radio" name="optMarie" value="non" <?php echo $requête->chknon ?>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête->enfants ?>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête->salaire ?>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<?php echo $requête->impots ?>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="submit" value="Calculer"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<?php
// Existem erros?
if(count($requête->erreurs)!=0){
// exibição de erros
echo "<hr>\n<font color=\"red\">\n";
echo "Les erreurs suivantes se sont produites<br>";
echo "<ul>";
for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i]."</li>\n";
}
echo "</ul>\n</font>\n";
}//se
?>
</body>
</html>
A página PHP limita-se a apresentar as informações que lhe são transmitidas pelo programa principal da aplicação na variável $requête, que é um objeto com os seguintes campos:
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 | |
o seu salário anual | |
o montante 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 é repor o formulário ao seu estado inicial: botão desmarcado, campos de preenchimento vazios. Note-se que este resultado não poderia ser obtido com um botão HTML do tipo «reset». Com efeito, quando este tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. Ora, na nossa aplicação, o navegador recebe formulários que podem não estar vazios.
A aplicação que processa o formulário anterior é a seguinte:
<?php
// processa o formulário de impostos
// bibliotecas
include "ImpotsDSN.php";
// configuração da aplicação
ini_set("register_globals","off");
ini_set("display_errors","off");
$formulaireImpots="impots_form.php";
$erreursImpots="impots_erreurs.php";
$bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
// recuperam-se os parâmetros
$requête->marié=$_POST["optMarie"];
$requête->enfants=$_POST["txtEnfants"];
$requête->salaire=$_POST["txtSalaire"];
// temos todos os parâmetros?
if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
// preparação do formulário em branco
$requête->chkoui="";
$requête->chknon="checked";
$requête->enfants="";
$requête->salaire="";
$requête->impots="";
$requête->erreurs=array();
// exibição do formulário
include $formulaireImpots;
// fim
exit(0);
}//if
// verificação dos parâmetros
$requête=vérifier($requête);
// erros?
if(count($requête->erreurs)!=0){
// exibição do formulário
include "$formulaireImpots";
// fim
exit(0);
}//if
// cálculo da resposta
$requête=calculerImpots($bdImpots,$requête);
// erros?
if(count($requête->erreurs)!=0){
// exibição do formulário de erros
include "$erreursImpots";
// fim
exit(0);
}//if
// exibição do formulário
include "$formulaireImpots";
// fim
exit(0);
// --------------------------------------------------------
function vérifier($requête){
// verifica a validade dos parâmetros da consulta
// inicialmente sem erros
$requête->erreurs=array();
// estado válido?
$requête->marié=strtolower($requête->marié);
if($requête->marié!="oui" && $requête->marié!="non"){
// um erro
$requête->erreurs[]="Statut marital [$requête->marié] incorrect";
}
// número de filhos válido?
if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
// um erro
$requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
}
// salário válido?
if(! preg_match("/^\s*\d+\s*$/",$requête->salaire)){
// erro
$requête->erreurs[]="Salaire [$requête->salaire] incorrect";
}
// estado dos botões de opção
if($requête->marié=="oui"){
$requête->chkoui="checked";
$requête->chknon="";
}else{
$requête->chknon="checked";
$requête->chkoui="";
}
// enviar a consulta
return $requête;
}//verificar
// --------------------------------------------------------
function calculerImpots($bdImpots,$requête){
// calcula o montante do imposto
// $bdImpots: dicionário que contém as informações necessárias para ler a fonte de dados ODBC
// $requête: a consulta que contém as informações sobre o contribuinte
// constrói-se um objeto ImpotsDSN
$objImpots=new ImpotsDSN($bdImpots);
// erros?
if(count($objImpots->erreurs)!=0){
// colocam-se os erros na consulta
$requête->erreurs=$objImpots->erreurs;
// concluído
return $requête;
}//if
// cálculo do imposto
$personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
$requête->impots=$objImpots->calculer($personne);
// retornamos o resultado
return $requête;
}//calculerImpots
Comentários:
- Em primeiro lugar, a classe ImpotsDSN está incluída. É esta classe que serve de base ao cálculo dos impostos e que nos irá ocultar o acesso à base de dados
- São efetuadas algumas inicializações com o objetivo de facilitar a manutenção da aplicação. Se os parâmetros se alterarem, os seus valores serão alterados nesta secção. Em geral, estes valores de configuração são normalmente armazenados num ficheiro ou numa base de dados.
- Recuperam-se os três parâmetros do formulário correspondentes aos campos HTML, optMarie, txtEnfants e txtSalaire.
- Estes parâmetros podem estar total ou parcialmente ausentes. Este será, nomeadamente, o caso aquando do pedido inicial do formulário. Nesse caso, limita-se a enviar um formulário vazio.
- Em seguida, verifica-se a validade dos três parâmetros recuperados. Se estes se revelarem incorretos, o formulário é reenviado tal como foi introduzido, mas acompanhado de uma lista de erros.
- Uma vez verificada a validade dos três parâmetros, é possível efetuar o cálculo do imposto. Este é calculado pela função calculerImpots. Esta função cria um objeto do tipo ImpotsDSN e utiliza o método calculer desse objeto.
- A criação do objeto ImpotsDSN pode falhar se a base de dados estiver indisponível. Nesse caso, a aplicação apresenta uma página de erro específica indicando os erros que ocorreram.
- Se tudo correr bem, o formulário é devolvido tal como foi preenchido, mas com a adição do montante do imposto a pagar.
A página de erros é a seguinte:
<?php // página de erro ?>
<html>
<head>
<title>Application impôts indisponible</title>
</head>
<body background="/poly/impots/images/standard.jpg">
<h3>Calcul d'impôts</h3>
<hr>
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<?php
// exibição de erros
for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Eis alguns exemplos de erros. Em primeiro lugar, introduz-se uma palavra-passe incorreta na base de dados:

Introduzem-se dados incorretos no formulário:

Por fim, introduzem-se valores corretos:

4.4. Aplicação Impostos: versão 2
No exemplo anterior, a validade dos parâmetros txtEnfants e txtSalaire do formulário é verificada pelo servidor. Propõe-se aqui verificá-la através de um script JavaScript incluído na página do formulário. É então o navegador que efetua a verificação dos parâmetros. O servidor só é solicitado se estes forem válidos. Desta forma, poupa-se «largura de banda». A página de visualização impots-form.php passa a ter o seguinte aspeto:
<?php //formulário de impostos ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
........
}//apagar
//------------------------------
function calculer(){
// verificação dos parâmetros antes de os enviar para o servidor
with(document.frmImpots){
//número de filhos
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// o modelo não foi verificado
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salário
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// o modelo não foi verificado
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//se
// Está tudo bem — enviamos o formulário para o servidor
submit();
}//com
}//calcular
</script>
</head>
<body background="/poly/impots/images/standard.jpg">
............
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
........
</body>
</html>
De notar as seguintes alterações:
- o botão Calculer já não é do tipo submit, mas sim do tipo button, associado a uma função denominada calculer. É esta função que irá analisar os campos txtEnfants e txTsalaire. Se estiverem corretos, os valores do formulário serão enviados para o servidor (submit); caso contrário, será exibida uma mensagem de erro.
Eis um exemplo de exibição em caso de erro:

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

O programa de visualização da página acima é o seguinte:
<?php //formulário de impostos ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
...
}//apagar
//------------------------------
function calculer(){
...
}//calcular
</script>
</head>
<body background="/poly/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <?php echo $requête[chkoui] ?>>oui
<input type="radio" name="optMarie" value="non" <?php echo $requête[chknon] ?>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête[enfants] ?>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête[salaire] ?>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<?php echo $requête[impots] ?>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<?php
// existem erros?
if(count($requête[erreurs])!=0){
// exibição de erros
echo "<hr>\n<font color=\"red\">\n";
echo "Les erreurs suivantes se sont produites<br>";
echo "<ul>";
for($i=0;$i<count($requête[erreurs]);$i++){
echo "<li>".$requête[erreurs][$i]."</li>\n";
}
echo "</ul>\n</font>\n";
}//if
// existem simulações?
else if(count($requête[simulations])!=0){
// exibição das simulações
echo "<hr>\n<h2>Résultats des simulations</h2>\n";
echo "<table border=\"1\">\n";
echo "<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>\n";
for($i=0;$i<count($requête[simulations]);$i++){
echo "<tr>".
"<td>".$requête[simulations][$i][0]."</td>".
"<td>".$requête[simulations][$i][1]."</td>".
"<td>".$requête[simulations][$i][2]."</td>".
"<td>".$requête[simulations][$i][3]."</td>".
"</tr>\n";
}//for
echo "</table>\n";
}//if
?>
</body>
</html>
O programa de visualização apresenta pequenas diferenças em relação à sua versão anterior:
- o dicionário recebe um dicionário $requête em vez de um objeto $requête. Por isso, escreve-se $requête[enfants] e não $requête->filhos.
- Este dicionário tem um campo simulations, que é uma matriz bidimensional. $requête[simulations][i] é a simulação n.º i. Esta é, por sua vez, uma matriz de quatro cadeias de caracteres: estado civil, número de filhos, salário anual, imposto a pagar.
O programa principal, por sua vez, sofreu alterações mais profundas:
<?php
// processa o formulário de impostos
// bibliotecas
include "ImpotsDSN.php";
// início da sessão
session_start();
// configuração da aplicação
ini_set("register_globals","off");
ini_set("display_errors","off");
$formulaireImpots="impots_form.php";
$erreursImpots="impots_erreurs.php";
$bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
// recuperar os parâmetros da sessão
$session=$_SESSION["session"];
// sessão válida?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// inicia-se uma nova sessão
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// Existem erros?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// exibição da página de erros
include $erreursImpots;
// fim
$session=array();
terminerSession($session);
}//se
}//se
// recuperam-se os parâmetros da troca em curso
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// temos todos os parâmetros?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// exibição do formulário vazio
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// fim
terminerSession($session);
}//if
// verificação dos parâmetros
$requête=vérifier($requête);
// há erros?
if(count($requête[erreurs])!=0){
// exibição do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
}//if
// cálculo do imposto a pagar
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// mais uma simulação
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// exibição do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
// --------------------------------------------------------
function vérifier($requête){
// verifica a validade dos parâmetros da consulta
// Inicialmente, sem erros
$requête[erreurs]=array();
// estado válido?
$requête[marié]=strtolower($requête[marié]);
if($requête[marié]!="oui" && $requête[marié]!="non"){
// um erro
$requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
}
// número de filhos válido?
if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
// um erro
$requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
}
// salário válido?
if(! preg_match("/^\s*\d+\s*$/",$requête[salaire])){
// erro
$requête[erreurs][]="Salaire [$requête[salaire]] incorrect";
}
// estado dos botões de opção
if($requête[marié]=="oui"){
$requête[chkoui]="checked";
$requête[chknon]="";
}else{
$requête[chknon]="checked";
$requête[chkoui]="";
}
// enviar a consulta
return $requête;
}//verificar
// --------------------------------------------------------
function terminerSession($session){
// guardamos a sessão
$_SESSION[session]=$session;
// fim do script
exit(0);
}//terminerSession
- Em primeiro lugar, esta aplicação gere uma sessão. Por isso, inicia-se uma sessão no início do script:
- A sessão armazena uma única variável: o dicionário $session. Este dicionário tem dois campos:
- objImpots: um objeto ImpotsDSN. Este objeto é armazenado na sessão do cliente para evitar acessos sucessivos desnecessários à base de dados ODBC.
- simulações: a tabela de simulações para reter, de uma transação para outra, as simulações das transações anteriores.
- Assim que a sessão for iniciada, recupera-se a variável $session. Se esta ainda não existir, significa que a sessão está a ser iniciada. Cria-se então um objeto ImpotsDSN e uma tabela de simulações vazia. Se necessário, são sinalizados erros.
<?php
...
// recuperar os parâmetros da sessão
$session=$_SESSION["session"];
// sessão válida?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// inicia-se uma nova sessão
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// Existem erros?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// exibição da página de erros
include $erreursImpots;
// fim
$session=array();
terminerSession($session);
}//se
}//if
- Assim que a sessão for corretamente inicializada, os parâmetros da troca são recuperados. Se não estiverem presentes todos os parâmetros esperados, é enviado um formulário vazio.
<?php
...
// recuperam-se os parâmetros da troca em curso
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// temos todos os parâmetros?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// exibição do formulário vazio
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// fim
terminerSession($session);
}//if
- Se todos os parâmetros estiverem corretos, são verificados. Se houver erros, estes são assinalados:
<?php
...
// verificação dos parâmetros
$requête=vérifier($requête);
// há erros?
if(count($requête[erreurs])!=0){
// exibição do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
}//if
- Se os parâmetros estiverem corretos, o imposto é calculado:
<?php
...
// cálculo do imposto a pagar
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
- A simulação em curso é adicionada à tabela de simulações, que é enviada ao cliente juntamente com o formulário:
<?php
...
// mais uma simulação
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// exibição do formulário
include "$formulaireImpots";
// fim
terminerSession($session);
- Em todos os casos, o script termina gravando a variável $session na sessão. Isto é feito no procedimento terminerSession.
<?php
...
function terminerSession($session){
// guardar a sessão
$_SESSION[session]=$session;
// fim do script
exit(0);
}//terminerSession
Concluímos apresentando o programa de visualização de erros de acesso à base de dados (impots-erreurs.php):
<?php // página de erro ?>
<html>
<head>
<title>Application impôts indisponible</title>
</head>
<body background="/poly/impots/images/standard.jpg">
<h3>Calcul d'impôts</h3>
<hr>
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<?php
// exibição de erros
for($i=0;$i<count($requête[erreurs]);$i++){
echo "<li>".$requête[erreurs][$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Também aqui, o objeto $requête foi substituído por um dicionário $requête.
4.6. Aplicação Impostos: versão 4
Vamos agora criar uma aplicação autónoma que será um cliente web da aplicação web impots anterior. A aplicação será uma aplicação de consola iniciada a partir de uma janela DOS:
dos>e:\php43\php.exe cltImpots.php
Syntaxe cltImpots.php urlImpots marié enfants salaire [jeton]
Os parâmetros do cliente web cltImpots são os seguintes:
// sintaxe $0 urlImpots casado filhos salário token
// cliente de um serviço fiscal que funciona em urlImpots
// envia a este serviço as três informações: casado, filhos, salário
// eventualmente com o token de sessão, caso este tenha sido transmitido
// exibe a tabela de simulações da sessão
Eis alguns exemplos de utilização. Em primeiro lugar, um primeiro exemplo sem token de sessão.
dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php sim 2 200000
Jeton de session=[a6297317667bc981c462120987b8dd18]
Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
Recuperámos corretamente o montante do imposto a pagar (22504 f). Também recuperámos o token de sessão. Podemos agora utilizá-lo para uma segunda consulta:
dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php sim 3 200000 a6297317667bc981c462120987b8dd18
Jeton de session=[a6297317667bc981c462120987b8dd18]
Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
[oui,3,200000,16400]
Conseguimos, de facto, obter a tabela das simulações enviadas pelo servidor web. O token recuperado mantém-se, naturalmente, o mesmo. Se consultarmos o serviço web quando a base de dados ainda não foi iniciada, obtemos o seguinte resultado:
dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php sim 3 2 000 000
Jeton de session=[8369014d5053212bc42f64bbdfb152ee]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)
Ao programar um cliente web, é necessário saber exatamente o que o servidor envia em resposta às diferentes solicitações possíveis de um cliente. O servidor envia um conjunto de linhas HTML que inclui informações úteis e outras que servem apenas para a formatação HTML. As expressões regulares de PHP podem ajudar-nos a identificar as informações úteis no fluxo de linhas enviadas pelo servidor. Para tal, precisamos de conhecer o formato exato das diferentes respostas do servidor. Para isso, podemos consultar o serviço web através de um navegador e analisar o código-fonte que foi enviado. Note-se que este método não permite conhecer os cabeçalhos HTTP da resposta do servidor web. Por vezes, é útil conhecê-los. Nesse caso, podemos utilizar um dos dois clientes web genéricos apresentados no capítulo anterior.
Se repetirmos os exemplos anteriores, eis o código HTML recebido pelo navegador para a tabela de simulações:
<h2>Résultats des simulations</h2>
<table border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
<tr><td>oui</td><td>3</td><td>200000</td><td>16400</td></tr>
</table>
Trata-se de uma tabela HTML, a única presente no documento enviado. Assim, a expressão regular "|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|" deve permitir localizar no documento as diferentes simulações recebidas. Quando a base de dados está indisponível, recebe-se o seguinte documento:
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<li>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</li>
</ul>
</font>
Para recuperar o erro, o cliente web deverá procurar na resposta do servidor web as linhas que correspondam ao modelo: "|<li>(.+?)</li>|".
Temos agora as orientações sobre o que fazer quando o utilizador solicitar o cálculo do imposto a partir do cliente de consola anterior:
- verificar se todos os parâmetros são válidos e, se necessário, sinalizar os erros.
- ligar-se ao URL fornecido como primeiro parâmetro. Para tal, seguiremos o modelo do cliente web genérico já apresentado e analisado
- no fluxo da resposta do servidor, utilizar expressões regulares para:
- identificar as mensagens de erro
- encontrar os resultados das simulações
O código do cliente cltImpots.php é o seguinte:
<?php
// sintaxe $0 urlImpots casado filhos salário token
// cliente de um serviço fiscal que funciona no urlImpots
// envia a este serviço as três informações: estado civil, filhos, salário
// eventualmente com o token de sessão, caso este tenha sido transmitido
// exibe a tabela de simulações da sessão
// verificação do número de argumentos
if(count($argv)!=5 && count($argv)!=6){
// erro
fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
// fim
exit(1);
}//se
// regista-se o URL
$urlImpots=analyseURL($argv[1]);
if(isset($urlImpots[erreur])){
// erro
fwrite(STDERR,"$urlImpots[erreur]\n");
// fim
exit(1);
}//se
// análise do pedido do utilizador
$demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
// erros?
if($demande[erreur]){
// mensagem
echo "$demande[erreur]\n";
// terminado
exit(1);
}//if
// é possível efetuar o pedido — utiliza-se o token de sessão
$impots=getImpots($urlImpots,$demande,$argv[5]);
// exibimos o token de sessão
echo "Jeton de session=[$impots[jeton]]\n";
// erros?
if(count($impots[erreurs])!=0){
//mensagens de erro
echo "Les erreurs suivantes se sont produites :\n";
for($i=0;$i<count($impots[erreurs]);$i++){
echo $impots[erreurs][$i]."\n";
}//para
}else{
// sem erros — exibição das simulações
echo "Simulations :\n";
for($i=0;$i<count($impots[simulations]);$i++){
echo "[".implode(",",$impots[simulations][$i])."]\n";
}//para
}//se
// fim do programa
exit(0);
// --------------------------------------------------------------
function analyseURL($URL){
// analisa a validade do URL $URL
$url=parse_url($URL);
// o protocolo
if(strtolower($url[scheme])!="http"){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//se
// a máquina
if(! isset($url[host])){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//se
// a porta
if(! isset($url[port])) $url[port]=80;
// o pedido
if(isset($url["query"])){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// retorno
return $url;
}//analyseURL
// -----------------------------------------------------------
function analyseDemande($demande){
// $demande: cadeia a analisar
// deve ter o formato «casado, filhos, salário»
// formato válido
if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
// formato inválido
return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
}
// está correto
$marié=strtolower($champs[1]);
return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
}//analyseDemande
// --------------------------------------------------------------
function getImpots($urlImpots,$demande,$jeton){
// $urlImpots: URL a verificar
// $demande: dicionário que contém os campos «marie», «enfants» e «salaire»
// $jeton: um eventual token de sessão
// abertura de uma ligação na porta $urlImpots[port] de $urlImpots[host]
$connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
// retorno em caso de erro
if(! $connexion){
return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
}//se
// envia-se os cabeçalhos HTTP para o servidor
POST($connexion,$urlImpots,$jeton,
array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
// lê-se a resposta do servidor — primeiro os cabeçalhos HTTP
// a primeira linha
$ligne=fgets($connexion,10000);
// URL encontrada?
if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
// URL inacessível — resposta com erro
return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
}//se
// leem-se os restantes cabeçalhos HTTP
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// procura do token, caso ainda não tenha sido encontrado
if(! $jeton){
// procura da linha «set-cookie»
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// encontrou-se o token — guarda-se
$jeton=$champs[1];
}//se
}//se
}//linha seguinte
// lê-se o documento seguinte
$document="";
while($ligne=fread($connexion,10000)){
$document.=$ligne;
}//enquanto
// encerra-se a ligação
fclose($connexion);
// analisa-se o documento
$impots=getInfos($document);
// retorna-se o resultado
return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
}//getImpots
// --------------------------------------------------------------
function POST($connexion,$url,$jeton,$paramètres){
// $connexion: ligação ao servidor web
// $url: o URL a consultar
// $paramètres: dicionário dos parâmetros a enviar
// prepara-se o POST
$post="";
while(list($paramètre,$valeur)=each($paramètres)){
$post.=$paramètre."=".urlencode($valeur)."&";
}//enquanto
// remove-se o último carácter
$post=substr($post,0,-1);
// prepara-se a solicitação HTTP no formato HTTP/1.0
$HTTP="POST $url[path] HTTP/1.0\n";
$HTTP.="Content-type: application/x-www-form-urlencoded\n";
$HTTP.="Content-length: ".strlen($post)."\n";
$HTTP.="Connection: close\n";
if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
$HTTP.="\n";
$HTTP.=$post;
// envia-se a solicitação HTTP
fwrite($connexion,$HTTP);
}//POST
// --------------------------------------------------------------
function getInfos($document){
// $document: documento HTML
// procura-se, ou a lista de erros
// ou a tabela de simulações
// preparação do resultado
$impots[erreurs]=array();
$impots[simulations]=array();
// existe algum erro?
// modelo da linha de erro
$modErreur="|<li>(.+?)</li>|";
// pesquisa no documento
if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
// recuperação de erros
for($i=0;$i<count($champs);$i++){
$impots[erreurs][]=$champs[$i][1];
}//para
// concluído
return $impots;
}//se
// modelo de uma linha da tabela de simulações
// <tr><td>sim</td><td>2</td><td>200000</td><td>22504</td></tr>
$modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
// pesquisa no documento
if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){
// recuperação das simulações
for($i=0;$i<count($champs);$i++){
$impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
}//para
// retorno do resultado
return $impots;
}//if
// não é normal chegar aqui
return $impots;
}//getInfos
?>
Comentários:
- o programa começa por verificar a validade dos parâmetros que recebeu. Para tal, utiliza duas funções: a analyseURL, que verifica a validade da URL, e a analyseDemande, que verifica os restantes parâmetros.
- O cálculo do imposto é efetuado pela função getImpots:
- A função getImpots é declarada da seguinte forma:
<?php
...
// --------------------------------------------------------------
function getImpots($urlImpots,$demande,$jeton){
// $urlImpots: URL a consultar
// $demande: dicionário que contém os campos «marie», «enfants» e «salaire»
// $jeton: um eventual token de sessão
- (continuação)
- $urlImpots é um dicionário que contém os seguintes campos:
- host: máquina onde reside o serviço web
- port: a sua porta de serviço
- path: o caminho do recurso solicitado
- $demande é um dicionário que contém os seguintes campos:
- casado: sim/não: estado civil
- filhos: número de filhos
- salário: salário anual
- $jeton é o token de sessão.
- $urlImpots é um dicionário que contém os seguintes campos:
A função devolve como resultado um dicionário com os seguintes campos:
- erros: matriz de mensagens de erro
- simulações: matriz de simulações, sendo que cada simulação é, por sua vez, uma matriz de quatro elementos (casado, filhos, salário, imposto)
- token: o token de sessão
- Assim que o resultado de getImpots for obtido, o programa apresenta os resultados e termina:
<?php
...
// é apresentado o token de sessão
echo "Jeton de session=[$impots[jeton]]\n";
// erros?
if(count($impots[erreurs])!=0){
//mensagens de erro
echo "Les erreurs suivantes se sont produites :\n";
for($i=0;$i<count($impots[erreurs]);$i++){
echo $impots[erreurs][$i]."\n";
}//para
}else{
// sem erros — exibição das simulações
echo "Simulations :\n";
for($i=0;$i<count($impots[simulations]);$i++){
echo "[".implode(",",$impots[simulations][$i])."]\n";
}//para
}//se
// fim do programa
exit(0);
- Vamos agora analisar a função getImpots($urlImpots,$demande,$jeton), que deve
- criar uma ligação TCP-IP na porta $urlImpots[port] da máquina $urlImpots[host]
- enviar ao servidor web os cabeçalhos HTTP que este espera, nomeadamente o token de sessão, caso exista
- enviar ao servidor web um pedido POST com os parâmetros contidos no dicionário $demande
- analisar a resposta do servidor web para encontrar nela uma lista de erros ou uma tabela de simulações.
- O envio dos cabeçalhos HTTP é efetuado através da função POST:
<?php
...
// envio dos cabeçalhos HTTP para o servidor
POST($connexion,$urlImpots,$jeton,
array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
- Depois de enviados os cabeçalhos HTTP, a função getImpots lê a resposta do servidor na íntegra e coloca-a em $document. A resposta é lida sem ser analisada, exceto a primeira linha, que nos permite saber se o servidor web encontrou ou não o URL que lhe foi solicitado. Com efeito, se o URL tiver sido encontrado, o servidor web responde HTTP/1.X 200 OK, em que X depende da versão do HTTP utilizada.
<?php
...
// URL encontrada?
if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
<?php
...
// URL inacessível — retorno com erro
return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
}//se
- O documento é analisado através da função getInfos:
- O resultado obtido é um dicionário com dois campos:
- erros: lista de erros — pode estar vazia
- simulações: lista de simulações — pode estar vazia
- Assim, a função getImpots pode apresentar o seu resultado sob a forma de um dicionário.
<?php
...
// retornamos o resultado
return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
- Analisemos agora a função POST:
<?php
...
// --------------------------------------------------------------
function POST($connexion,$url,$jeton,$paramètres){
// $connexion: a ligação ao servidor web
// $url: o URL a consultar
// $paramètres: dicionário dos parâmetros a enviar
// prepara-se o POST
$post="";
while(list($paramètre,$valeur)=each($paramètres)){
$post.=$paramètre."=".urlencode($valeur)."&";
}//enquanto
// remove-se o último carácter
$post=substr($post,0,-1);
// prepara-se a solicitação HTTP no formato HTTP/1.0
$HTTP="POST $url[path] HTTP/1.0\n";
$HTTP.="Content-type: application/x-www-form-urlencoded\n";
$HTTP.="Content-length: ".strlen($post)."\n";
$HTTP.="Connection: close\n";
if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
$HTTP.="\n";
$HTTP.=$post;
// envia-se a solicitação HTTP
fwrite($connexion,$HTTP);
}//POST
Embora, no âmbito do programa, já tivéssemos tido a oportunidade de solicitar um recurso web através de um GET, ainda não tínhamos tido a oportunidade de o fazer com um POST. O POST difere do GET na forma como envia parâmetros ao servidor. Deve enviar os seguintes cabeçalhos HTTP:
com
- chemin: caminho do recurso web solicitado, neste caso $url[path]
- HTTP/1.X: o protocolo HTTP pretendido. Aqui, optou-se por HTTP/1.0 para obter a resposta num único bloco. HTTP/1.1 permite o envio da resposta em vários blocos (chunked).
- N indica o número de caracteres que o cliente se prepara para enviar ao servidor
Os N caracteres que constituem os parâmetros da consulta são enviados imediatamente a seguir à linha em branco que encerra os cabeçalhos HTTP enviados ao servidor. Estes parâmetros têm o formato param1=val1¶m2=val2&..., em que parami é o nome do parâmetro e vali é o seu valor. Os valores válidos podem conter caracteres «incómodos», tais como espaços, o caractere &, o caractere =, etc. Estes caracteres devem ser substituídos por uma cadeia %XX, em que XX é o seu código hexadecimal. A função PHP urlencode realiza esta tarefa. O processo inverso é realizado pela função urldecode.
Por fim, note-se que, se houver um token, este é enviado com um cabeçalho HTTP Cookie: PHPSESSID=token.
- Resta-nos agora analisar a função que analisa a resposta do servidor:
<?php
...
// --------------------------------------------------------------
function getInfos($document){
// $document: documento HTML
// procura-se, ou a lista de erros
// ou a tabela de simulações
// preparação do resultado
$impots[erreurs]=array();
$impots[simulations]=array();
- Recorde-se que os erros são enviados pelo servidor na forma <li>mensagem de erro</li>. A expressão regular «|<li>(.+?)</li>|» deve permitir recuperar estas informações. Aqui, utilizámos o sinal | para delimitar a expressão regular, em vez do sinal /, uma vez que este também existe na própria expressão regular. A função preg_match_all ($modèle, $document, $champs, PREG_SET_ORDER) permite recuperar todas as ocorrências de $modèle encontradas em $document. Estas são colocadas na tabela $champs. Assim, $champs[i] representa a ocorrência n.º i de $modèle em $document. Em $champs[$i][0] é colocada a cadeia correspondente ao modelo. Se este tivesse parênteses, a cadeia correspondente ao primeiro parêntese é colocada em $champs[$i][1], a segunda em $champs[$i][2] e assim sucessivamente. O código para recuperar os erros será, portanto, o seguinte:
<?php
...
// existe algum erro?
// modelo da linha de erro
$modErreur="|<li>(.+?)</li>|";
// pesquisa no documento
if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
// recuperação de erros
for($i=0;$i<count($champs);$i++){
$impots[erreurs][]=$champs[$i][1];
}//para
// concluído
return $impots;
}//if
4.7. Aplicação de Impostos: Conclusão
Mostrámos diferentes versões da nossa aplicação cliente-servidor para o cálculo de impostos:
- versão 1: o serviço é prestado por um conjunto de programas PHP; o cliente é um navegador. Realiza uma única simulação e não guarda um registo das anteriores.
- versão 2: adicionam-se algumas funcionalidades do lado do navegador, inserindo scripts JavaScript no documento HTML carregado pelo mesmo. Este controla a validade dos parâmetros do formulário.
- versão 3: permite-se que o serviço se lembre das diferentes simulações realizadas por um cliente através da gestão de uma sessão. A interface HTML é alterada em conformidade para as apresentar.
- versão 4: o cliente é agora uma aplicação de consola autónoma. Isto permite-nos voltar à programação de clientes web.
Nesta altura, podemos fazer algumas observações:
- as versões 1 a 3 permitem a utilização de navegadores sem outras capacidades além da de executar scripts JavaScript. Note-se que um utilizador tem sempre a possibilidade de inibir a execução destes. A aplicação funcionará então apenas parcialmente na sua versão 1 (a opção «Apagar» não funcionará) e de todo nas suas versões 2 e 3 (as opções «Apagar» e «Calcular» não funcionarão). Poderia ser interessante prever uma versão do serviço que não utilize scripts JavaScript.
Ao desenvolver um serviço Web, é necessário questionar-se sobre os tipos de clientes a que se destina. Se se pretender atingir o maior número possível de clientes, deve-se criar uma aplicação que envie aos navegadores apenas HTML (sem JavaScript nem applets). Se estivermos a trabalhar numa intranet e tivermos controlo sobre a configuração dos terminais da mesma, podemos então dar-nos ao luxo de ser mais exigentes em relação ao cliente.
A versão 4 é um cliente web que recupera a informação de que necessita no fluxo HTML enviado pelo servidor. Muitas vezes, não temos controlo sobre esse fluxo. É o que acontece quando se desenvolve um cliente para um serviço web existente na rede e gerido por outra entidade. Vejamos um exemplo. Suponhamos que o nosso serviço de simulações de cálculo de impostos tenha sido desenvolvido pela empresa X. Atualmente, o serviço envia as simulações numa tabela HTML e o nosso cliente aproveita esse facto para as recuperar. Assim, compara cada linha da resposta do servidor com a expressão regular:
// modelo de uma linha da tabela de simulações
// <tr><td>sim</td><td>2</td><td>200000</td><td>22504</td></tr>
$modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
Suponhamos agora que o programador da aplicação altere a apresentação visual da resposta, colocando as simulações não numa tabela, mas numa lista com o seguinte formato:
Neste caso, o nosso cliente web terá de ser reescrito. É esta a ameaça permanente que paira sobre os clientes web de aplicações que não controlamos diretamente. O XML pode oferecer uma solução para este problema:
- em vez de gerar HTML, o serviço de simulações irá gerar XML. No nosso exemplo, isso poderia ser
<simulations>
<entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
<simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
<simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
- pode ser associada a esta resposta uma folha de estilo que indique aos navegadores a forma visual a atribuir a esta resposta XML
- os clientes web programados ignorariam essa folha de estilo e recuperariam a informação diretamente do fluxo XML da resposta
Se o criador do serviço pretender alterar a apresentação visual dos resultados que fornece, modificará a folha de estilo e não o XML. Graças à folha de estilo, os navegadores apresentarão a nova apresentação visual e os clientes web programados não terão de ser alterados.
