3. Introdução à programação web em PHP
3.1. Programação PHP
Vale lembrar aqui que PHP é uma linguagem de programação por si só e que, embora seja utilizada principalmente no desenvolvimento de aplicativos para a web, pode ser empregada em outros contextos. O documento “PHP por meio de exemplos”, disponível em http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf, apresenta os fundamentos da linguagem. Presume-se aqui que esses conceitos já tenham sido assimilados. Vamos mostrar, com um exemplo simples, o procedimento para executar um programa PHP no Windows. O código a seguir foi salvo com o nome coucou.php.
A execução deste programa é feita em uma janela do DOS do Windows:
dos>"e:\program files\easyphp\php\php.exe" coucou.php
X-Powered-By: PHP/4.3.0-dev
Content-type: text/html
coucou
Observe-se que o interpretador PHP envia, por padrão:
- o interpretador PHP é o php.exe e normalmente está localizado no diretório <php> de instalação do software.
- os cabeçalhos HTTP X-Powered-By e Content-type:
- a linha vazia que separa os cabeçalhos HTTP do restante do documento
- o documento formado aqui pelo texto gerado pela função echo
3.2. O arquivo de configuração do interpretador PHP
O comportamento do interpretador PHP é definido por um arquivo de configuração chamado php.ini e armazenado, no Windows, no próprio diretório do Windows. Trata-se de um arquivo de tamanho considerável, já que, no Windows e para a versão 4.2 do PHP, ele tem cerca de 1.000 linhas, das quais, felizmente, três quartos são comentários. Vamos examinar alguns dos atributos de configuração do PHP:
permite incluir instruções entre as tags <? >. No off, elas deveriam ser incluídas entre <?php ... > | |
no on permite usar a sintaxe <% =variável %> utilizada pela tecnologia ASP (Active Server Pages) | |
permite o envio do cabeçalho HTTP X-Powered-By: PHP/4.3.0-dev. Em off, esse cabeçalho é removido. | |
define o escopo do monitoramento de erros. Aqui, todos os erros (E_ALL), exceto os avisos durante a execução (~E_NOTICE), serão sinalizados | |
em on, insere os erros no fluxo HTML enviado ao cliente. Assim, eles são exibidos no navegador. Recomenda-se definir essa opção como off. | |
os erros serão armazenados em um arquivo | |
armazena o último erro ocorrido na variável $php_errormsg | |
define o arquivo de armazenamento de erros (se log_errors=on) | |
em on, algumas variáveis tornam-se globais. Considerado uma falha de segurança. | |
gera, por padrão, o cabeçalho HTTP: Content-type: text/html | |
a lista de diretórios que serão vasculhados em busca dos arquivos exigidos pelas diretivas include ou require | |
o diretório onde serão salvos os arquivos que armazenam as diferentes sessões em andamento. O disco em questão é aquele onde o PHP foi instalado. Aqui, /temp refere-se ao e:\temp |
Este arquivo de configuração influencia a portabilidade do programa PHP escrito. De fato, se um aplicativo web precisar recuperar o valor de um campo C de um formulário web, ele poderá fazê-lo de diversas maneiras, dependendo se a variável de configuração register_globals tiver o valor on ou off:
- off: o valor será recuperado por $HTTP_GET_VARS["C"] ou _GET["C"] ou $HTTP_POST_VARS["C"] ou $_POST["C"], dependendo do método (GET/POST) utilizado pelo cliente para enviar os valores do formulário
- : igual ao acima, mais $C, pois o valor do campo C foi definido como global em uma variável com o mesmo nome do campo
Se um desenvolvedor escrever um programa utilizando a notação $C porque o servidor web/PHP que ele utiliza atribui à variável register_globals o valor de on, esse programa não funcionará mais se for migrado para um servidor web/PHP em que essa mesma variável seja off. Portanto, deve-se procurar escrever programas evitando o uso de funcionalidades que dependam da configuração do servidor web/PHP.
3.3. Configurar PHP durante a execução
Para melhorar a portabilidade de um programa PHP, é possível definir manualmente algumas das variáveis de configuração do PHP. Essas variáveis são alteradas durante a execução do programa e apenas para ele. Duas funções são úteis nesse processo:
retorna o valor da variável de configuração confVariable | |
define o valor da variável de configuração confVariable |
Aqui está um exemplo em que se define o valor da variável de configuração track_errors:
<?php
// valor da variável de configuração track_errors
echo "track_errors=".ini_get("track_errors")."\n";
// alteração desse valor
ini_set("track_errors","off");
// verificação
echo "track_errors=".ini_get("track_errors")."\n";
?>
Ao executar, obtêm-se os seguintes resultados:
E:\data\serge\web\php\poly\intro>"E:\Program Files\EasyPHP\php\php.exe" conf1.php
Content-type: text/html
track_errors=1
track_errors=off
O valor da variável de configuração track_errors era inicialmente 1 (~on). Ele foi alterado para off. É importante lembrar que, se nossa aplicação precisar se basear em determinados valores das variáveis de configuração, é recomendável inicializá-las no próprio programa.
3.4. Contexto de execução dos exemplos
Os exemplos desta apostila serão executados com a seguinte configuração:
- PC no Windows 2000
- servidor Apache 1.3
- PHP 4.3
A configuração do servidor Apache é feita no arquivo httpd.conf. As linhas a seguir instruem o Apache a carregar o PHP como um módulo integrado ao Apache e a encaminhar ao interpretador PHP qualquer solicitação direcionada a um documento com determinados sufixos, incluindo o .php. Esse é o sufixo padrão que usaremos para nossos programas PHP.
LoadModule php4_module "E:/Program Files/EasyPHP/php/php4apache.dll"
AddModule mod_php4.c
AddType application/x-httpd-php .phtml .pwml .php3 .php4 .php .php2 .inc
Além disso, definimos para o Apache um alias “poly”:
Alias "/poly/" "e:/data/serge/web/php/poly/"
<Directory "e:/data/serge/web/php/poly">
Options Indexes FollowSymLinks Includes
AllowOverride All
#Ordem: permitir, negar
Allow from all
</Directory>
Vamos chamar de <poly> o caminho e:/data/serge/web/php/poly. Se quisermos solicitar, por meio de um navegador, o documento doc.php ao servidor Apache, usaremos o http://localhost/poly/doc.php URL. O servidor Apache reconhecerá no URL o alias poly e, então, associará o URL /poly/doc.php ao documento <poly>\doc.php.
3.5. Um primeiro exemplo
Vamos escrever um primeiro aplicativo web/PHP. O texto a seguir está gravado no arquivo heure.php:
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php
$maintenant=time();
echo date("j/m/y, h:i:s",$maintenant);
?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Se acessarmos essa página com um navegador, obtemos o seguinte resultado:

A parte dinâmica da página foi gerada pelo código PHP:
O que aconteceu exatamente? O navegador solicitou o arquivo URL http://localhost/poly/intro/heure.php. O servidor web (no exemplo, o Apache) recebeu essa solicitação e detectou, devido ao sufixo .php do documento solicitado, que deveria encaminhar essa solicitação ao interpretador PHP. Este, então, analisa o documento heure.php e executa todas as partes de código localizadas entre as tags <?php > e substitui cada uma delas pelas linhas escritas pelas instruções PHP, echo ou print. Assim, o interpretador PHP executará a parte de código acima e a substituirá pela linha escrita pela instrução echo:
Depois que todas as partes do código PHP foram executadas, o documento PHP tornou-se um simples documento HTML, que é então enviado ao cliente.
Procurar-se-á evitar ao máximo misturar o código PHP com o código HTML. Para isso, seria possível reescrever a aplicação anterior da seguinte maneira:
<!-- código PHP -->
<?php
// obtém-se a hora atual
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- código HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
O resultado obtido no navegador é idêntico:

A segunda versão é melhor do que a primeira, pois há menos código PHP no código HTML. Assim, a estrutura da página fica mais visível. É possível ir além, colocando o código PHP e o código HTML em dois arquivos diferentes. O código PHP está armazenado no arquivo heure3.php:
<!-- código PHP -->
<?php
// recupera-se a hora atual
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
// exibe-se a resposta
include "heure3-page1.php";
?>
O código HTML está armazenado no arquivo heure3-page1.php:
<!-- código HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Quando o navegador solicitar o documento heure3.php, ele será carregado e analisado pelo interpretador PHP. Ao encontrar a linha
O interpretador incluirá o arquivo heure3-page1.php no código-fonte de heure3.php e o executará. Assim, tudo ocorre como se tivéssemos o seguinte código PHP:
<!-- código PHP -->
<?php
// recupera-se a hora atual
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- código HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
O resultado obtido é o mesmo de antes:

A solução de colocar os códigos PHP e HTML em arquivos diferentes será adotada posteriormente. Ela apresenta diversas vantagens:
- a estrutura das páginas enviadas ao cliente não fica obscurecida pelo código PHP. Assim, elas podem ser mantidas por um “web designer” com conhecimentos gráficos, mas com pouca experiência em PHP.
- o código PHP funciona como uma “interface” para as solicitações dos clientes. Seu objetivo é calcular os dados necessários para a página que será enviada em resposta ao cliente.
No entanto, a solução apresenta uma desvantagem: em vez de exigir o carregamento de um único documento, ela exige o carregamento de vários documentos, o que pode resultar em perda de desempenho.
3.6. Recuperar os parâmetros enviados por um cliente web
3.6.1. por meio de um POST
Consideremos o seguinte formulário, no qual o usuário deve fornecer duas informações: um nome e uma idade.

Quando o usuário preenche os campos Nom e Age, ele clica no botão Envoyer, que é do tipo submit. Os valores do formulário são então enviados ao servidor. Este retorna o formulário, além de uma tabela listando os valores que recebeu:

O navegador solicita o formulário ao aplicativo nomage.php a seguir:
<?php
// os parâmetros esperados foram obtidos?
$post=isset($_POST["txtNom"]) && isset($_POST["txtAge"]);
if($post){
// recuperam-se os parâmetros txtNom e txtAge “enviados” pelo cliente
$nom=$_POST["txtNom"];
$age=$_POST["txtAge"];
} else {
$nom="";
$age="";
}//se
// exibição da página
include "nomage-p1.php";
?>
Algumas explicações:
- um campo de formulário HTML, denominado “campo”, pode ser enviado ao servidor pelo método GET ou pelo método POST. Se for enviado pelo método GET, o servidor pode recuperá-lo na variável $_GET["champ"] e na variável $_POST["champ"], casose for enviado pelo método POST.
- A existência de um dado pode ser verificada com a função isset(dado), que retorna true se o dado existir e false caso contrário.
- A aplicação nomage.php cria três variáveis: $nom para o nome do formulário, $age para a idade e $post para indicar se houve valores “enviados” ou não. Essas três variáveis são transmitidas para a página nomage-p1.php. Observe que, embora essa página participe da elaboração da resposta ao cliente, ele não tem conhecimento disso. Para ele, é a aplicação nomage.php que lhe responde.
- Na primeira vez que um cliente solicita a aplicação nomage.php, temos $post em falso. De fato, nessa primeira chamada, nenhum valor do formulário é transmitido ao servidor.
A página nomage-p1.php é a seguinte:
<html>
<head>
<title>Formulaire web</title>
</head>
<body>
<center>
<h3>Un formulaire Web</h3>
<h4>Récupération des valeurs des champs d'un formulaire</h4>
<hr>
<form name="frmPersonne" method="post">
<table>
<tr>
<td>Nom</td>
<td><input type="text" value="<?php echo $nom ?>" name="txtNom" size="20"></td>
<td>Age</td>
<td><input type="text" value="<?php echo $age ?>" name="txtAge" size="3"></td>
<tr>
</table>
<input type="submit" name="cmdEffacer" value="Envoyer">
</form>
</center>
<hr>
<?php
// houve valores enviados?
if ($post) {
?>
<h4>Valeurs récupérées</h4>
<table border="1">
<tr>
<td>Nom</td><td><?php echo $nom ?></td>
<td width="10"></td>
<td>Age</td><td><?php echo $age ?></td>
<tr>
</table>
<?php } ?>
</body>
</html>
O aplicativo nomage-p1.php apresenta o formulário frmPersonne. Este é definido pela tag:
Como o atributo action da tag não está definido, o navegador transmitirá os dados do formulário para o URL que ele consultou para obtê-los, ou seja, o c.a.d. aplicativo nomage.php.
Distinguamos os dois casos de chamada do aplicativo nomage.php:
- É a primeira vez que o usuário a chama. A aplicação nomage.php, portanto, chama a aplicação nomage-p1.php, fornecendo-lhe os valores ($nom,$age,$post)=("","",falso). A aplicação nomage-p1.php exibe, então, um formulário vazio.
- O usuário preenche o formulário e utiliza o botão Envoyer (do tipo submit). Os valores do formulário (txtNom, txtAge) são então “enviados” (method="post" em <form>) para o aplicativo nomage.php (atributo action não definido em <form>). A aplicação nomage.php calcula ($nom, $age,$post) = (txtNom, txtAge, verdadeiro) e os transmite ao aplicativo nomage-p1.php, que então exibe um formulário já preenchido, bem como a tabela dos valores recuperados.
3.6.2. por um GET
No caso em que os valores do formulário são transmitidos ao servidor por meio de um GET, a aplicação nomage.php passa a ser a aplicação nomage2.php a seguir:
<?php
// os parâmetros esperados foram recebidos?
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
// os parâmetros txtNom e txtAge “GETTés” são recuperados pelo cliente
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//se
// exibição da página
include "nomage-p2.php";
?>
O aplicativo nomage-p2.php é idêntico ao aplicativo nomage-p1.php, com as seguintes diferenças:
- a tag form foi alterada:
- o aplicativo agora recupera uma variável $get em vez de $post:
Durante a execução, quando os valores são inseridos no formulário e enviados ao servidor, o navegador reflete em seu campo URL o fato de que os valores foram enviados pelo método GET:
![]()
3.7. Recuperar os cabeçalhos HTTP enviados por um cliente web
Quando um navegador faz uma solicitação a um servidor web, ele envia a ele um certo número de cabeçalhos HTTP. Às vezes, é interessante ter acesso a eles. Inicialmente, pode-se utilizar a tabela associativa $_SERVER. Essa tabela contém diversas informações fornecidas pelo servidor web, incluindo, entre outras, os cabeçalhos HTTP fornecidos pelo cliente. Consideremos o seguinte programa, que exibe todos os valores da tabela $_SERVER:
<?php
// exibe as variáveis relacionadas ao servidor web
// envio de texto simples
header("Content-type: text/plain");
// percurso pela tabela associativa $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//while
?>
Vamos salvar esse código em headers.php e acessar esse URL em um navegador:

Recebemos algumas informações, incluindo os cabeçalhos HTTP enviados pelo navegador. Esses são os valores associados às chaves que começam com HTTP. Vamos detalhar algumas das informações obtidas acima:
tipos de documentos aceitos pelo cliente web | |
tipos de caracteres aceitos nos documentos | |
tipos de codificação aceitos para os documentos | |
tipos de idiomas aceitos para os documentos | |
tipo de conexão com o servidor. Keep-Alive: o servidor deve manter a conexão aberta após enviar sua resposta | |
? tempo máximo de permanência da conexão aberta | |
máquina host consultada pelo cliente | |
identidade do cliente | |
endereço IP do cliente | |
porta de comunicação utilizada pelo cliente | |
protocolo HTTP utilizado pelo servidor | |
método de consulta utilizado pelo cliente (GET ou POST) | |
solicitação ?param1=val1¶m2=val2&... enviada após a solicitação URL (método GET) |
Ao modificar ligeiramente o código do programa anterior, podemos recuperar apenas os cabeçalhos HTTP:
<?php
// exibe as variáveis relacionadas ao servidor web
// envia texto simples
header("Content-type: text/plain");
// percurso da tabela associativa $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
// cabeçalho HTTP?
if(strtolower(substr($clé,0,4))=="http")
echo substr($clé,5)." : $valeur\n";
}//enquanto
?>
O resultado obtido no navegador é o seguinte:

Se quisermos um cabeçalho HTTP específico, escreveremos, por exemplo, $_SERVER["HTTP_ACCEPT"].
3.8. Recuperar informações do ambiente
O servidor web/PHP é executado em um ambiente cujas características podem ser obtidas por meio da tabela $_ENV, que armazena diversas características do ambiente de execução. Consideremos a seguinte aplicação env1.php:
<?php
// exibe as variáveis relacionadas ao servidor web
// envia texto simples
header("Content-type: text/plain");
// percorre a tabela associativa $_ENV
reset($_ENV);
while (list($clé,$valeur)=each($_ENV)){
echo "$clé : $valeur\n";
}//enquanto
?>
Ela exibe o seguinte resultado em um navegador (visualização parcial):

Vemos, por exemplo, acima que o servidor web/PHP está sendo executado no Windows OS NT.
3.9. Exemplos
3.9.1. Geração dinâmica de formulário - 1
Tomaremos como exemplo a geração de um formulário com apenas um campo de entrada: uma lista suspensa. O conteúdo dessa lista suspensa é construído dinamicamente com valores extraídos de uma matriz. Na prática, esses valores costumam ser obtidos de um banco de dados. O formulário é o seguinte:

Se, no exemplo acima, executarmos Envoyer, obtemos a seguinte resposta:

O código HTML do formulário inicial, uma vez gerado, é o seguinte:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<option>un</option>
<option>deux</option>
<option>trois</option>
<option>quatre</option>
<option>cinq</option>
<option>six</option>
<option>sept</option>
<option>huit</option>
<option>neuf</option>
<option>dix</option>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
O aplicativo PHP é composto por uma página principal, valeurs.php, que é chamada tanto para obter o formulário inicial (a lista de valores) quanto para processar os valores deste e fornecer a resposta (o valor escolhido). O aplicativo gera duas páginas diferentes:
- a do formulário inicial, que será gerada pelo programa valeurs-p1.php
- a página da resposta fornecida ao usuário, que será gerada pelo programa valeurs-p2.php
A aplicação valeurs.php é a seguinte:
<?php
// a matriz de valores
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// os parâmetros esperados foram fornecidos?
$requêteVide=! isset($_POST["cmbValeurs"]);
// recuperamos a escolha do usuário
if ($requêteVide){
// solicitação inicial
include "valeurs-p1.php";
}else{
// resposta a um POST
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
Ele define a tabela de valores e chama o valeurs-p1.php para gerar o formulário inicial caso a solicitação do cliente estivesse vazia, ou o valeurs-p2.php para gerar a resposta caso houvesse uma solicitação válida. O programa valeurs1-php é o seguinte:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
for($i=0;$i<count($valeurs);$i++){
echo "<option>$valeurs[$i]</option>\n";
}//para
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
A lista de valores da lista suspensa é gerada dinamicamente a partir da tabela $valeurs transmitida por valeurs.php. O programa valeurs-p2.php gera a resposta:
<html>
<head>
<title>réponse</title>
</head>
<body>
<h2>Vous avez choisi le nombre <?php echo $choix ?></h2>
</body>
</html>
Aqui, limitamo-nos a exibir o valor da variável $choix, que também é transmitida por valeurs.php.
3.9.2. Geração dinâmica de formulário - 2
Retomamos o exemplo anterior, modificando-o da seguinte maneira. O formulário proposto continua sendo o mesmo:

A resposta é diferente:

Na resposta, o formulário é devolvido, com o número escolhido pelo usuário indicado abaixo dele. Além disso, esse número é o que aparece como selecionado na lista exibida pela resposta.
O código de valeurs.php é o seguinte:
<?php
// configuração
ini_set("register_globals","off");
// a tabela de valores
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// recupera-se a eventual escolha do usuário
$choix=$_POST["cmbValeurs"];
// exibe-se a resposta
include "valeurs-p1.php";
?>
Observe que, neste caso, tomamos o cuidado de configurar o PHP para que não haja variáveis globais. Geralmente, essa é uma precaução sensata, pois as variáveis globais podem causar problemas de segurança. Uma alternativa é inicializar todas as variáveis utilizadas. Isso terá o efeito de “sobrescrever” uma eventual variável global com o mesmo nome.
A página do formulário é exibida pelo valeurs-p1.php:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
for($i=0;$i<count($valeurs);$i++){
// se a opção atual for igual à escolha, ela é selecionada
if (isset($choix) && $choix==$valeurs[$i])
echo "<option selected>$valeurs[$i]</option>\n";
else echo "<option>$valeurs[$i]</option>\n";
}//for
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// continuação da página
if(isset($choix)){
echo "<hr>\n";
echo "<h3>Vous avez choisi le nombre $choix</h3>\n";
}
?>
</body>
</html>
O programa gerador da página utiliza a variável $choix, passada pelo programa valeurs.php. Vale ressaltar aqui que a estrutura HTML da página está começando a ficar seriamente “contaminada” pelo código PHP. O front-end valeurs.php poderia realizar mais tarefas, conforme mostra a nova versão a seguir:
<?php
// configuração
ini_set("register_globals","off");
// a tabela de valores
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// recupera-se a eventual escolha do usuário
$choix=$_POST["cmbValeurs"];
// calcula-se a lista de valores a serem exibidos
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// se a opção atual for igual à escolha, ela é selecionada
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// calcula-se a segunda parte da página
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//se
// exibe-se a resposta
include "valeurs-p2.php";
?>
A página agora é gerada pelo programa valeurs-p2.php a seguir:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
// exibição da lista de valores
echo $HTMLvaleurs;
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// exibição da parte 2
echo $HTMLpart2;
?>
</body>
</html>
O código HTML agora está livre de boa parte do código PHP. Vale lembrar, no entanto, o objetivo dessa divisão em um programa front-end que analisa e processa a solicitação de um cliente e em programas responsáveis apenas por exibir páginas configuradas com os dados transmitidos pelo front-end: trata-se de separar o trabalho do desenvolvedor PHP do trabalho do designer gráfico. O desenvolvedor PHP trabalha no front-end, enquanto o designer gráfico trabalha nas páginas da web. Em nossa nova versão, o designer gráfico não pode mais, por exemplo, trabalhar na parte 2 da página, já que não tem mais acesso ao código HTML dessa parte. Na primeira versão, ele podia. Portanto, nenhum dos dois métodos é perfeito.
3.9.3. Geração dinâmica de formulários - 3
Retomamos o mesmo problema de antes, mas, desta vez, os valores são obtidos de um banco de dados. No nosso exemplo, trata-se do banco de dados MySQL:
- o banco de dados se chama dbValeurs
- seu proprietário é admDbValeurs, com a senha mdpDbValeurs
- o banco de dados possui uma única tabela chamada tvaleurs
- essa tabela possui apenas um campo inteiro chamado valor
dos> mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbValeurs
mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs |
+---------------------+
1 row in set (0.00 sec)
mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) | | | 0 | |
+--------+---------+------+-----+---------+-------+
mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 6 |
| 5 |
| 7 |
| 8 |
| 9 |
+--------+
10 rows in set (0.00 sec)
Em um aplicativo que utiliza um banco de dados, geralmente são encontradas as seguintes etapas:
- Conexão com o SGBD
- Envio de consultas SQL para um banco de dados SGBD
- Processamento dos resultados dessas consultas
- Encerramento da conexão com o SGBD
As etapas 2 e 3 são realizadas repetidamente, sendo que o encerramento da conexão só ocorre ao final da consulta ao banco de dados. Esse é um esquema relativamente comum para qualquer pessoa que já tenha consultado um banco de dados de forma interativa. A tabela a seguir apresenta as instruções PHP para realizar essas diferentes operações com o SGBD e o MySQL:
$connexion=mysql_pconnect($hote,$user,$pwd) $connexion=mysql_connect($hote,$user,$pwd) $hote: nome de internet da máquina na qual o SGBD e o MySQL estão sendo executados. De fato, é possível trabalhar com SGBD e MySQL remotos. $user: nome de um usuário conhecido do SGBD MySQL $pwd: sua senha $connexion: a conexão criada mysql_pconnect cria uma conexão persistente com o SGBD e o MySQL. Essa conexão não é encerrada ao final do script. Ela permanece aberta. Assim, quando for necessário abrir uma nova conexão com o SGBD, o PHP procurará uma conexão existente pertencente ao mesmo usuário. Se a encontrar, ele a utilizará. Isso representa uma economia de tempo. O mysql_connect cria uma conexão não persistente, que é fechada assim que o trabalho com o SGBD e o MySQL for concluído. | |
$résultats=mysql_db_query($base,$requête,$connexion) $base: a base de dados MySQL com a qual vamos trabalhar $requête: uma consulta SQL (insert, delete, update, select, ...) $connexion: a conexão com o SGBD MySQL $résultats: os resultados da consulta — variam dependendo se a consulta é um select ou uma operação de atualização (insert, update, delete, ...) | |
$résultats=mysql_db_query($base,"select ...",$connexion) O resultado de uma consulta `SELECT` é uma tabela, ou seja, um conjunto de linhas e colunas. Essa tabela pode ser acessada por meio de $résultats. $ligne=mysql_fetch_row($résultats) lê uma linha da tabela e a armazena em $ligne na forma de uma matriz. Assim, $ligne[i] é a coluna i da linha recuperada. A função mysql_fetch_row pode ser chamada repetidamente. A cada vez, ela lê uma nova linha da tabela $résultats. Quando chega ao fim da tabela, a função retorna o valor false. Assim, a tabela $résultats pode ser utilizada da seguinte maneira: while($linha = mysql_fetch_row($resultados)) { // processa a linha atual $linha }//while | |
$résultats=mysql_db_query($base,"inserir ...",$connexion) O valor $résultats é verdadeiro ou falso, dependendo se a operação foi bem-sucedida ou falhou. Em caso de sucesso, a função mysql_affected_rows permite saber o número de linhas modificadas pela operação de atualização. | |
mysql_close($conexão) $connexion: uma conexão com o SGBD MySQL |
O código do front-end valeurs.php passa a ser o seguinte:
<?php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("track_errors","on");
// a tabela de valores
list($erreur,$valeurs)=getValeurs();
// houve algum erro?
if($erreur){
// exibição da página de erro
include "valeurs-err.php";
// fim
return;
}//if
// recuperamos a eventual escolha do usuário
$choix=$_POST["cmbValeurs"];
// calcula-se a lista de valores a serem exibidos
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// se a opção atual for igual à escolha, ela é selecionada
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// calcula-se a segunda parte da página
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//se
// exibe-se a resposta
include "valeurs-p1.php";
// fim
return;
// ------------------------------------------------------------------------
function getValeurs(){
// recupera os valores de um banco de dados MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// abertura de uma conexão persistente com o servidor MySQL
// ou, caso contrário, de uma conexão normal
($connexion=mysql_pconnect($hote,$user,$pwd))
|| ($connexion=mysql_connect($hote,$user,$pwd));
if(! $connexion)
return array("Base de données indisponible(".mysql_error()."). Veuillez recommencer ultérieurement.");
// obtenção dos valores
$selectValeurs=mysql_db_query($db,"select $champ from $table",$connexion);
if(! $selectValeurs)
return array("Base de données indisponible(".mysql_error()."). Veuillez recommencer ultérieurement.");
// os valores são colocados em uma matriz
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//enquanto
// fechamento da conexão (se for persistente, ela não será, na verdade, fechada)
mysql_close($connexion);
// retorno do resultado
return array("",$valeurs);
}//getValeurs
?>
Desta vez, os valores a serem inseridos na lista suspensa não são fornecidos por uma tabela, mas pela função getValeurs(). Essa função:
- abre uma conexão persistente (mysql_pconnect) com o servidor mySQL, passando um nome de usuário registrado e sua senha.
- Uma vez estabelecida a conexão, é enviada uma consulta select para recuperar os valores presentes na tabela tvaleurs do banco de dados dbValeurs.
- O resultado da consulta select é armazenado na tabela $valeurs, que é retornada ao programa chamador.
- A função, na verdade, retorna uma matriz com dois resultados ($erreur, $valeurs), em que o primeiro elemento é uma possível mensagem de erro ou, caso contrário, a string vazia.
- O programa chamador verifica se houve erro ou não e, em caso afirmativo, exibe a página valeurs-err.php. Ela é a seguinte:
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>L'erreur suivante s'est produite</h3>
<font color="red">
<h4><?php echo $erreur ?></h4>
</font>
</body>
</html>
- Se não houver erro, o programa chamador terá os valores na matriz $valeurs. Assim, voltamos ao problema anterior.
Aqui estão dois exemplos de execução:
- com erro

- sem erro

3.9.4. Geração dinâmica de formulários - 4
No exemplo anterior, o que aconteceria se mudássemos o código SGBD? Se passássemos, por exemplo, de MySQL para o Oracle ou para o SQL Server? Seria necessário reescrever a função getValeurs(), que fornece os valores. A vantagem de ter reunido o código necessário para recuperar os valores a serem exibidos na lista em uma função é que o código a ser modificado fica bem localizado e não espalhado por todo o programa. A função getValeurs() pode ser reescrita de forma a torná-la independente do SGBD utilizado. Basta que ela trabalhe com o driver ODBC do SGBD, em vez de diretamente com o SGBD.
Existem diversos bancos de dados no mercado. Para padronizar o acesso aos bancos de dados no Windows, a Microsoft desenvolveu uma interface chamada ODBC (Open DataBase Connectivity). Essa camada oculta as particularidades de cada banco de dados por meio de uma interface padrão. No Windows, existem diversos drivers que facilitam o acesso aos bancos de dados. Aqui está, por exemplo, uma lista de drivers instalados em um computador com Windows:

O SGBD MySQL também possui um driver ODBC. Um aplicativo que utilize drivers ODBC pode acessar qualquer um dos bancos de dados acima sem a necessidade de reescrever o código.
![]() |
Vamos tornar nossa base de dados MySQL dbValeurs acessível por meio de um driver ODBC. O procedimento abaixo é para o Windows 2000. Para os sistemas Win9x, o procedimento é muito semelhante. Ativamos o gerenciador de recursos ODBC:

Utilize o botão [Add] para adicionar uma nova fonte de dados ODBC:

Selecione o driver ODBC MySQL, execute [Terminer] e, em seguida, defina as características da fonte de dados:

o nome atribuído à fonte de dados ODBC (odbc-valores) | |
o nome do servidor que hospeda o SGBD MySQL, que gerencia a fonte de dados (localhost) | |
o nome do banco de dados MySQL, que é a fonte de dados (dbValeurs) | |
um usuário com direitos de acesso suficientes para gerenciar o banco de dados MySQL (admDbValeurs) | |
sua senha (mdpDbValeurs) |
PHP é capaz de trabalhar com os drivers ODBC. A tabela a seguir apresenta as funções úteis que devem ser conhecidas:
$connexion = odbc_pconnect ($dsn, $user, $pwd) $connexion=odbc_connect($dsn,$user,$pwd) $dsn: nome DSN (Data Source Name) da máquina na qual o SGBD está sendo executado $user: nome de um usuário conhecido do SGBD $pwd: sua senha $connexion: a conexão criada odbc_pconnect cria uma conexão persistente com o SGBD. Essa conexão não é encerrada ao final do script. Ela permanece aberta. Assim, quando for necessário abrir uma nova conexão com o SGBD, o PHP procurará uma conexão existente pertencente ao mesmo usuário. Se a encontrar, ele a utilizará. Isso representa uma economia de tempo. O odbc_connect cria uma conexão não persistente, que é fechada assim que o trabalho com o SGBD for concluído. | |
$requêtePréparée=odbc_prepare($connexion,$requête) $requête: uma consulta SQL (inserir, excluir, atualizar, selecionar, ...) $connexion: a conexão com o SGBD Analisa a consulta $requête e prepara sua execução. A consulta assim “preparada” é referenciada pelo resultado $requêtePréparée. Preparar uma consulta para sua execução não é obrigatório, mas melhora o desempenho, pois a análise da consulta é feita apenas uma vez. Em seguida, solicita-se a execução da consulta preparada. Se a execução de uma consulta não preparada for solicitada repetidamente, a análise da mesma é realizada a cada vez, o que é desnecessário. Uma vez preparada, a consulta é executada por $res=odbc_execute($requêtePréparée) retorna verdadeiro ou falso, dependendo se a execução da consulta foi bem-sucedida ou não | |
O resultado de uma consulta SELECT é uma tabela, ou seja, um conjunto de linhas e colunas. Essa tabela pode ser acessada por meio de $requêtePréparée. $res=odbc_fetch_row($requêtePréparée) lê uma linha da tabela de resultados da função select. Retorna verdadeiro ou falso, dependendo se a execução da consulta foi bem-sucedida ou não. Os elementos da linha recuperada estão disponíveis por meio da função odbc_result: $val=odbc_result($requêtePréparée,i): coluna i da linha que acaba de ser lida $val=odbc_result($requêtePréparée,"nomColonne"): coluna nomColonne da linha que acabou de ser lida A função odbc_fetch_row pode ser chamada repetidamente. A cada chamada, ela lê uma nova linha da tabela de resultados. Quando chega ao fim da tabela, a função retorna o valor false. Assim, a tabela de resultados pode ser utilizada da seguinte maneira: | |
odbc_close($conexão) $connexion: uma conexão com o SGBD MySQL |
A função getValeurs(), responsável por recuperar os valores do banco de dados ODBC, é a seguinte:
// ------------------------------------------------------------------------
function getValeurs(){
// recupera os valores de um banco de dados MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
// abertura de uma conexão persistente com o servidor MySQL
// ou, caso contrário, de uma conexão normal
($connexion=odbc_pconnect($dsn,$user,$pwd))
|| ($connexion=odbc_connect($dsn,$user,$pwd));
if(! $connexion)
return array("1 - Base de données indisponible(".odbc_error()."). Veuillez recommencer ultérieurement.");
// obtenção dos valores
$selectValeurs=odbc_prepare($connexion,"select $champ from $table");
if(! odbc_execute($selectValeurs))
return array("2 - Base de données indisponible(".odbc_error()."). Veuillez recommencer ultérieurement.");
// os valores são colocados em uma matriz
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//enquanto
// fechamento da conexão (se for persistente, ela não será, na verdade, fechada)
odbc_close($connexion);
// retorno do resultado
return array("",$valeurs);
}//getValeurs
?>
Se executarmos o novo aplicativo sem ativar o banco de dados odbc-valeurs, obtemos o seguinte resultado:

Observe-se que o código de erro retornado pelo driver ODBC (odbc_error()=S1000) não é muito explícito. Se o banco de dados odbc-valeurs for disponibilizado, obtêm-se os mesmos resultados de antes.
Concluindo, pode-se dizer que essa solução é adequada para a manutenção do aplicativo. De fato, se o banco de dados precisar ser alterado, o aplicativo não precisará ser alterado. O administrador do sistema simplesmente criará uma nova fonte de dados ODBC para o novo banco de dados. Ainda com vistas à manutenção, seria recomendável definir os parâmetros de acesso ao banco de dados ($dsn, $user, $pwd) em um arquivo separado que a aplicação carregaria na inicialização (include).
3.9.5. Recuperar os valores de um formulário
Já recuperamos, em várias ocasiões, os valores de um formulário enviados por um cliente web. O exemplo a seguir mostra um formulário que reúne os componentes HTML mais comuns e se destina a recuperar os parâmetros enviados pelo navegador do cliente. O formulário é o seguinte:

Ele vem pré-preenchido. Em seguida, o usuário pode modificá-lo:

Se ele clicar no botão [Envoyer], o servidor retorna a lista de valores do formulário:

O formulário é uma página estática HTML balises.html:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//apagar
</script>
</head>
<body background="/images/standard.jpg">
<form method="POST" action="parameters.php">
<table border="0">
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" value="oui" name="R1">Oui
<input type="radio" name="R1" value="non" checked>Non
</td>
</tr>
<tr>
<td>Cases à cocher</td>
<td>
<input type="checkbox" name="C1" value="un">1
<input type="checkbox" name="C2" value="deux" checked>2
<input type="checkbox" name="C3" value="trois">3
</td>
</tr>
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="txtSaisie" size="20" value="qqs mots">
</td>
</tr>
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="txtMdp" size="20" value="unMotDePasse">
</td>
</tr>
<tr>
<td>Boîte de saisie</td>
<td>
<textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
</td>
</tr>
<tr>
<td>combo</td>
<td>
<select size="1" name="cmbValeurs">
<option>choix1</option>
<option selected>choix2</option>
<option>choix3</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix simple</td>
<td>
<select size="3" name="lst1">
<option selected>liste1</option>
<option>liste2</option>
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix multiple</td>
<td>
<select size="3" name="lst2[]" multiple>
<option selected>liste1</option>
<option>liste2</option>
<option selected>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>bouton</td>
<td>
<input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
</td>
</tr>
<tr>
<td>envoyer</td>
<td>
<input type="submit" value="Envoyer" name="cmdRenvoyer">
</td>
</tr>
<tr>
<td>rétablir</td>
<td>
<input type="reset" value="Rétablir" name="cmdRétablir">
</td>
</tr>
</table>
<input type="hidden" name="secret" value="uneValeur">
</form>
</body>
</html>
A tabela abaixo resume a função das diferentes tags deste documento e o valor recuperado por PHP para os diferentes tipos de componentes de um formulário. O valor de um campo com o nome HTML C pode ser enviado por um POST ou um GET. No primeiro caso, ele será recuperado na variável $_GET["C"] e, no segundo caso, na variável $_POST["C"]. A tabela a seguir pressupõe o uso de um POST.
Controle | da baliza HTML | valor recuperado por PHP |
<form method="POST" > | ||
<input type="text" name="txtSaisie" size="20" value="algumas palavras"> | $_POST["txtSaisie"]: valor contido no campo txtSaisie do formulário | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"> | $_POST["txtmdp"]: valor contido no campo txtMdp do formulário | |
<textarea rows="2" name="areaSaisie" cols="20"> linha1 linha 2 linha 3 </textarea> | $_POST["areaSaisie"]: linhas contidas no campo areaSaisie na forma de uma única sequência de caracteres: "ligne1\r\nligne2\r\nligne3". As linhas são separadas entre si pela sequência "\r\n". | |
<input type="radio" value="Sim" name="R1">Sim <input type="radio" name="R1" value="não" checked>Não | $_POST["R1"]: valor (=value) do botão de opção marcado como “sim” ou “não”, conforme o caso. | |
<input type="checkbox" name="C1" value="um">1 <input type="checkbox" name="C2" value="dois" checked>2 <input type="checkbox" name="C3" value="três">3 | $_POST["C1"]: valor (=value) da caixa de seleção se ela estiver marcada; caso contrário, a variável não existe. Assim, se a caixa de seleção C1 foi marcada, $_POST["C1"] vale “um”; caso contrário, $_POST["C1"] não existe. | |
<select size="1" name="cmbValeurs"> <option>opção1</option> <option selected>opção2</option> <option>opção3</option> </select> | $_POST["cmbValeurs"]: opção selecionada na lista, por exemplo, “opção3”. | |
<select size="3" name="lst1"> <option selected>lista1</option> <option>lista2</option> <option>lista3</option> <option>lista4</option> <option>lista5</option> </select> | $_POST["lst1"]: opção selecionada na lista, por exemplo, “lista5”. | |
<select size="3" name="lst2[]" multiple> <option>lista1</option> <option>lista2</option> <option selected>lista3</option> <option>lista4</option> <option>lista5</option> </select> | $_POST["lst2"]: tabela das opções selecionadas na lista, por exemplo, ["liste3,"liste5"]. Observe-se a sintaxe específica da tag HTML para este caso específico: lst2[]. | |
<input type="hidden" name="secret" value="uneValeur"> | $_POST["secret"]: valor (=value) do campo, neste caso “uneValeur”. |
No nosso exemplo, os valores do formulário são encaminhados para o programa parameters.php:
O código deste último é o seguinte:
<?php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
// método de chamada
$méthode=$_SERVER["REQUEST_METHOD"];
// recuperação de parâmetros
// isso depende do método de envio desses parâmetros
if($méthode=="GET")
$param=$_GET;
else $param=$_POST;
$R1=$param["R1"];
$C1=$param["C1"];
$C2=$param["C2"];
$C3=$param["C3"];
$txtSaisie=$param["txtSaisie"];
$txtMdp=$param["txtMdp"];
$areaSaisie=implode("<br>",explode("\r\n",$param["areaSaisie"]));
$cmbValeurs=$param["cmbValeurs"];
$lst1=$param["lst1"];
$lst2=implode("<br>",$param["lst2"]);
$secret=$param["secret"];
// solicitação válida?
$requêteValide=isset($R1) && (isset($C1) || isset($C2) || isset($C3))
&& isset($txtSaisie) && isset($txtMdp) && isset($areaSaisie)
&& isset($cmbValeurs) && isset($lst1) && isset($lst2)
&& isset($secret);
// exibição da página
if ($requêteValide)
include "parameters-p1.php";
else include "balises.html";
?>
Vamos detalhar alguns pontos deste programa:
- o aplicativo não depende do modo de envio dos valores do formulário para o servidor. Nos dois casos possíveis (GET e POST), o dicionário dos valores enviados é referenciado por $param.
- A partir do conteúdo do campo areaSaisie “linha1\r\nlinha2\r\n...” cria-se uma matriz de cadeias de caracteres por meio de explode("\r\n", $param["areaSaisie"]). Assim, obtém-se a matriz [ligne1,ligne2,...]. A partir dela, cria-se a string "ligne1<br>ligne2<br>..." com a função implode.
- O valor da lista de seleção múltipla lst2 é um array, por exemplo, ["option3","option5"]. A partir dele, cria-se uma cadeia de caracteres “option3<br>option5” com a função implode.
- O aplicativo verifica se todos os parâmetros foram definidos. É importante lembrar aqui que qualquer URL pode ser chamada manualmente ou por programa e que nem sempre os parâmetros esperados estão presentes. Se faltar algum parâmetro, é exibida a página balises.html; caso contrário, é exibida a página parameters-p1.php. Esta última exibe os valores recuperados e calculados na parameters.php em uma tabela:
<html>
<head>
<title>Récupération des paramètres d'un formulaire</title>
</head>
<body>
<table border="1">
<tr>
<td>R1</td>
<td><?php echo $R1 ?></td>
</tr>
<tr>
<td>C1</td>
<td><?php echo $C1 ?></td>
</tr>
<tr>
<td>C2</td>
<td><?php echo $C2 ?></td>
</tr>
<tr>
<td>C3</td>
<td><?php echo $C3 ?></td>
</tr>
<tr>
<td>txtSaisie</td>
<td><?php echo $txtSaisie ?></td>
</tr>
<tr>
<td>txtMdp</td>
<td><?php echo $txtMdp ?></td>
</tr>
<tr>
<td>areaSaisie</td>
<td><?php echo $areaSaisie ?></td>
</tr>
<tr>
<td>cmbValeurs</td>
<td><?php echo $cmbValeurs ?></td>
</tr>
<tr>
<td>lst1</td>
<td><?php echo $lst1 ?></td>
</tr>
<tr>
<td>lst2</td>
<td><?php echo $lst2 ?></td>
</tr>
<tr>
<td>secret</td>
<td><?php echo $secret ?></td>
</tr>
</table>
</body>
</html>
3.10. Rastreamento de sessão
3.10.1. O problema
Um aplicativo web pode envolver várias trocas de formulários entre o servidor e o cliente. O funcionamento é o seguinte:
etapa 1
- o cliente C1 abre uma conexão com o servidor e faz sua solicitação inicial.
- O servidor envia o formulário F1 ao cliente C1 e encerra a conexão aberta na etapa 1.
etapa 2
- o cliente C1 preenche o formulário e o reenvia ao servidor. Para isso, o navegador abre uma nova conexão com o servidor.
- Este processa os dados do formulário 1, calcula as informações I1 a partir deles, envia um formulário F2 ao cliente C1 e encerra a conexão aberta na etapa 3.
Etapa 3
- O ciclo das etapas 3 e 4 se repete nas etapas 5 e 6. Ao final da etapa 6, o servidor terá recebido dois formulários F1 e F2 e, a partir deles, terá calculado as informações I1 e I2.
O problema que se coloca é: como o servidor consegue manter as informações I1 e I2 associadas ao cliente C1? Esse problema é conhecido como acompanhamento da sessão do cliente C1. Para compreender sua origem, vamos examinar o esquema de um aplicativo servidor TCP-IP que atende simultaneamente a vários clientes:
![]() |
Em uma aplicação cliente-servidor TCP-IP clássica:
- o cliente estabelece uma conexão com o servidor
- troca dados com o servidor por meio dessa conexão
- a conexão é encerrada por um dos dois parceiros
Os dois pontos importantes desse mecanismo são:
- uma única conexão é criada para cada um dos clientes
- essa conexão é utilizada durante todo o tempo em que o servidor mantém o diálogo com seu cliente
O que permite ao servidor saber, a qualquer momento, com qual cliente está trabalhando é a conexão, ou, em outras palavras, o “canal” que o conecta ao seu cliente. Como esse canal é dedicado a um determinado cliente, tudo o que chega por esse canal vem desse cliente e tudo o que é enviado por esse canal chega a esse mesmo cliente.
O mecanismo cliente-servidor HTTP segue o esquema anterior, com a particularidade de que a comunicação cliente-servidor se limita a uma única troca entre o cliente e o servidor:
- o cliente abre uma conexão com o servidor e faz sua solicitação
- o servidor responde e encerra a conexão
Se, no momento T1, um cliente C fizer uma solicitação ao servidor, ele obterá uma conexão C1 que servirá para a única troca de solicitação e resposta. Se, no momento T2, esse mesmo cliente fizer uma segunda solicitação ao servidor, ele obterá uma conexão C2, diferente da conexão C1. Para o servidor, não há, portanto, nenhuma diferença entre essa segunda solicitação do usuário C e sua solicitação inicial: nos dois casos, o servidor considera o cliente como um novo cliente. Para que haja uma ligação entre as diferentes conexões do cliente C ao servidor, é necessário que o cliente C seja “reconhecido” pelo servidor como um “usuário habitual” e que o servidor recupere as informações que possui sobre esse usuário habitual.
Imaginemos um sistema de gerenciamento que funcionasse da seguinte maneira:
- Há uma única fila
- Existem vários guichês. Portanto, vários clientes podem ser atendidos simultaneamente. Quando um guichê fica livre, um cliente sai da fila para ser atendido nesse guichê
- Se for a primeira vez que o cliente se apresenta, a pessoa no guichê lhe entrega uma ficha com um número. O cliente só pode fazer uma pergunta. Ao receber a resposta, ele deve sair do guichê e ir para o final da fila de espera. O atendente do guichê anota as informações desse cliente em um arquivo com o número da ficha.
- Quando chegar novamente a sua vez, o cliente poderá ser atendido por um atendente diferente do da vez anterior. Este solicita a ficha e pega o arquivo com o número da ficha. Mais uma vez, o cliente faz uma solicitação, recebe uma resposta e as informações são adicionadas ao seu arquivo.
- E assim por diante... Com o passar do tempo, o cliente terá a resposta para todas as suas solicitações. O acompanhamento entre as diferentes solicitações é feito por meio do ficha e do arquivo associado a ele.
O mecanismo de acompanhamento de sessão em um aplicativo cliente-servidor web é semelhante ao funcionamento descrito acima:
- na sua primeira solicitação, o cliente recebe um token do servidor web
- ele apresentará esse token em cada uma de suas solicitações subsequentes para se identificar
O token pode assumir diferentes formas:
- um campo oculto em um formulário
- o cliente faz sua primeira solicitação (o servidor o reconhece pelo fato de o cliente não possuir um token)
- O servidor envia sua resposta (um formulário) e insere o token em um campo oculto desse formulário. Nesse momento, a conexão é encerrada (o cliente sai da janela com seu token). O servidor se encarregou, se necessário, de associar informações a esse token.
- O cliente faz sua segunda solicitação, reenviando o formulário. O servidor recupera o token desse formulário. Ele pode então processar a segunda solicitação do cliente, tendo acesso, graças ao token, às informações calculadas durante a primeira solicitação. Novas informações são adicionadas ao arquivo vinculado ao token, uma segunda resposta é enviada ao cliente e a conexão é encerrada pela segunda vez. O token foi colocado novamente no formulário da resposta para que o usuário possa apresentá-lo em sua próxima solicitação.
- e assim por diante...
A principal desvantagem dessa técnica é que o token precisa ser inserido em um formulário. Se a resposta do servidor não for um formulário, o método do campo oculto não pode mais ser utilizado.
- A técnica do cookie
- o cliente faz sua primeira solicitação (o servidor o reconhece pelo fato de o cliente não ter um token)
- o servidor responde adicionando um cookie nos cabeçalhos HTTP da resposta. Isso é feito por meio do comando HTTP Set-Cookie:
Set-Cookie: param1=valor1;param2=valor2;....
onde parami são nomes de parâmetros e valeursi são seus valores. Entre os parâmetros, estará o token. Muitas vezes, há apenas o token no cookie, sendo que as demais informações são registradas pelo servidor na pasta vinculada ao token. O navegador que recebe o cookie irá armazená-lo em um arquivo no disco. Após a resposta do servidor, a conexão é encerrada (o cliente sai do guichê com seu token).
- (continuação)
- o cliente faz sua segunda solicitação ao servidor. Sempre que uma solicitação é feita a um servidor, o navegador verifica, entre todos os cookies que possui, se há algum proveniente do servidor solicitado. Se houver, ele o envia ao servidor sempre na forma de um comando HTTP, o comando Cookie, cuja sintaxe é análoga à do comando Set-Cookie utilizado pelo servidor:
Cookie: param1=valor1;param2=valor2;....
Entre os cabeçalhos HTTP enviados pelo navegador, o servidor encontrará o token que lhe permite reconhecer o cliente e recuperar as informações a ele associadas.
Essa é a forma mais utilizada de token. Ela apresenta uma desvantagem: um usuário pode configurar seu navegador para que ele não aceite cookies. Esse tipo de usuário, portanto, não tem acesso às aplicações web que utilizam cookies.
- Reescrita de URL
- o cliente faz sua primeira solicitação (o servidor o reconhece pelo fato de o cliente não possuir um token)
- o servidor envia sua resposta. Ela contém links que o usuário deve utilizar para continuar na aplicação. No URL de cada um desses links, o servidor adiciona o token na forma URL;token=valor.
- Quando o usuário clica em um dos links para continuar a aplicação, o navegador faz sua solicitação ao servidor web, enviando nos cabeçalhos HTTP o URL URL;token=valor solicitado. O servidor é então capaz de recuperar o token.
3.10.2. API e PHP para o rastreamento de sessão
Apresentamos agora os principais métodos úteis para o rastreamento de sessão:
inicia a sessão à qual pertence a solicitação atual. Caso esta ainda não fizesse parte de uma sessão, esta é criada. | |
identificador da sessão atual | |
dicionário que armazena os dados de uma sessão. Acessível para leitura e gravação | |
exclui os dados contidos na sessão atual. Esses dados permanecem disponíveis para a troca cliente-servidor em andamento, mas não estarão disponíveis na próxima troca. |
3.10.3. Exemplo 1
Apresentamos um exemplo inspirado no livro “Programação com J2EE”, publicado pela editora Wrox e distribuído pela Eyrolles. Este exemplo permite ver como funciona uma sessão PHP. A página principal é a seguinte:

Nela, encontramos os seguintes elementos:
- o ID da sessão obtido pela função session_id(). Esse ID, gerado pelo navegador, é enviado ao cliente por meio de um cookie que o navegador reenvia ao solicitar um URL da mesma árvore de diretórios. É isso que mantém a sessão.
- um contador que é incrementado à medida que o navegador faz solicitações e que indica que a sessão está sendo mantida corretamente.
- um link que permite excluir os dados associados à sessão atual. Isso é feito pela função session_destroy()
- um link para recarregar a página
O código da aplicação cycledevie.php é o seguinte:
<?php
//cycledevie.php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
// iniciando uma sessão
session_start();
// é preciso invalidá-la?
$action=$_GET["action"];
if($action=="invalider"){
// fim da sessão
session_destroy();
}//if
// gerenciamento do contador
if(! isset($_SESSION["compteur"]))
// o contador não existe — vamos criá-lo
$_SESSION["compteur"]=0;
// o contador existe — incrementá-lo
else $_SESSION["compteur"]++;
// recuperamos o ID da sessão atual
$idSession=session_id();
// recupera-se o contador
$compteur=$_SESSION["compteur"];
// passamos o controle para a página de visualização
include "cycledevie-p1.php";
?>
Observe os seguintes pontos:
- desde o início da aplicação, uma sessão é iniciada. Se o cliente tiver enviado um token de sessão, é a sessão com esse identificador que é reiniciada, e todos os dados a ela associados são colocados no dicionário $_SESSION. Caso contrário, um novo token de sessão é criado.
- Se o cliente tiver enviado um parâmetro action com o valor “invalider”, os dados da sessão são marcados como “a serem excluídos” para a próxima troca. Eles não serão salvos no servidor ao final da troca, ao contrário do que ocorre em uma troca normal.
- Recupera-se um contador vinculado à sessão no dicionário $_SESSION, bem como o ID da sessão (session_id()).
- A página a ser enviada ao cliente é gerada pelo programa cycledevie-p1.php
A página cycledevie-p1.php exibe a página enviada ao cliente:
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : <?php echo $idSession ?>
<br>compteur : <?php echo $compteur ?>
<br><a href="cycledevie.php?action=invalider">Invalider la session</a>
<br><a href="cycledevie.php">Recharger la page</a>
</body>
</html>
Observe-se o código URL anexado a cada um dos dois links:
- cycledevie.php para recarregar a página
- cycledevie.php?action=invalider para invalidar a sessão. Nesse caso, um parâmetro action=invalider é anexado ao URL. Ele será recuperado pelo programa do servidor cycledevie.php por meio da instrução $action=$_GET["action"].
Vamos recarregar a página duas vezes seguidas:

O contador foi incrementado corretamente. O ID da sessão não mudou. Vamos agora invalidar a sessão:

Vemos que perdemos o ID da sessão, mas que o contador foi incrementado mais uma vez. Vamos recarregar a página:

Vemos que recomeçamos com o mesmo ID de sessão de antes. O contador, por sua vez, volta a zero. A função session_destroy() não tem, portanto, efeito imediato. Os dados da sessão atual são excluídos apenas para a troca cliente-servidor que se segue àquela em que a exclusão é realizada. A sessão ID não mudou, o que parece indicar que session_destroy() não inicia uma nova sessão com a criação de um novo ID. O cookie do token de sessão foi reenviado pelo navegador do cliente e PHP recuperou a sessão a partir desse token, sessão essa que já não continha dados.
Os testes anteriores foram realizados com o navegador Netscape configurado para usar cookies. Vamos configurá-lo agora para não usá-los. Isso significa que ele não armazenará nem reenviará os cookies enviados pelo servidor. Espera-se, então, que as sessões não funcionem mais. Vamos tentar com uma primeira troca:

Temos um ID de sessão, aquele gerado por session_start(). Vamos recarregar a página com o link:

Surpreendentemente, os resultados acima mostram que temos uma sessão ativa e que o contador está sendo gerenciado corretamente. Como isso é possível, já que os cookies foram desativados e não há mais troca de token entre o servidor e o navegador? O URL da captura de tela acima nos dá a resposta:
Esse é o URL do link “Atualizar a página”. Vamos verificar o código-fonte da página exibida pelo navegador:
<a href="cycledevie.php?action=invalider&PHPSESSID=587ce26f943a288d8f41212e30fed13c">Invalider la session</a>
<a href="cycledevie.php?PHPSESSID=587ce26f943a288d8f41212e30fed13c">Recharger la page</a>
Vale lembrar que o código inicial dos dois links na página cycledevie-p1.php é este:
<a href="cycledevie.php?action=invalider">Invalider la session</a>
<a href="cycledevie.php">Recharger la page</a>
O interpretador PHP, portanto, reescreveu por conta própria os URL dos dois links, adicionando o token de sessão. Assim, este é transmitido corretamente pelo navegador quando os links são ativados. Isso explica por que, mesmo sem os cookies ativados, a sessão continua sendo gerenciada corretamente.
3.10.4. Exemplo 3
Propomos escrever uma aplicação PHP que funcionaria como cliente da aplicação compteur anterior. Ela a chamaria N vezes seguidas, sendo que N seria passado como parâmetro. Nosso objetivo é mostrar um cliente web programado e a maneira de gerenciar o token de sessão. Nosso ponto de partida será um cliente web genérico chamado da seguinte forma:
clientweb URL GET/HEAD
- URL: URL solicitada
- GET/HEAD: GET para solicitar o código HTML da página, HEAD para restringir apenas aos cabeçalhos HTTP
Aqui está um exemplo com o URL http://localhost/poly/sessions/2/cycledevie.php. Este programa é o mesmo já descrito, com uma pequena diferença:
// definimos o caminho do cookie
session_set_cookie_params(0,"/poly/sessions/2");
// inicia-se uma sessão
session_start();
A função session_set_cookie_params permite definir certos parâmetros do cookie que conterá o token de sessão. O primeiro parâmetro é o tempo de vida do cookie. Um tempo de vida nulo significa que o cookie é excluído pelo navegador que o recebeu quando este for fechado. O segundo parâmetro é o caminho para o URL, para o qual o navegador deve reenviar o cookie. No exemplo acima, se o navegador recebeu o cookie da máquina localhost, ele reenviará o cookie para qualquer URL que esteja na árvore de diretórios http://localhost/poly/sessions/2/.
dos>e:\php43\php.exe clientweb.php http://localhost/poly/sessions/2/cycledevie.php GET
HTTP/1.1 200 OK
Date: Wed, 09 Oct 2002 13:58:16 GMT
Server: Apache/1.3.24 (Win32)
Set-Cookie: PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4; path=/poly/sessions/2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: text/html
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : 48d5aaa0e99850b17c33a6e22d38e5c4 <br>compteur : 0
<br><a href="cycledevie.php?action=invalider&PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4">Invalid
er la session</a>
<br><a href="cycledevie.php?PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4">Recharger la page</a>
</body>
</html>
O programa clientweb exibe tudo o que recebe do servidor. Acima, vemos o comando HTTP Set-cookie, com o qual o servidor envia um cookie ao seu cliente. Aqui, o cookie contém duas informações:
- PHPSESSID, que é o token da sessão
- path, que define a URL à qual o cookie pertence. path=/poly/sessions/2 indica ao navegador que ele deverá reenviar o cookie ao servidor sempre que solicitar uma URL que comece por /poly/sessions/2 da máquina que lhe enviou o cookie.
- Um cookie também pode definir um prazo de validade. Neste caso, essa informação está ausente. O cookie será, portanto, excluído ao fechar o navegador. Um cookie pode ter um prazo de validade de N dias, por exemplo. Enquanto estiver válido, o navegador o reenviará sempre que uma das páginas URL de seu domínio (Path) for acessada. Tomemos como exemplo um site de vendas online de CD. Ele pode acompanhar a navegação do cliente em seu catálogo e determinar, aos poucos, suas preferências: música clássica, por exemplo. Essas preferências podem ser armazenadas em um cookie com validade de 3 meses. Se esse mesmo cliente retornar ao site após um mês, o navegador reenviará o cookie para o servidor. Com base nas informações contidas no cookie, o servidor poderá então adaptar as páginas geradas às preferências do cliente.
Segue abaixo o código do cliente web.
<?php
// configuração
dl("php_curl.dll"); // biblioteca CURL
// sintaxe: $0 URL GET
// são necessários três argumentos
if($argc != 3){
// mensagem de erro
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// encerramento
exit(1);
}//if
// o terceiro argumento deve ser GET ou HEAD
$header=strtolower($argv[2]);
if($header!="get" && $header!="head"){
// mensagem de erro
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// parada
exit(2);
}//se
// o primeiro argumento é um URL
$URL=strtolower($argv[1]);
// preparação da conexão
$connexion=curl_init($URL);
// configuração da conexão
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// execução da conexão
curl_exec($connexion);
// encerramento da conexão
curl_close($connexion);
// fim
exit(0);
?>
O programa anterior utiliza a biblioteca CURL:
inicializa um objeto CURL com o URL a ser alcançado | |
define o valor de algumas opções da conexão. Destacamos as duas utilizadas no programa: CURLOPT_HEADER=1: permite obter os cabeçalhos HTTP enviados pelo servidor CURLOPT_NOBODY=1: permite ignorar o documento enviado pelo servidor por trás dos cabeçalhos HTTP | |
estabelece a conexão com $URL com as opções solicitadas. Exibe na tela tudo o que o servidor envia | |
encerra a conexão |
O programa anterior é bastante simples. No entanto, a biblioteca CURL não permite manipular detalhadamente a resposta do servidor, por exemplo, analisá-la linha por linha. O programa a seguir faz o mesmo que o anterior, mas utilizando as funções básicas de rede da biblioteca PHP. Ele servirá como ponto de partida para a criação de um cliente para nossa aplicação cycledevie.php.
<?php
// sintaxe: $0 URL GET/HEAD
// são necessários três argumentos
if($argc != 3){
// mensagem de erro
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// encerramento
exit(1);
}//if
// conexão e exibição do resultado
$résultats=getURL($argv[1],$argv[2]);
if(isset($résultats->erreur)){
// erro
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}else{
// exibição da resposta do servidor
echo $résultats->réponse;
}//if
// fim
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$header){
// conecta-se a $URL
// gera um GET ou um HEAD, dependendo do valor do cabeçalho
// a resposta do servidor forma o resultado da função
// análise do URL
$url=parse_url($URL);
// o protocolo
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//se
// a máquina
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// a porta
$port=$url["port"];
if(! isset($port)) $port=80;
// o caminho
$chemin=$url["path"];
// a solicitação
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//se
// análise de $header
$header=strtoupper($header);
if($header!="GET" && $header!="HEAD"){
// mensagem de erro
$résultats->erreur="méthode [$header] doit être GET ou HEAD";
// parada
return $résultats;
}//if
// abertura de uma conexão na porta $port de $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// retorno em caso de erro
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion representa um fluxo de comunicação bidirecional
// entre o cliente (este programa) e o servidor web acessado
// esse canal é utilizado para a troca de comandos e informações
// o protocolo de comunicação é HTTP
// o cliente envia o comando get para solicitar o URL /
// sintaxe get URL HTTP/1.0
// os cabeçalhos (headers) do protocolo HTTP devem terminar com uma linha em branco
fputs($connexion, "$header $chemin HTTP/1.0\n\n");
// o servidor agora responderá no canal $connexion. Ele enviará todos
// esses dados e, em seguida, fechará o canal. O cliente, portanto, lê tudo o que chega de $connexion
// até o fechamento do canal
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// o cliente, por sua vez, encerra a conexão
fclose($connexion);
// retorno
return $résultats;
}//getURL
?>
Vamos comentar alguns pontos deste programa:
- o programa aceita dois parâmetros:
- um URL URL cujo conteúdo se deseja exibir na tela.
- um método GET ou HEAD, a ser utilizado dependendo se deseja apenas os cabeçalhos HTTP (HEAD) ou também o corpo do documento associado ao URL (GET).
- Ambos os parâmetros são passados para a função getURL. Essa função retorna um objeto $résultats. Esse objeto possui um campo erreur caso haja um erro e, caso contrário, um campo réponse. O campo erreur serve para armazenar uma eventual mensagem de erro. O campo réponse é a resposta do servidor web contatado.
- A função getURL analisa o URL e o $URL com a função parse_url. A instrução $url=parse_url($URL) criará o tabela associativa $url com as seguintes chaves, se houver:
- scheme: o protocolo do URL (http, ftp, etc.)
- host: a máquina do URL
- port: a porta do URL
- path: o caminho do URL
- querystring: os parâmetros do URL
Uma URL estará correta se tiver o formato http://machine[:port][/chemin].
- O parâmetro $header também é verificado
- uma vez que os parâmetros estejam verificados e corretos, cria-se uma conexão TCP na máquina ($hote, $port), em seguida, envia-se o comando HTTP, GET ou HEAD, dependendo do parâmetro $header.
- em seguida, lê-se a resposta do servidor, que é armazenada em $résultats->resposta.
A execução do programa produz os seguintes resultados:
dos>"e:\php43\php.exe" geturl.php http://localhost/poly/sessions/2/cycledevie.php get
HTTP/1.1 200 OK
Date: Wed, 09 Oct 2002 14:56:55 GMT
Server: Apache/1.3.24 (Win32)
Set-Cookie: PHPSESSID=ea0d2673811ed069e7289d86933a4c0a; path=/poly/sessions/2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Connection: close
Content-Type: text/html
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : ea0d2673811ed069e7289d86933a4c0a <br>compteur : 0
<br><a href="cycledevie.php?action=invalider&PHPSESSID=ea0d2673811ed069e7289d86933a4c0a">Invalid
er la session</a>
<br><a href="cycledevie.php?PHPSESSID=ea0d2673811ed069e7289d86933a4c0a">Recharger la page</a>
</body>
</html>
O leitor atento deve ter notado que a resposta do servidor varia de acordo com o programa cliente utilizado. No primeiro caso, o servidor enviou um cabeçalho HTTP: Transfer-Encoding: chunked, cabeçalho esse que não foi enviado no segundo caso. Isso se deve ao fato de que o segundo cliente enviou o cabeçalho HTTP: get URL HTTP/1.0, que solicita um URL e indica que está operando com o protocolo HTTP versão 1.0, o que obriga o servidor a responder com esse mesmo protocolo. No entanto, o cabeçalho HTTP Transfer-Encoding: chunked pertence ao protocolo HTTP versão 1.1. Por isso, o servidor não o utilizou em sua resposta. Isso nos mostra que o primeiro cliente fez sua solicitação indicando que estava trabalhando com o protocolo HTTP versão 1.1.
Agora, criamos o programa clientCompteur, chamado da seguinte forma:
clientCompteur URL N [JSESSIONID]
- URL: URL do aplicativo cycledevie
- N: número de chamadas a serem feitas para este aplicativo
- PHPSESSID: parâmetro opcional — token de uma sessão
O objetivo do programa é chamar N vezes o aplicativo cycledevie.php, gerenciando o cookie de sessão e exibindo, a cada vez, o valor do contador retornado pelo servidor. Ao final das N chamadas, o valor do contador deve ser N-1. Aqui está um primeiro exemplo de execução:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 3
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Set-Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea; path=/poly/sessions/2
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 0]
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 1]
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 2]
O programa exibe:
- os cabeçalhos HTTP que ele envia ao servidor na forma --> entêteEnvoyé
- os cabeçalhos HTTP que recebe na forma <-- entêteReçu
- o valor do contador após cada chamada
Percebe-se que, na primeira chamada:
- o cliente não envia nenhum cookie
- o servidor envia um (Set-Cookie:)
Nas chamadas seguintes:
- o cliente reenvia sistematicamente o cookie que recebeu do servidor na primeira chamada. É isso que permitirá ao servidor reconhecê-lo e incrementar seu contador.
- já o servidor não reenvia mais nenhum cookie
Reexecutamos o programa anterior, passando o token acima como terceiro parâmetro:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 1 2425e00d1d65c2bdcbafc1ce6244f7ea
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:32:03 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 3]
Vemos aqui que, logo na primeira chamada do cliente, o servidor recebe um cookie de sessão válido. Isso talvez indique uma possível falha de segurança. Se eu conseguir interceptar um token de sessão na rede, poderei me passar por quem iniciou a sessão. No nosso exemplo, a primeira chamada (sem token de sessão) representa quem inicia a sessão (talvez com um login e senha que lhe darão o direito de obter um token) e a segunda chamada (com token de sessão) representa quem “hackeou” o token de sessão da primeira chamada. Se a operação em andamento for uma transação bancária, isso pode se tornar um problema sério...
O código do cliente é o seguinte:
<?php
// sintaxe: $0 URL N [PHPSESSID]
// são necessários três argumentos
if($argc!=3 && $argc!=4){
// mensagem de erro
fputs(STDERR,"Syntaxe : $argv[0] URL N [PHPSESSID]");
// parada
exit(1);
}//if
// recuperação dos parâmetros
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// conexão e exibição do resultado
$résultats=getURL($URL,$N,$PHPSESSID);
if(isset($résultats->erreur)){
// erro
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}
// fim
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$N,$PHPSESSID){
// conecta-se ao URL
// gera um GET ou um HEAD, dependendo do valor do cabeçalho
// a resposta do servidor forma o resultado da função
// análise do URL
$url=parse_url($URL);
// o protocolo
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//se
// a máquina
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// a porta
$port=$url["port"];
if(! isset($port)) $port=80;
// o caminho
$chemin=$url["path"];
// a solicitação
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//se
// verificação de $N
if (! preg_match("/^\d+$/",$N)){
// erro
$résultats->erreur="nombre [$N] erroné";
// fim
return $résultats;
}//if
// são feitas as chamadas de $N para $URL
for($i=0;$i<$N;$i++){
// abertura de uma conexão na porta $port de $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// retorno em caso de erro
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//se
// $connexion representa um fluxo de comunicação bidirecional
// entre o cliente (este programa) e o servidor web acessado
// esse canal é utilizado para a troca de comandos e informações
// o protocolo de comunicação é HTTP
// o cliente envia os cabeçalhos HTTP
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:porta
envoie($connexion, "Host: $hote:$port\n");
// Conexão: fechar
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// linha vazia
envoie($connexion,"\n");
// o servidor agora responderá no canal $connexion. Ele enviará todos
// seus dados e, em seguida, fechará o canal.
// O cliente começa lendo os cabeçalhos HTTP, terminados por uma linha em branco
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// eco de linha
echo "<-- $ligne\n";
// procura o token, caso ainda não tenha sido encontrado
if(! $PHPSESSID){
// busca pela linha “set-cookie”
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// o token foi encontrado — ele é armazenado
$PHPSESSID=$champs[1];
}//if
}//se
// busca pelo modo de transferência do documento
if(! $CHUNKED){
// busca pela linha Transfer-Encoding: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// transferência em partes
$CHUNKED=1;
}//if
}//se
}//linha seguinte
// eco da linha
echo "<-- $ligne\n";
// a leitura do documento depende da forma como ele foi enviado
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// busca pelo contador no documento
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// o contador foi encontrado — exibindo-o
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
// o cliente encerra a conexão
fclose($connexion);
}//para i
}//getURL
//--------------------------
function getDoc($connexion){
// leitura do documento em $connexion
$doc="";
while($ligne=fread($connexion,10000))
$doc.=$ligne;
// fim
return $doc;
}//getDoc
//--------------------------
function getChunkedDoc($connexion){
// leitura do documento em $connexion
// este documento é enviado em partes na forma
// número de caracteres da parte em hexadecimal
// continuação do fragmento
// linha vazia
// a dimensão do fragmento é lida na primeira linha
$taille=hexdec(rtrim(fgets($connexion,10000)));
// lê-se o documento seguinte
$doc="";
while($taille!=0){
// leitura de um trecho de $taille caracteres
$doc.=fread($connexion,$taille);
// linha vazia
fgets($connexion,10000);
// trecho seguinte
// leitura do tamanho do trecho
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//enquanto
// fim
return $doc;
}// getChunkedDoc
//--------------------------
function envoie($flux,$msg){
// envia $msg para $flux
fwrite($flux,$msg);
// exibe na tela
echo "--> $msg";
}//envia
?>
Vamos analisar os pontos importantes desse programa:
- é preciso realizar N trocas cliente-servidor. É por isso que elas estão em um loop
- a cada troca, o cliente abre uma conexão TCP-IP com o servidor. Assim que a conexão é estabelecida, ele envia ao servidor os cabeçalhos HTTP de sua solicitação:
<?php
...
// o cliente envia os cabeçalhos HTTP
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:porta
envoie($connexion, "Host: $hote:$port\n");
// Conexão: fechar
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// linha vazia
envoie($connexion,"\n");
Se o token PHPSESSID estiver disponível, ele é enviado na forma de um cookie; caso contrário, não é enviado. Observe que o cliente indicou que estava trabalhando com o protocolo HTTP/1.1. Isso explica por que, posteriormente, o servidor enviará a ele o cabeçalho HTTP: Transfer-Encoding: chunked, que pertence ao protocolo HTTP/1.1, mas não ao protocolo HTTP/1.0.
- Depois de enviar sua solicitação, o cliente aguarda a resposta do servidor. Ele começa analisando os cabeçalhos HTTP dessa resposta. Ele procura nela duas linhas:
A linha “Cookie:” é o cabeçalho HTTP que contém o token de sessão PHPSESSID. O cliente deve recuperá-lo para reenviá-lo ao servidor na próxima troca de dados. A linha “Transfer-Encoding: chunked”, se estiver presente, indica que o servidor enviará um documento em partes. Cada parte é então enviada ao cliente no seguinte formato:
Se a linha Transfer-Encoding: chunked não estiver presente, o documento é enviado de uma só vez após a linha vazia dos cabeçalhos HTTP. Portanto, dependendo da presença ou não dessa linha, o modo de recepção do documento será diferente. O código de interpretação dos cabeçalhos HTTP é o seguinte:
<?php
...
// O cliente começa lendo os cabeçalhos HTTP, que terminam com uma linha vazia
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// eco da linha
echo "<-- $ligne\n";
// busca pelo token, caso ainda não tenha sido encontrado
if(! $PHPSESSID){
// busca pela linha “set-cookie”
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// o token foi encontrado — ele é armazenado
$PHPSESSID=$champs[1];
}//if
}//se
// busca pelo modo de transferência do documento
if(! $CHUNKED){
// busca pela linha Transfer-Encoding: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// transferência em partes
$CHUNKED=1;
}//if
}//if
}//linha seguinte
- Quando o token for encontrado pela primeira vez, ele não será mais procurado nas chamadas subsequentes ao servidor. Após o processamento dos cabeçalhos HTTP da resposta, passa-se para o documento que se segue aos cabeçalhos HTTP. Este é lido de maneira diferente, dependendo do seu modo de transferência:
<?php
...
// a leitura do documento depende da forma como ele foi enviado
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
- No documento $document recebido, procura-se a linha que fornece o valor do contador. Essa busca também é feita com uma expressão regular:
<?php
...
// busca do contador no documento
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// o contador foi encontrado — exibi-lo
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//se
- Caso o servidor envie o documento de uma só vez, o recebimento é simples:
<?php
...
//--------------------------
function getDoc($connexion){
// leitura do documento em $connexion
$doc="";
while($ligne=fgets($connexion,10000))
$doc.=$ligne;
// fim
return $doc;
}//getDoc
- No caso de o servidor enviar o documento em várias partes, a leitura do mesmo se torna mais complexa:
<?php
...
function getChunkedDoc($connexion){
// leitura do documento em $connexion
// este documento é enviado em partes na forma
// número de caracteres da parte em hexadecimal
// continuação do fragmento
// a tamanho do fragmento é lido na primeira linha
$taille=hexdec(rtrim(fgets($connexion,10000)));
// lê-se o documento a seguir
$doc="";
while($taille!=0){
// leitura do trecho de $taille caracteres
$doc.=fread($connexion,$taille);
// linha vazia
fgets($connexion,10000);
// trecho seguinte
// leitura do tamanho do trecho
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//enquanto
// fim
return $doc;
}// getChunkedDoc
Vale lembrar que um trecho do documento é enviado na forma
Começamos, portanto, lendo o tamanho do documento. Uma vez conhecido esse tamanho, solicitamos à função fread que leia $taille caracteres no fluxo $connexion e, em seguida, a linha vazia que se segue. Repetimos esse processo até que o servidor informe que enviará um documento de tamanho 0.
3.10.5. Exemplo 4
No exemplo anterior, o cliente web retorna o token na forma de um cookie. Vimos que ele também poderia retorná-lo dentro da própria solicitação URL, na forma URL;PHPSESSID=xxx. Vamos verificar isso. O programa clientCompteur.php é transformado em clientCompteur2.php e modificado da seguinte forma:
<?php
...
....
// o cliente envia os cabeçalhos HTTP
// get URL HTTP/1.1
if($PHPSESSID)
envoie($connexion, "GET $chemin?PHPSESSID=$PHPSESSID HTTP/1.1\n");
else envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:porta
envoie($connexion, "Host: $hote:$port\n");
// Conexão: fechada
envoie($connexion, "Connection: close\n");
// linha vazia
envoie($connexion,"\n");
....
O cliente solicita, portanto, o URL do medidor por meio de GET e URL;PHPSESSID=xx HTTP/1.1 e não envia mais nenhum cookie. Essa é a única alteração. Aqui estão os resultados de uma primeira chamada:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 2
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 07:21:19 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Set-Cookie: PHPSESSID=573212ba82303d7903caf8944ee7a86f; path=/poly/sessions/2
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 0]
--> GET /poly/sessions/2/cycledevie.php?PHPSESSID=573212ba82303d7903caf8944ee7a86f HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 07:21:19 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 1]
Na primeira chamada, o cliente solicita o URL sem token de sessão. O servidor responde enviando-lhe o token. O cliente então consulta novamente o mesmo URL, anexando o token recebido a ele. Percebe-se que o contador foi incrementado, o que comprova que o servidor reconheceu corretamente que se tratava da mesma sessão.
3.10.6. Exemplo 5
Este exemplo mostra um aplicativo composto por três páginas, que chamaremos de page1, page2 e page3. O usuário deve acessá-las nesta ordem:
- a página1 é um formulário que solicita uma informação: um nome
- a página2 é um formulário exibido em resposta ao envio do formulário da página1. Ela solicita uma segunda informação: uma idade
- a página3 é um documento HTML que exibe o nome obtido pela página1 e a idade obtida pela página2.
Há três trocas cliente-servidor:
- na primeira troca, o formulário da página1 é solicitado pelo cliente e enviado pelo servidor
- na segunda troca, o cliente envia o formulário da página1 (nome) ao servidor. Ele recebe em resposta o formulário da página2 ou, novamente, o formulário da página1, caso haja algum erro.
- na terceira troca, o cliente envia o formulário da página2 (idade) ao servidor. Ele recebe em troca o formulário da página3 ou, novamente, o formulário da página2, caso este estivesse com erros. O documento da página3 exibe o nome e a idade. O nome foi obtido pelo servidor na segunda troca e “esquecido” desde então. Utiliza-se uma sessão para registrar o nome na troca 2, de modo que ele esteja disponível na troca 3.
A página page1 obtida na primeira troca é a seguinte:

Preenche-se o campo do nome:

Utiliza-se o botão [Suite] e obtém-se então a seguinte página page2:

Preencha o campo de idade:

Utiliza-se o botão [Suite] e obtém-se então a seguinte página page3:

Ao enviar a página page1 ao servidor, este pode devolvê-la com um código de erro se o nome estiver vazio:

Ao enviar a página page2 ao servidor, este pode devolvê-la com um código de erro se a idade for inválida:

O aplicativo é composto por seis programas:
chama o page1.php | |
exibe a página1. O formulário da página1 é processado pelo etape2.php. | |
processa os valores do formulário da página1. Se houver erros, a página1 é exibida novamente pelo page1.php; caso contrário, a página2 é exibida pelo page2.php. | |
exibe a página2. O formulário da página2 é processado por etape3.php. | |
processa os valores do formulário da página 2. Se houver erros, a página 2 é exibida novamente pelo page2.php; caso contrário, a página 3 é exibida pelo page3.php. | |
exibe a página3. |
A etapa 1 do aplicativo é processada pelo seguinte programa etape1.php:
<?php
// etape1.php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
// início da sessão
session_start();
$_SESSION["session"]=""; //zera variável de sessão
// preparação da página 1
$requête->nom="";
$requête->erreurs=array();
// exibição da página 1
include "page1.php";
// fim
exit(0);
?>
Observe os seguintes pontos:
- o aplicativo requer o acompanhamento da sessão. Portanto, cada etapa da sessão inicia uma nova sessão.
- As informações da sessão a serem mantidas serão armazenadas em um objeto $session.
- As informações necessárias para a exibição das diferentes (três) páginas do aplicativo serão colocadas em um objeto $requête.
O programa page1.php exibe as informações contidas no $requête:
<? // page1.php ?>
<html>
<head>
<title>page 1</title>
</head>
<body>
<h3>Page 1/3</h3>
<form name="frmNom" method="POST" action="etape2.php">
<table>
<tr>
<td>Votre nom</td>
<td><input type="text" name="nom" value="<? echo $requête->nom ?>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<? // erros?
if (count($requête->erreurs)!=0){
?>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<? for($i=0;$i<count($requête->erreurs);$i++){ ?>
<li><? echo $requête->erreurs[$i] ?>
<? }//para ?>
</ul>
<? }//se ?>
</body>
</html>
- A página recebe um objeto $requête contendo dois campos: nom e erreurs. Ela exibe o valor desses dois campos.
- Além disso, ela apresenta um formulário. Os valores deste (nome) são enviados pelo método POST ao programa etape2.php:
A aplicação etape2.php é responsável por processar os valores do formulário da página1 e por exibir novamente a página1 caso haja erros (nome incorreto); caso contrário, exibe a página2 para obter a idade.
<?php
// etape2.php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
// início da sessão
session_start();
// normalmente, deve haver um parâmetro “nome”
// registrado na solicitação
$requête->nom=$_POST["nom"];
// se não houver parâmetros, enviamos a página 1 sem erros
if (! isset($requête->nom)){
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//se
// se o parâmetro “nom” estiver presente, verifica-se sua validade
$page=calculerPage($requête);
// houve erros?
if(count($page->erreurs)!=0){
// página 1 com erros
$requête->erreurs=$page->erreurs;
include "page1.php";
exit(0);
}//se
// sem erros — o nome é armazenado na sessão
unset($session);
$session->nom=$requête->nom;
$_SESSION["session"]=$session;
// exibição da página 2
$requête->age="";
$requête->erreurs=array();
include "page2.php";
// fim
exit(0);
// ---------calculerPage
function calculerPage($requête){
// verifica a validade da solicitação $requête
// retorna uma matriz de erros em $page->erros
// inicialmente, sem erros
$page->erreurs=array();
// o nome não pode estar vazio
if (preg_match("/^\s*$/",$requête->nom)){
$page->erreurs[]="Vous n'avez pas indiqué de nom";
}
// voltar à página
return $page;
}//calculerPage
?>
- etape2 começa verificando se possui o parâmetro nom esperado. Caso contrário, ele faz com que uma página1 vazia seja exibida novamente. Esse caso é possível se etape2 for chamado diretamente por um cliente que não passe parâmetros para ele.
- Se o parâmetro nom estiver presente, verifica-se sua validade. Isso é feito por meio de um procedimento chamado calculerPage, cuja função é gerar um objeto $page com um campo erreurs, que é uma matriz de erros. É possível ocorrer apenas um erro, mas quisemos demonstrar que é possível gerenciar uma lista de erros.
- Se houver erros, a página page1 é exibida novamente, acompanhada da lista de erros.
- Se não houver erros, o nome é armazenado no objeto $session, que armazena os dados relacionados à sessão atual. Em seguida, a página page2 é exibida.
O programa page2.php exibe a página 2:
<? // page2.php ?>
<html>
<head>
<title>page 2</title>
</head>
<body>
<h3>Page 2/3</h3>
<form name="frmAge" method="POST" action="etape3.php">
<table>
<tr>
<td>Nom</td>
<td><font color="green"><? echo $requête->nom ?></font></td>
</tr>
<tr>
<td>Votre âge</td>
<td><input type="text" name="age" size="3" value="<? echo $requête->age ?>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<? // erros?
if (count($requête->erreurs)!=0){
?>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<? for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i];
}//para
?>
</ul>
</font>
<? } ?>
</body>
</html>
O princípio desta página é muito semelhante ao da page2.php. Ela exibe o conteúdo de um objeto $requête que contém os campos nom, age e erreurs. Ela exibe um formulário cujos valores serão processados pelo etape3.php.
O programa etape3.php processa, portanto, os valores do formulário da página 2, aqui restritos à idade:
<?php
// etape3.php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
// início da sessão
session_start();
// recuperamos os parâmetros nome e idade
$requête->age=$_POST["age"];
$session=$_SESSION["session"];
$requête->nom=$session->nom;
// normalmente, deve haver um nome e uma idade
if (! isset($requête->age) || ! isset($requête->nom)){
// se a chamada estiver incorreta, enviamos a página 1
$_SESSION["session"]=""; // por precaução
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// o parâmetro idade estiver presente — verifica-se sua validade
$page=calculerPage($requête);
// houve erros?
if(count($page->erreurs)!=0){
// página 2 com erros
$requête->erreurs=$page->erreurs;
include "page2.php";
exit(0);
}//se
// sem erro — armazenamento da idade na sessão
$session->age=$requête->age;
$_SESSION["session"]=$session;
// exibição da página 3
include "page3.php";
// fim
exit(0);
// ---------calculerPage
function calculerPage($requête){
// verifica a validade da solicitação $requête
// retorna uma matriz de erros em $page->erros
// inicialmente, sem erros
$page->erreurs=array();
// a idade deve estar em um formato válido
if (! preg_match("/^\s*\d{1,3}\s*$/",$requête->age)){
$page->erreurs[]="âge incorrect";
}
// voltar à página
return $page;
}//calculerPage
?>
- O programa começa recuperando o nome da sessão (proveniente da página 1) e a idade do formulário da página 2. Se alguma dessas informações estiver faltando, a página 1 é exibida.
- Em seguida, verifica-se a validade da idade. Se a idade estiver incorreta, a página 2 é exibida novamente com uma lista de erros. Se a idade estiver correta, a página 3 é exibida. Essa página se limita a exibir os dois valores (nome, idade) obtidos pelos dois formulários (página 1, página 2).

