3. Introdução à programação web em PHP
3.1. Programação PHP
Recorde-se aqui que o PHP é uma linguagem por direito próprio e que, embora seja utilizado principalmente no âmbito do desenvolvimento de aplicações para a Web, pode ser utilizado noutros contextos. O documento «PHP através de exemplos», disponível em http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf, apresenta os fundamentos da linguagem. Partimos do princípio de que esses conhecimentos já foram adquiridos. Vamos mostrar, com um exemplo simples, o procedimento para executar um programa PHP no Windows. O código seguinte foi guardado com o nome coucou.php.
A execução deste programa é feita numa janela 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
Note-se que o interpretador PHP envia, por predefinição:
- o interpretador PHP é o php.exe e encontra-se normalmente 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 resto do documento
- o documento aqui formado pelo texto gerado pela função echo
3.2. O ficheiro de configuração do interpretador PHP
O comportamento do interpretador PHP é definido por um ficheiro de configuração denominado php.ini e guardado, no Windows, no próprio diretório do Windows. Trata-se de um ficheiro de dimensão considerável, uma vez que, no Windows e para a versão 4.2 do PHP, tem cerca de 1000 linhas, das quais, felizmente, três quartos são comentários. Vamos analisar alguns dos atributos de configuração do PHP:
permite incluir instruções entre as balizas <? >. No off, estas devem ser incluídas entre <?php ... > | |
no on permite utilizar 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, este cabeçalho é suprimido. | |
define o âmbito do acompanhamento de erros. Aqui, todos os erros (E_ALL), exceto os avisos durante a execução (~E_NOTICE), serão sinalizados | |
em on, coloca os erros no fluxo HTML enviado ao cliente. Estes são, portanto, visualizados no navegador. Recomenda-se definir esta opção para off. | |
os erros serão guardados num ficheiro | |
armazena o último erro ocorrido na variável $php_errormsg | |
define o ficheiro de registo de erros (se log_errors=on) | |
em on, algumas variáveis tornam-se globais. Considerado uma falha de segurança. | |
gera, por predefinição, o cabeçalho HTTP: Content-type: text/html | |
a lista de diretórios que serão explorados na procura dos ficheiros exigidos pelas diretivas include ou require | |
o diretório onde serão guardados os ficheiros que armazenam as diferentes sessões em curso. O disco em questão é aquele onde foi instalado o PHP. Aqui, /temp refere-se ao e:\temp |
Este ficheiro de configuração influencia a portabilidade do programa PHP escrito. Com efeito, se uma aplicação web tiver de recuperar o valor de um campo C de um formulário web, poderá fazê-lo de várias formas, dependendo se a variável de configuração register_globals tem 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
- : idêntico ao acima, mais $C, uma vez que o valor do campo C foi definido como global numa variável com o mesmo nome que o campo
Se um programador escrever um programa utilizando a notação $C porque o servidor web/PHP que utiliza atribui à variável register_globals o valor de on, esse programa deixará de funcionar se for transferido para um servidor web/PHP onde essa mesma variável seja off. Por isso, procuraremos escrever programas evitando utilizar 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. Estas são alteradas durante a execução do programa e apenas para ele. Duas funções são úteis neste processo:
retorna o valor da variável de configuração confVariable | |
define o valor da variável de configuração confVariable |
Eis 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 deste valor
ini_set("track_errors","off");
// verificação
echo "track_errors=".ini_get("track_errors")."\n";
?>
Após a execução, 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). Alteramo-lo para off. É importante ter em conta que, se a nossa aplicação tiver de se basear em determinados valores das variáveis de configuração, é aconselhável inicializá-las no próprio programa.
3.4. Contexto de execução dos exemplos
Os exemplos deste folheto 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 ficheiro httpd.conf. As linhas seguintes indicam ao Apache para carregar o PHP como um módulo integrado ao Apache e para encaminhar ao interpretador PHP qualquer pedido que vise um documento com determinados sufixos, incluindo o .php. Este é o sufixo predefinido que iremos utilizar para os 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, recusar
Allow from all
</Directory>
Chamemos <poly> ao caminho e:/data/serge/web/php/poly. Se quisermos solicitar, através de um navegador, o documento doc.php ao servidor Apache, utilizaremos o http://localhost/poly/doc.php URL. O servidor Apache reconhecerá no URL o alias poly e, em seguida, associará o URL /poly/doc.php ao documento <poly>\doc.php.
3.5. Um primeiro exemplo
Vamos escrever uma primeira aplicação web/PHP. O texto seguinte está guardado no ficheiro 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 acedermos a esta 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 ficheiro http://localhost/poly/intro/heure.php com o código URL. O servidor web (no exemplo, o Apache) recebeu este pedido e detetou, devido ao sufixo .php do documento solicitado, que devia encaminhar este pedido para o interpretador PHP. Este analisa então o documento heure.php e executa todas as partes de código situadas entre as balizas <?php > e substitui cada uma delas pelas linhas escritas pelas instruções PHP, echo ou print. Assim, o interpretador PHP irá executar a parte de código acima e substituí-la pela linha escrita pela instrução echo:
Assim que todas as partes de código PHP tiverem sido executadas, o documento PHP transforma-se num simples documento HTML, que é então enviado ao cliente.
Procurar-se-á evitar, tanto quanto possível, misturar o código PHP com o código HTML. Para tal, poder-se-ia reescrever a aplicação anterior da seguinte forma:
<!-- 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. A estrutura da página fica, assim, mais visível. É possível ir mais longe, colocando o código PHP e o código HTML em dois ficheiros diferentes. O código PHP está armazenado no ficheiro 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 ficheiro 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, este será carregado e analisado pelo interpretador PHP. Ao encontrar a linha
O interpretador irá incluir o ficheiro heure3-page1.php no código-fonte de heure3.php e executá-lo. Assim, tudo acontece 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 que anteriormente:

A solução de colocar os códigos PHP e HTML em ficheiros diferentes será adotada posteriormente. Apresenta várias vantagens:
- a estrutura das páginas enviadas ao cliente não fica submersa no código PHP. Assim, podem ser mantidas por um «web designer» com competências gráficas, mas com poucos conhecimentos de PHP.
- o código PHP funciona como «front-end» para os pedidos dos clientes. Tem como objetivo calcular os dados necessários para a página que será devolvida em resposta ao cliente.
No entanto, esta solução apresenta uma desvantagem: em vez de exigir o carregamento de um único documento, requer o carregamento de vários documentos, o que pode resultar numa perda de desempenho.
3.6. Recuperar os parâmetros enviados por um cliente web
3.6.1. através de um POST
Consideremos o seguinte formulário, no qual o utilizador deve fornecer duas informações: um nome e uma idade.

Quando o utilizador preenche os campos Nom e Age, clica em seguida no botão Envoyer, que é do tipo submit. Os valores do formulário são então enviados para o servidor. Este devolve o formulário, juntamente com uma tabela que lista os valores que recebeu:

O navegador solicita o formulário à seguinte aplicação nomage.php:
<?php
// temos os parâmetros esperados?
$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 para o servidor através do método GET ou do método POST. Se for enviado através do 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 devolve 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 ou não valores «enviados». Estas três variáveis são transmitidas para a página nomage-p1.php. Note-se que, embora esta participe na elaboração da resposta ao cliente, este 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, obtém-se $post como falso. Com efeito, durante esta 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
// foram enviados valores?
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>
A aplicação nomage-p1.php apresenta o formulário frmPersonne. Este é definido pela baliza:
Como o atributo action da baliza não está definido, o navegador transmitirá os dados do formulário para a URL que consultou para os obter, a c.a.d. à aplicação nomage.php.
Distinguamos os dois casos de chamada da aplicação nomage.php:
- É a primeira vez que o utilizador a chama. A aplicação nomage.php chama, portanto, a aplicação nomage-p1.php, fornecendo-lhe os valores ($nom,$age,$post)=("","",falso). A aplicação nomage-p1.php apresenta então um formulário vazio.
- O utilizador 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 a aplicação nomage.php (atributo action não definido em <form>). A aplicação nomage.php calcula ($nom, $age,$post) = (txtNom, txtAge, verdadeiro) e transmite-os à aplicação nomage-p1.php, que apresenta então um formulário já preenchido, bem como a tabela dos valores recuperados.
3.6.2. por um GET
No caso de os valores do formulário serem transmitidos ao servidor por um GET, a aplicação nomage.php passa a ser a aplicação seguinte, nomage2.php:
<?php
// temos os parâmetros esperados?
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
// o cliente recupera os parâmetros txtNom e txtAge «GETTés»
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//se
// exibição da página
include "nomage-p2.php";
?>
A aplicação nomage-p2.php é idêntica à aplicação nomage-p1.php, com as seguintes diferenças:
- a baliza form foi alterada:
- a aplicação recupera agora uma variável $get em vez de $post:
Durante a execução, quando os valores são introduzidos no formulário e enviados para o servidor, o navegador reflete no seu campo URL o facto de os valores terem sido enviados pelo método GET:
![]()
3.7. Recuperar os cabeçalhos HTTP enviados por um cliente web
Quando um navegador faz um pedido a um servidor web, envia-lhe um certo número de cabeçalhos HTTP. Por vezes, é interessante ter acesso a esses cabeçalhos. Em primeiro lugar, pode-se recorrer à tabela associativa $_SERVER. Este contém várias informações fornecidas pelo servidor web, incluindo, entre outras, os cabeçalhos HTTP fornecidos pelo cliente. Consideremos o seguinte programa que apresenta todos os valores da tabela $_SERVER:
<?php
// exibe as variáveis relacionadas com o servidor web
// envia-se texto simples
header("Content-type: text/plain");
// percurso pelo tabuleiro associativo $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//while
?>
Vamos guardar este código em headers.php e aceder a este URL num navegador:

Recebemos várias informações, incluindo os cabeçalhos HTTP enviados pelo navegador. Estes são os valores associados às chaves que começam por HTTP. Vamos detalhar algumas das informações obtidas acima:
tipos de documentos aceites pelo cliente web | |
tipos de caracteres aceites nos documentos | |
tipos de codificação aceites para os documentos | |
tipos de idiomas aceites para os documentos | |
tipo de ligação com o servidor. Keep-Alive: o servidor deve manter a ligação aberta após ter enviado a sua resposta | |
? tempo máximo de permanência da ligação aberta | |
máquina anfitriã 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) | |
pedido ?param1=val1¶m2=val2&... colocado a seguir ao URL solicitado (método GET) |
Ao alterar ligeiramente o código do programa anterior, podemos recuperar apenas os cabeçalhos HTTP:
<?php
// exibe as variáveis relacionadas com o 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 se pretender um cabeçalho HTTP específico, deve escrever-se, por exemplo, $_SERVER["HTTP_ACCEPT"].
3.8. Recuperar informações do ambiente
O servidor web/PHP é executado num ambiente cujas características podem ser obtidas através da tabela $_ENV, que armazena várias características do ambiente de execução. Consideremos a seguinte aplicação env1.php:
<?php
// exibe as variáveis relacionadas com o 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
?>
Apresenta o seguinte resultado num navegador (visualização parcial):

Como se pode ver, por exemplo, acima, o servidor web/PHP está a ser executado no Windows OS NT.
3.9. Exemplos
3.9.1. Geração dinâmica de formulários - 1
Tomamos como exemplo a geração de um formulário com apenas um controlo: uma lista suspensa. O conteúdo desta lista suspensa é construído dinamicamente com valores retirados de uma tabela. Na realidade, estes valores são frequentemente retirados de uma base de dados. O formulário é o seguinte:

Se, no exemplo acima, introduzirmos 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>
A aplicação PHP é composta por uma página principal, valeurs.php, que é chamada tanto para obter o formulário inicial (a lista de valores) como para processar os valores do mesmo e fornecer a resposta (o valor escolhido). A aplicação 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 utilizador, 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");
// existem os parâmetros esperados
$requêteVide=! isset($_POST["cmbValeurs"]);
// recuperamos a escolha do utilizador
if ($requêteVide){
// pedido inicial
include "valeurs-p1.php";
}else{
// resposta a um POST
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
Este programa 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 a solicitação fosse 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 do menu suspenso é 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 apresentar o valor da variável $choix, também transmitida por valeurs.php.
3.9.2. Geração dinâmica de formulários - 2
Retomamos o exemplo anterior, alterando-o da seguinte forma. O formulário proposto continua a ser o mesmo:

A resposta é diferente:

Na resposta, é devolvido o formulário, com o número escolhido pelo utilizador indicado por baixo. Além disso, esse número é o que aparece como selecionado na lista apresentada 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 utilizador
$choix=$_POST["cmbValeurs"];
// exibe-se a resposta
include "valeurs-p1.php";
?>
Note-se que, neste caso, tomámos o cuidado de configurar o PHP de forma a que não existam variáveis globais. Trata-se, em geral, de uma precaução sensata, uma vez que as variáveis globais levantam problemas de segurança. Uma alternativa é inicializar todas as variáveis que se utilizam. Isto terá como efeito «sobrescrever» uma eventual variável global com o mesmo nome.
A página do formulário é apresentada 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, seleciona-se essa opção
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 baseia-se na variável $choix passada pelo programa valeurs.php. Note-se aqui que a estrutura HTML da página começa a ficar seriamente «contaminada» por código PHP. O front-end valeurs.php poderia realizar mais tarefas, como mostra a seguinte nova versão:
<?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 utilizador
$choix=$_POST["cmbValeurs"];
// calcula-se a lista de valores a apresentar
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// se a opção atual for igual à escolha, seleciona-se essa opção
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 seguinte programa valeurs-p2.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
// 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 está agora livre de grande parte do código PHP. Recorde-se, no entanto, o objetivo da divisão num programa frontal que analisa e processa o pedido de um cliente e em programas simplesmente encarregados de apresentar páginas configuradas com os dados transmitidos pelo frontal: trata-se de separar o trabalho do programador PHP do trabalho do designer gráfico. O programador PHP trabalha no front-end, enquanto o designer gráfico trabalha nas páginas web. Na nossa nova versão, o designer gráfico já não pode, por exemplo, trabalhar na parte 2 da página, uma vez que já não tem acesso ao código HTML da mesma. Na primeira versão, isso era possível. Nenhum dos dois métodos é, portanto, 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 a partir de uma base de dados. No nosso exemplo, trata-se da base MySQL:
- a base de dados chama-se dbValeurs
- o seu proprietário é admDbValeurs, com a palavra-passe mdpDbValeurs
- a base de dados tem uma única tabela chamada tvaleurs
- esta tabela tem 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)
Numa aplicação que utilize uma base de dados, encontram-se geralmente as seguintes etapas:
- Ligação ao SGBD
- Envio de consultas SQL para uma base de dados SGBD
- Processamento dos resultados dessas consultas
- Encerramento da ligação ao SGBD
Os passos 2 e 3 são repetidos, sendo que o encerramento da ligação só ocorre no final da utilização da base de dados. Trata-se de um esquema relativamente comum para qualquer pessoa que já tenha utilizado uma base de dados de forma interativa. A tabela seguinte apresenta as instruções PHP para realizar estas 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 do computador no qual o SGBD e o MySQL estão a ser executados. De facto, é possível trabalhar com SGBD e MySQL remotos. $user: nome de um utilizador conhecido do SGBD MySQL $pwd: a sua palavra-passe $connexion: a ligação criada O mysql_pconnect cria uma ligação persistente com o SGBD e o MySQL. Essa ligação não é encerrada no final do script. Permanece aberta. Assim, quando for necessário abrir uma nova ligação com o SGBD, o PHP procurará uma ligação existente pertencente ao mesmo utilizador. Se a encontrar, utiliza-a. Isto representa uma poupança de tempo. O mysql_connect cria uma ligação não persistente, que é, portanto, encerrada quando o trabalho com o SGBD e o MySQL estiver 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 ligação ao SGBD MySQL $résultats: os resultados da consulta — variam consoante a consulta seja 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. Esta tabela está acessível através de $résultats. $ligne=mysql_fetch_row($résultats) lê uma linha da tabela e coloca-a em $ligne sob a forma de um array. Assim, $ligne[i] corresponde à coluna i da linha recuperada. A função mysql_fetch_row pode ser chamada repetidamente. Cada vez que é chamada, lê uma nova linha da tabela $résultats. Quando se chega ao fim da tabela, a função devolve o valor false. Assim, a tabela $résultats pode ser explorada da seguinte forma: 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, consoante a operação tenha sido bem-sucedida ou tenha falhado. Em caso de sucesso, a função mysql_affected_rows permite saber o número de linhas alteradas pela operação de atualização. | |
mysql_close($conexão) $connexion: uma ligação ao 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();
// ocorreu algum erro?
if($erreur){
// exibição da página de erro
include "valeurs-err.php";
// fim
return;
}//if
// recuperamos a eventual escolha do utilizador
$choix=$_POST["cmbValeurs"];
// calcula-se a lista de valores a apresentar
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// se a opção atual for igual à escolha, seleciona-se essa opção
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 uma base de dados MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// abertura de uma ligação persistente ao servidor MySQL
// ou, caso contrário, de uma ligaçã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 numa matriz
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//enquanto
// encerramento da ligação (se for persistente, não será, na verdade, encerrada)
mysql_close($connexion);
// retorno do resultado
return array("",$valeurs);
}//getValeurs
?>
Desta vez, os valores a introduzir no menu suspenso não são fornecidos por uma tabela, mas sim pela função getValeurs(). Esta função:
- abre uma ligação persistente (mysql_pconnect) com o servidor mySQL, passando um nome de utilizador registado e a respetiva palavra-passe.
- Assim que a ligação é estabelecida, é enviada uma consulta select para recuperar os valores presentes na tabela tvaleurs da base de dados dbValeurs.
- O resultado da consulta select é colocado na tabela $valeurs, que é devolvida ao programa chamador.
- Na verdade, a função devolve uma matriz com dois resultados ($erreur, $valeurs), em que o primeiro elemento é uma eventual mensagem de erro ou, caso contrário, a cadeia vazia.
- O programa chamador verifica se ocorreu ou não um erro e, caso tenha ocorrido, apresenta a página valeurs-err.php. Esta é 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 tiver ocorrido qualquer erro, o programa chamador dispõe dos valores na tabela $valeurs. Assim, voltamos ao problema anterior.
Eis 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 alterássemos o SGBD? Se passássemos do MySQL para o Oracle ou para o SQL Server, por exemplo? 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 apresentar na lista numa única função é que o código a modificar é bem específico e não está espalhado por todo o programa. A função getValeurs() pode ser reescrita de forma a tornar-se independente do SGBD utilizado. Basta que ela funcione com o controlador ODBC do SGBD, em vez de diretamente com o SGBD.
Existem inúmeras bases de dados no mercado. Para uniformizar o acesso às bases de dados no MS Windows, a Microsoft desenvolveu uma interface denominada ODBC (Open DataBase Connectivity). Esta camada oculta as particularidades de cada base de dados numa interface padrão. No Windows, existem vários controladores que facilitam o acesso às bases de dados. Aqui está, por exemplo, uma lista de controladores instalados num computador Windows:

O SGBD MySQL também possui um controlador ODBC. Uma aplicação que utilize controladores ODBC pode aceder a qualquer uma das bases de dados acima referidas sem necessidade de reescrever o código.
![]() |
Vamos tornar a nossa base de dados MySQL dbValeurs acessível através de um controlador ODBC. O procedimento abaixo é o do Windows 2000. Para os sistemas Win9x, o procedimento é muito semelhante. Ativamos o gestor de recursos ODBC:

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

Seleciona-se o controlador ODBC MySQL, clica-se em [Terminer] e, em seguida, definem-se as características da fonte de dados:

o nome atribuído à fonte de dados ODBC (odbc-valores) | |
o nome do computador que aloja o SGBD MySQL que gere a fonte de dados (localhost) | |
o nome da base de dados MySQL, que é a fonte de dados (dbValeurs) | |
um utilizador com direitos de acesso suficientes à base de dados MySQL a gerir (admDbValeurs) | |
a sua palavra-passe (mdpDbValeurs) |
PHP é capaz de trabalhar com controladores ODBC. A tabela seguinte apresenta as funções úteis a conhecer:
$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á a ser executado $user: nome de um utilizador conhecido do SGBD $pwd: a sua palavra-passe $connexion: a ligação criada O odbc_pconnect cria uma ligação persistente com o SGBD. Essa ligação não é encerrada no final do script. Permanece aberta. Assim, quando for necessário abrir uma nova ligação com o SGBD, o PHP procurará uma ligação existente pertencente ao mesmo utilizador. Se a encontrar, utiliza-a. Isto representa uma poupança de tempo. O odbc_connect cria uma ligação não persistente, que é, portanto, encerrada quando o trabalho com o SGBD estiver concluído. | |
$requêtePréparée=odbc_prepare($connexion,$requête) $requête: uma consulta SQL (inserir, eliminar, atualizar, selecionar, ...) $connexion: a ligação ao SGBD Analisa a consulta $requête e prepara a sua execução. A consulta assim «preparada» é referenciada pelo resultado $requêtePréparée. Preparar uma consulta para a sua execução não é obrigatório, mas melhora o desempenho, uma vez que a análise da consulta é feita apenas uma vez. Em seguida, solicita-se a execução da consulta preparada. Se se solicitar a execução repetida de uma consulta não preparada, a análise da mesma é efetuada de cada vez, o que é desnecessário. Uma vez preparada, a consulta é executada por $res=odbc_execute($requêtePréparée) que retorna verdadeiro ou falso, consoante a execução da consulta for bem-sucedida ou não | |
O resultado de uma consulta SELECT é uma tabela, ou seja, um conjunto de linhas e colunas. Esta tabela está acessível através 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, consoante a execução da consulta for bem-sucedida ou não. Os elementos da linha recuperada estão disponíveis através da função odbc_result: $val=odbc_result($requêtePréparée,i): coluna i da linha que acabou 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. Em cada chamada, ela lê uma nova linha da tabela de resultados. Quando chega ao fim da tabela, a função devolve o valor false. Assim, a tabela de resultados pode ser utilizada da seguinte forma: | |
odbc_close($conexão) $connexion: uma ligação ao SGBD MySQL |
A função getValeurs(), responsável por recuperar os valores da base de dados ODBC, é a seguinte:
// ------------------------------------------------------------------------
function getValeurs(){
// recupera os valores de uma base de dados MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
// abertura de uma ligação persistente ao servidor MySQL
// ou, caso contrário, de uma ligaçã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 numa matriz
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//enquanto
// encerramento da ligação (se for persistente, não será, na verdade, encerrada)
odbc_close($connexion);
// retorno do resultado
return array("",$valeurs);
}//getValeurs
?>
Se executarmos a nova aplicação sem ativar a base de dados odbc-valeurs, obtemos o seguinte resultado:

Note-se que o código de erro devolvido pelo controlador ODBC (odbc_error()=S1000) não é muito explícito. Se a base de dados odbc-valeurs for disponibilizada, obtêm-se os mesmos resultados que anteriormente.
Em conclusão, pode-se dizer que esta solução é adequada para a manutenção da aplicação. Com efeito, se a base de dados tiver de ser alterada, a aplicação não terá de ser alterada. O administrador do sistema criará simplesmente uma nova fonte de dados ODBC para a nova base de dados. Ainda no que diz respeito à manutenção, seria aconselhável definir os parâmetros de acesso à base de dados ($dsn, $user, $pwd) num ficheiro separado que a aplicação carregaria no arranque (include).
3.9.5. Recuperar os valores de um formulário
Já recuperámos, em várias ocasiões, os valores de um formulário enviados por um cliente web. O exemplo seguinte mostra um formulário que reúne os componentes HTML mais comuns e destina-se a recuperar os parâmetros enviados pelo navegador do cliente. O formulário é o seguinte:

É apresentado pré-preenchido. Em seguida, o utilizador pode alterá-lo:

Se clicar no botão [Envoyer], o servidor devolve-lhe 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 etiquetas 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, será recuperado na variável $_GET["C"] e, no segundo caso, na variável $_POST["C"]. A tabela seguinte pressupõe a utilização de um POST.
Controlo | 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 sob a forma de uma única cadeia de caracteres: "ligne1\r\nligne2\r\nligne3". As linhas estã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 estiver marcada; caso contrário, a variável não existe. Assim, se a caixa de seleção C1 tiver sido 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ção 2</option> <option>opção3</option> </select> | $_POST["cmbValeurs"]: opção selecionada na lista, por exemplo, «opção 3». | |
<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"]. Deve-se notar a sintaxe específica da baliza 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 dos parâmetros
// depende do método de envio dos mesmos
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"];
// pedido válido?
$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 aspetos deste programa:
- a aplicação não depende do modo de transmissão dos valores do formulário para o servidor. Nos dois casos possíveis (GET e POST), o dicionário dos valores transmitidos é referenciado por $param.
- A partir do conteúdo do campo areaSaisie «linha1\r\nlinha2\r\n...», cria-se uma matriz de cadeias de caracteres através de explode("\r\n", $param["areaSaisie"]). Assim, obtém-se a matriz [ligne1,ligne2,...]. A partir desta, cria-se a cadeia de caracteres "ligne1<br>ligne2<br>..." com a função implode.
- O valor da lista de seleção múltipla lst2 é um tabuário, por exemplo, ["option3","option5"]. A partir deste, cria-se uma cadeia de caracteres «option3<br>option5» com a função implode.
- A aplicação verifica se todos os parâmetros foram definidos. É importante ter em conta que qualquer função URL pode ser chamada manualmente ou por programa e que nem sempre se dispõe dos parâmetros esperados. Se faltar algum parâmetro, é apresentada a página balises.html; caso contrário, é apresentada a página parameters-p1.php. Esta última apresenta os valores recuperados e calculados na parameters.php numa 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. Acompanhamento da sessão
3.10.1. O problema
Uma aplicação web pode consistir em várias trocas de formulários entre o servidor e o cliente. O funcionamento é então o seguinte:
etapa 1
- o cliente C1 estabelece uma ligação com o servidor e efetua o seu pedido inicial.
- O servidor envia o formulário F1 ao cliente C1 e encerra a ligação estabelecida na etapa 1.
etapa 2
- O cliente C1 preenche o formulário e reenvia-o para o servidor. Para tal, o navegador estabelece uma nova ligação com o servidor.
- Este processa os dados do formulário 1, calcula as informações I1 a partir desses dados, envia um formulário F2 ao cliente C1 e encerra a ligação aberta na etapa 3.
Etapa 3
- O ciclo das etapas 3 e 4 repete-se nas etapas 5 e 6. No final da etapa 6, o servidor terá recebido dois formulários F1 e F2 e, a partir destes, terá calculado as informações I1 e I2.
A questão que se coloca é: como é que o servidor consegue manter as informações I1 e I2 associadas ao cliente C1? A este problema chama-se «acompanhamento da sessão do cliente C1». Para compreender a sua origem, analisemos o esquema de uma aplicação de servidor TCP-IP que atende simultaneamente vários clientes:
![]() |
Numa aplicação cliente-servidor TCP-IP clássica:
- o cliente estabelece uma ligação com o servidor
- troca dados com o servidor através dessa ligação
- a ligação é encerrada por um dos dois parceiros
Os dois pontos importantes deste mecanismo são:
- é criada uma ligação única para cada um dos clientes
- esta ligação é utilizada durante todo o período de diálogo entre o servidor e o seu cliente
O que permite ao servidor saber, num determinado momento, com que cliente está a trabalhar é a ligação ou, por outras palavras, o «canal» que o liga ao seu cliente. Sendo este canal dedicado a um determinado cliente, tudo o que chega por esse canal provém 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 ligação com o servidor e faz o seu pedido
- o servidor responde e encerra a ligação
Se, no momento T1, um cliente C fizer um pedido ao servidor, obtém uma ligação C1 que servirá para a única troca de pedido-resposta. Se, no momento T2, esse mesmo cliente efetuar uma segunda solicitação ao servidor, obterá uma ligação C2 diferente da ligação C1. Para o servidor, não há, portanto, qualquer diferença entre esta segunda solicitação do utilizador C e a sua solicitação inicial: em ambos os casos, o servidor considera o cliente como um novo cliente. Para que haja uma ligação entre as diferentes ligações do cliente C ao servidor, é necessário que o cliente C seja «reconhecido» pelo servidor como um «cliente habitual» e que o servidor recupere as informações que possui sobre esse cliente habitual.
Imaginemos um sistema de gestão que funcionasse da seguinte forma:
- Existe uma única fila de espera
- Existem vários balcões. Assim, vários clientes podem ser atendidos simultaneamente. Quando um balcão fica livre, um cliente sai da fila de espera para ser atendido nesse balcão
- Se for a primeira vez que o cliente se apresenta, a pessoa no balcão entrega-lhe uma ficha com um número. O cliente só pode fazer uma pergunta. Quando obtém a resposta, deve sair do balcão e passar para o fim da fila de espera. O funcionário do balcão regista as informações desse cliente num processo com o seu número.
- Quando chega novamente a sua vez, o cliente pode ser atendido por um funcionário diferente do da vez anterior. Este pede-lhe a ficha e recupera o processo com o número da ficha. Mais uma vez, o cliente faz um pedido, obtém uma resposta e são adicionadas informações ao seu processo.
- E assim sucessivamente... Com o passar do tempo, o cliente terá a resposta a todas as suas solicitações. O acompanhamento entre as diferentes solicitações é feito através do ficha e do processo a ele associado.
O mecanismo de acompanhamento de sessão numa aplicação web cliente-servidor é análogo ao funcionamento anterior:
- na sua primeira solicitação, um cliente recebe um token do servidor web
- ele apresentará esse token em cada uma das suas solicitações subsequentes para se identificar
O token pode assumir diferentes formas:
- um campo oculto num formulário
- o cliente efetua a sua primeira solicitação (o servidor reconhece-o pelo facto de o cliente não possuir um token)
- O servidor envia a sua resposta (um formulário) e coloca o token num campo oculto do mesmo. Nesse momento, a ligação é encerrada (o cliente sai da janela com o seu token). O servidor terá, eventualmente, associado informações a esse token.
- O cliente efetua o seu segundo pedido, reenviando o formulário. O servidor recupera o token desse formulário. Pode então processar o segundo pedido do cliente, tendo acesso, graças ao token, às informações calculadas durante o primeiro pedido. São adicionadas novas informações ao ficheiro associado ao token, é enviada uma segunda resposta ao cliente e a ligação é encerrada pela segunda vez. O token foi novamente incluído no formulário da resposta para que o utilizador o possa apresentar na sua próxima solicitação.
- E assim sucessivamente...
A principal desvantagem desta técnica é que o token tem de ser inserido num formulário. Se a resposta do servidor não for um formulário, o método do campo oculto deixa de ser utilizável.
- A técnica do cookie
- o cliente faz a sua primeira solicitação (o servidor reconhece-o pelo facto de o cliente não ter um token)
- o servidor responde adicionando um cookie aos cabeçalhos HTTP da resposta. Isto é feito através do comando HTTP Set-Cookie:
Set-Cookie: param1=valor1;param2=valor2;....
onde parami são nomes de parâmetros e valeursi os respetivos valores. Entre os parâmetros, estará o token. Muitas vezes, apenas o token consta no cookie, sendo as restantes informações registadas pelo servidor na pasta associada ao token. O navegador que recebe o cookie irá armazená-lo num ficheiro no disco. Após a resposta do servidor, a ligação é encerrada (o cliente sai da janela com o seu token).
- (continuação)
- o cliente faz a sua segunda solicitação ao servidor. Sempre que é feita uma solicitação a um servidor, o navegador verifica, entre todos os cookies que possui, se existe algum proveniente do servidor solicitado. Se sim, envia-o ao servidor sempre sob a 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 irá identificar o token que lhe permite reconhecer o cliente e recuperar as informações a ele associadas.
Esta é a forma mais utilizada de token. Apresenta uma desvantagem: um utilizador pode configurar o seu navegador para que não aceite cookies. Este tipo de utilizador não tem, então, acesso às aplicações web que utilizam cookies.
- Reescrita de URL
- o cliente faz o seu primeiro pedido (o servidor reconhece-o pelo facto de o cliente não ter um token)
- o servidor envia a sua resposta. Esta contém links que o utilizador deve utilizar para continuar a aplicação. No URL de cada um desses links, o servidor adiciona o token na forma URL;token=valor.
- Quando o utilizador clica num dos links para continuar a aplicação, o navegador envia o seu pedido ao servidor web, incluindo nos cabeçalhos HTTP o token solicitado na forma URL URL;token=valor. O servidor consegue então recuperar o token.
3.10.2. O API e o PHP para o acompanhamento da sessão
Apresentamos agora os principais métodos úteis para o acompanhamento da sessão:
inicia a sessão à qual pertence o pedido atual. Se este 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 escrita | |
elimina os dados contidos na sessão atual. Estes dados permanecem disponíveis para a troca cliente-servidor em curso, 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 Wrox e distribuído pela Eyrolles. Este exemplo permite ver como funciona uma sessão PHP. A página principal é a seguinte:

Nela encontram-se os seguintes elementos:
- o ID da sessão obtido pela função session_id(). Este ID, gerado pelo navegador, é enviado ao cliente através de um cookie que o navegador reenvia quando solicita um URL da mesma árvore de diretórios. É isto que mantém a sessão.
- um contador que é incrementado à medida que o navegador faz pedidos e que indica que a sessão está efetivamente a ser mantida.
- um link que permite eliminar os dados associados à sessão atual. Isto é 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");
// inicia-se uma sessão
session_start();
// é necessário invalidá-la?
$action=$_GET["action"];
if($action=="invalider"){
// fim da sessão
session_destroy();
}//if
// gestão do contador
if(! isset($_SESSION["compteur"]))
// o contador não existe — criamo-lo
$_SESSION["compteur"]=0;
// o contador existe - incrementamo-lo
else $_SESSION["compteur"]++;
// recuperar o ID da sessão atual
$idSession=session_id();
// recupera-se o contador
$compteur=$_SESSION["compteur"];
// passamos o controlo para a página de visualização
include "cycledevie-p1.php";
?>
É importante destacar os seguintes pontos:
- logo no início da aplicação, é iniciada uma sessão. 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, é criado um novo token de sessão.
- Se o cliente tiver enviado um parâmetro action com o valor «invalider», os dados da sessão são marcados como «a eliminar» para a próxima troca. Não serão guardados no servidor no final da troca, ao contrário do que acontece numa troca normal.
- Recupera-se um contador associado à sessão no dicionário $_SESSION, bem como o ID da sessão (session_id()).
- A página a enviar ao cliente é gerada pelo programa cycledevie-p1.php
A página cycledevie-p1.php apresenta 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>
Repare-se no código URL associado a cada uma das duas ligações:
- cycledevie.php para recarregar a página
- cycledevie.php?action=invalider para invalidar a sessão. Neste caso, é anexado um parâmetro action=invalider ao URL. Este será recuperado pelo programa do servidor cycledevie.php através da instrução $action=$_GET["action"].
Vamos recarregar a página duas vezes seguidas:

O contador foi efetivamente incrementado. O ID da sessão não se alterou. 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 da sessão que anteriormente. O contador, por sua vez, volta a zero. A função session_destroy() não tem, portanto, um efeito imediato. Os dados da sessão atual são eliminados apenas para a troca cliente-servidor que se segue àquela em que a eliminação é efetuada. A sessão ID não se alterou, o que parece indicar que a função 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 o 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 utilizar cookies. Vamos agora configurá-lo para não utilizar cookies. Isto significa que não irá armazenar nem reenviar os cookies enviados pelo servidor. Espera-se, então, que as sessões deixem de funcionar. Vamos tentar com uma primeira troca de dados:

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 em curso e que o contador está a ser gerido corretamente. Como é que isto é possível, uma vez que os cookies foram desativados e já não há troca de tokens entre o servidor e o navegador? O URL da captura de ecrã acima dá-nos a resposta:
Este é o URL do link «Atualizar a página». Vamos verificar o código-fonte da página apresentada pelo navegador:
<a href="cycledevie.php?action=invalider&PHPSESSID=587ce26f943a288d8f41212e30fed13c">Invalider la session</a>
<a href="cycledevie.php?PHPSESSID=587ce26f943a288d8f41212e30fed13c">Recharger la page</a>
Recorde-se 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 reescreveu, portanto, por si próprio os URL dos dois links, adicionando-lhes o token de sessão. Assim, este é efetivamente transmitido pelo navegador quando os links são ativados. O que explica que, mesmo sem os cookies ativados, a sessão continue a ser gerida corretamente.
3.10.4. Exemplo 3
Propomos escrever uma aplicação PHP que funcione como cliente da aplicação compteur anterior. Esta chamá-la-ia N vezes seguidas, sendo que N seria passado como parâmetro. O nosso objetivo é mostrar um cliente web programado e a forma de gerir o token de sessão. 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 limitar a pesquisa apenas aos cabeçalhos HTTP
Eis um exemplo com o URL http://localhost/poly/sessions/2/cycledevie.php. Este programa é o já descrito, com uma ligeira diferença:
// define-se 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 determinados parâmetros do cookie que irá 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 é eliminado pelo navegador que o recebeu quando este é 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 tiver recebido o cookie da máquina localhost, reenviará o cookie para qualquer URL que se encontre 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 apresenta tudo o que recebe do servidor. Acima, vemos o comando HTTP Set-cookie, com o qual o servidor envia um cookie ao seu cliente. Neste caso, 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 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 não está presente. O cookie será, portanto, eliminado quando o navegador for fechado. Um cookie pode ter um prazo de validade de N dias, por exemplo. Enquanto estiver válido, o navegador reenviá-lo-á sempre que uma das URL do seu domínio (Path) for consultada. Tomemos como exemplo um site de vendas online de CD. Este pode acompanhar o percurso do cliente no seu catálogo e determinar, gradualmente, as suas preferências: música clássica, por exemplo. Essas preferências podem ser armazenadas num cookie com uma validade de 3 meses. Se esse mesmo cliente regressar ao site ao fim de um mês, o navegador reenviará o cookie para a aplicação do servidor. Esta, com base nas informações contidas no cookie, poderá então adaptar as páginas geradas às preferências do cliente.
Segue-se 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");
// paragem
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");
// paragem
exit(2);
}//se
// o primeiro argumento é um URL
$URL=strtolower($argv[1]);
// preparação da ligação
$connexion=curl_init($URL);
// configuração da ligação
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// execução da ligação
curl_exec($connexion);
// encerramento da ligaçã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 ligaçã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 ligação a $URL com as opções solicitadas. Exibe no ecrã tudo o que o servidor envia | |
encerra a ligação |
O programa anterior é bastante simples. No entanto, a biblioteca CURL não permite manipular com precisão a resposta do servidor, por exemplo, analisá-la linha a linha. O programa seguinte faz o mesmo que o anterior, mas utilizando as funções de rede básicas da PHP. Servirá de ponto de partida para a criação de um cliente para a 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");
// paragem
exit(1);
}//if
// ligaçã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){
// liga-se a $URL
// gera um GET ou um HEAD, dependendo do valor do cabeçalho
// a resposta do servidor constitui 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";
// paragem
return $résultats;
}//if
// abertura de uma ligaçã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 contactado
// este 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 vai agora responder no canal $connexion. Vai enviar todos
// esses dados e, em seguida, encerrará o canal. O cliente lê, portanto, tudo o que chega de $connexion
// até ao encerramento do canal
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// o cliente, por sua vez, encerra a ligação
fclose($connexion);
// voltar
return $résultats;
}//getURL
?>
Vamos comentar alguns pontos deste programa:
- o programa aceita dois parâmetros:
- um URL URL cujo conteúdo se pretende apresentar no ecrã.
- um método GET ou HEAD, a utilizar consoante se pretenda 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. Esta função devolve um objeto $résultats. Este objeto possui um campo erreur caso ocorra 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 contactado.
- A função getURL analisa o URL e o $URL com a função parse_url. A instrução $url=parse_url($URL) irá criar o tabuleiro associativo $url com as seguintes chaves, se existirem:
- scheme: o protocolo do URL (http, ftp, etc.)
- host: o computador do URL
- port: a porta do URL
- path: o caminho do URL
- querystring: os parâmetros do URL
Uma URL será válida se tiver o formato http://machine[:port][/chemin].
- O parâmetro $header também é verificado
- assim que os parâmetros forem verificados e estiverem corretos, é criada uma ligação TCP na máquina ($hote, $port), depois envia-se o comando HTTP, GET ou HEAD, consoante o parâmetro $header.
- em seguida, lê-se a resposta do servidor, que é colocada 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 terá reparado que a resposta do servidor não é a mesma consoante 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. Isto deve-se ao facto de o segundo cliente ter enviado o cabeçalho HTTP: get URL HTTP/1.0, que solicita um URL e indica que está a utilizar o protocolo HTTP versão 1.0, o que obriga o servidor a responder-lhe 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 na sua resposta. Isto mostra-nos que o primeiro cliente fez a sua solicitação indicando que estava a trabalhar com o protocolo HTTP versão 1.1.
Vamos agora criar o programa clientCompteur, chamado da seguinte forma:
clientCompteur URL N [JSESSIONID]
- URL: URL da aplicação cycledevie
- N: número de chamadas a efetuar para esta aplicação
- PHPSESSID: parâmetro opcional — token de uma sessão
O objetivo do programa é chamar N vezes a aplicação cycledevie.php, gerindo o cookie de sessão e apresentando, em cada chamada, o valor do contador devolvido pelo servidor. No final das N chamadas, o valor do contador deve ser N-1. Eis 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 apresenta:
- os cabeçalhos HTTP que 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
Vê-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 o 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. Isto pode indicar uma potencial falha de segurança. Se eu conseguir interceptar um token de sessão na rede, posso então fazer-me passar por quem iniciou essa sessão. No nosso exemplo, a primeira chamada (sem token de sessão) representa quem inicia a sessão (talvez com um nome de utilizador e palavra-passe que lhe conferem o direito a 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 curso for uma operação bancária, isto pode tornar-se problemático...
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]");
// paragem
exit(1);
}//if
// recuperação dos parâmetros
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// ligação e visualizaçã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){
// liga-se ao URL
// gera um GET ou um HEAD, dependendo do valor do cabeçalho
// a resposta do servidor constitui 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 efetuadas as chamadas de $N para $URL
for($i=0;$i<$N;$i++){
// abertura de uma ligaçã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 contactado
// este 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 vai agora responder no canal $connexion. Vai enviar todos
// os seus dados e, em seguida, encerrará o canal.
// O cliente começa por ler 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 do token, caso ainda não tenha sido encontrado
if(! $PHPSESSID){
// procura da linha «set-cookie»
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// o token foi encontrado — é guardado
$PHPSESSID=$champs[1];
}//if
}//se
// procura do modo de transferência do documento
if(! $CHUNKED){
// procura da linha Transfer-Encoding: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// transferência por partes
$CHUNKED=1;
}//if
}//if
}//linha seguinte
// eco da linha
echo "<-- $ligne\n";
// a leitura do documento depende da forma como foi enviado
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// procura do contador no documento
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// encontrou-se o contador - exibe-se
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
// o cliente encerra a ligaçã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 sob a forma
// número de caracteres da parte em hexadecimal
// continuação do fragmento
// linha vazia
// leia-se o tamanho do fragmento na 1.ª 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);
// fragmento seguinte
// leitura do tamanho do fragmento
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//enquanto
// terminou
return $doc;
}// getChunkedDoc
//--------------------------
function envoie($flux,$msg){
// envia $msg para $flux
fwrite($flux,$msg);
// echo ecrã
echo "--> $msg";
}//envia
?>
Vamos analisar os pontos importantes deste programa:
- é necessário efetuar N trocas cliente-servidor. É por isso que estas se encontram num ciclo
- em cada troca, o cliente estabelece uma ligação TCP-IP com o servidor. Assim que a ligação é estabelecida, o cliente envia ao servidor os cabeçalhos HTTP da 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, é enviado na forma de um cookie; caso contrário, não é enviado. Note-se que o cliente indicou que estava a utilizar o protocolo HTTP/1.1. O que explica que, posteriormente, o servidor lhe envie 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 o seu pedido, o cliente aguarda a resposta do servidor. Começa por analisar os cabeçalhos HTTP dessa resposta. 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 o reenviar ao servidor na próxima troca de dados. A linha «Transfer-Encoding: chunked», se estiver presente, indica que o servidor irá enviar um documento em partes. Cada parte é então enviada ao cliente na seguinte forma:
Se a linha «Transfer-Encoding: chunked» não estiver presente, o documento é enviado de uma só vez, a seguir à linha vazia dos cabeçalhos «HTTP». Assim, dependendo da presença ou ausência desta linha, o modo de receção do documento será diferente. O código de interpretação dos cabeçalhos «HTTP» é o seguinte:
<?php
...
// O cliente começa por ler 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";
// procura do token, caso ainda não tenha sido encontrado
if(! $PHPSESSID){
// procura da linha «set-cookie»
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// encontrou-se o token - guarda-se
$PHPSESSID=$champs[1];
}//if
}//se
// procura do modo de transferência do documento
if(! $CHUNKED){
// procura da linha Transfer-Encoding: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// transferência por partes
$CHUNKED=1;
}//if
}//if
}//linha seguinte
- Quando o token for encontrado pela primeira vez, não será novamente procurado nas chamadas seguintes 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 forma diferente, consoante o seu modo de transferência:
<?php
...
// a leitura do documento depende da forma como 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. Esta pesquisa é também efetuada com uma expressão regular:
<?php
...
// procura do contador no documento
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// encontrou-se o contador - exibe-se
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//se
- Caso o servidor envie o documento de uma só vez, a sua receção é 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 sua leitura torna-se mais complexa:
<?php
...
function getChunkedDoc($connexion){
// leitura do documento em $connexion
// este documento é enviado em partes sob a forma
// número de caracteres da parte em hexadecimal
// continuação do fragmento
// a dimensão do fragmento é lida na primeira linha
$taille=hexdec(rtrim(fgets($connexion,10000)));
// lê-se o documento que se segue
$doc="";
while($taille!=0){
// leitura de um segmento de $taille caracteres
$doc.=fread($connexion,$taille);
// linha vazia
fgets($connexion,10000);
// fragmento seguinte
// leitura do tamanho do fragmento
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//enquanto
// terminou
return $doc;
}// getChunkedDoc
Recorde-se que uma parte do documento é enviada na forma
Começa-se, portanto, por ler o tamanho do documento. Uma vez conhecido este, solicita-se à função fread que leia $taille caracteres no fluxo $connexion e, em seguida, a linha vazia que se segue. Repetimos este processo até que o servidor envie a informação de que vai enviar um documento com tamanho 0.
3.10.5. Exemplo 4
No exemplo anterior, o cliente web devolve o token sob a forma de um cookie. Vimos que também o pode devolver no próprio URL solicitado, sob a forma URL;PHPSESSID=xxx. Vamos verificar isso. O programa clientCompteur.php é transformado em clientCompteur2.php e alterado 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: encerrada
envoie($connexion, "Connection: close\n");
// linha vazia
envoie($connexion,"\n");
....
O cliente solicita, portanto, o URL do contador através do GET URL;PHPSESSID=xx HTTP/1.1 e deixa de enviar qualquer cookie. Esta é a única alteração. Eis 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-lhe enviando-lhe o token. O cliente volta então a solicitar o mesmo URL, anexando-lhe o token recebido. Verifica-se que o contador foi efetivamente incrementado, o que comprova que o servidor reconheceu corretamente que se tratava da mesma sessão.
3.10.6. Exemplo 5
Este exemplo mostra uma aplicação composta por três páginas, a que chamaremos page1, page2 e page3. O utilizador deve aceder a elas nesta ordem:
- a página1 é um formulário que solicita uma informação: um nome
- a página2 é um formulário obtido em resposta ao envio do formulário da página1. Solicita uma segunda informação: uma idade
- a página 3 é um documento HTML que apresenta o nome obtido na página 1 e a idade obtida na página 2.
Existem 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. Recebe em resposta o formulário da página2 ou, novamente, o formulário da página1, caso este estivesse incorreto.
- na terceira troca, o cliente envia o formulário da página2 (idade) para o servidor. Recebe em troca o formulário da página3 ou, novamente, o formulário da página2, caso este estivesse incorreto. O documento da página3 apresenta 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 registar o nome na troca 2, para que este 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:

Preenche-se o campo da idade:

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

Quando se envia a página «page1» para o servidor, este pode devolvê-la com um código de erro se o nome estiver em branco:

Quando se envia a página page2 para o servidor, este pode devolvê-la com um código de erro se a idade for inválida:

A aplicação é composta por seis programas:
recorre ao 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 é novamente apresentada pelo page1.php; caso contrário, é a página2 que é apresentada pelo page2.php. | |
exibe a página2. O formulário da página2 é processado pelo 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 que é exibida pelo page3.php. | |
exibe a página3. |
A etapa 1 da aplicação é 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"]=""; //reinicialização da 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);
?>
É importante destacar os seguintes pontos:
- a aplicação necessita de um acompanhamento da sessão. Por isso, cada etapa da mesma inicia uma sessão.
- As informações da sessão a conservar serão armazenadas num objeto $session.
- As informações necessárias para a exibição das diferentes (três) páginas da aplicação serão colocadas num objeto $requête.
O programa page1.php apresenta 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 que contém dois campos: nom e erreurs. A página apresenta o valor destes dois campos.
- Além disso, apresenta um formulário. Os valores deste (nome) são enviados pelo método POST para o programa etape2.php:
A aplicação etape2.php é responsável por processar os valores do formulário da página 1 e por voltar a apresentar a página 1 caso haja erros (nome incorreto); caso contrário, apresenta a página 2 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 existir um parâmetro «nome»
// registado 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 «nome» estiver presente, verifica-se a 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 — guarda-se o nome 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 do pedido $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 por verificar se dispõe efetivamente do parâmetro nom esperado. Se não for esse o caso, faz com que seja exibida novamente uma página1 vazia. Esta situação é possível se etape2 for chamada diretamente por um cliente que não lhe passe quaisquer parâmetros.
- Se o parâmetro nom estiver presente, verifica-se a sua validade. Isto é feito através de um procedimento denominado calculerPage, cuja função é produzir um objeto $page com um campo erreurs que é uma matriz de erros. É possível ocorrer apenas um erro, mas quisemos demonstrar que é possível gerir 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 é guardado no objeto $session, que armazena os dados relacionados com a sessão atual. Em seguida, é apresentada a página page2.
O programa page2.php apresenta 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. Apresenta o conteúdo de um objeto $requête que contém os campos nom, age e erreurs. Apresenta 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 reduzidos à idade:
<?php
// etape3.php
// configuração
ini_set("register_globals","off");
ini_set("display_errors","off");
// início da sessão
session_start();
// recuperam-se 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, envia-se 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 a 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 erros - memorização 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 do pedido $requête
// retorna uma matriz de erros em $page->erros
// inicialmente, sem erros
$page->erreurs=array();
// a idade deve ter 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 por recuperar o nome da sessão (proveniente da página 1) e a idade do formulário da página 2. Se faltar alguma destas informações, é apresentada a página 1.
- 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, é exibida a página 3. Esta limita-se a exibir os dois valores (nome, idade) obtidos através dos dois formulários (página 1, página 2).

