Skip to content

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.

<?php
    echo "coucou";
?>

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:

short_open_tag = On
permite incluir instruções entre as balizas <? >. No off, estas devem ser incluídas entre <?php ... >
asp_tags = Off
no on permite utilizar a sintaxe <% =variável %> utilizada pela tecnologia ASP (Active Server Pages)
expose_php = On
permite o envio do cabeçalho HTTP X-Powered-By: PHP/4.3.0-dev. Em off, este cabeçalho é suprimido.
error_reporting  =  E_ALL & ~E_NOTICE
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
display_errors = Off
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.
log_errors = On
os erros serão guardados num ficheiro
track_errors = On
armazena o último erro ocorrido na variável $php_errormsg
error_log = E:\Program Files\EasyPHP\php\erreurs.log
define o ficheiro de registo de erros (se log_errors=on)
register_globals = Off
em on, algumas variáveis tornam-se globais. Considerado uma falha de segurança.
default_mimetype = "text/html"
gera, por predefinição, o cabeçalho HTTP: Content-type: text/html
include_path    =".;E:\Program Files\EasyPHP\php\pear\"
a lista de diretórios que serão explorados na procura dos ficheiros exigidos pelas diretivas include ou require
session.save_path = /temp
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:

ini_get("confVariable")
retorna o valor da variável de configuração confVariable
ini_set("confVariable","valeur")
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:

Image

A parte dinâmica da página foi gerada pelo código PHP:

            <?php
              $maintenant=time();
              echo date("j/m/y, h:i:s",$maintenant);
            ?>

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:

2/10/02, 06:13:47

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:

Image

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

  include "heure3-page1.php";

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:

Image

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.

Image

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:

Image

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:

       <form name="frmPersonne" method="post">

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:

  1. É 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.
  2. 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:
       <form name="frmPersonne" method="get">
  • a aplicação recupera agora uma variável $get em vez de $post:
     <?php
          // foram enviados valores para o servidor? 
         if ($get) { 
     ?>

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:

Image

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:

Image

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:

HTTP_ACCEPT
tipos de documentos aceites pelo cliente web
HTTP_ACCEPT_CHARSET
tipos de caracteres aceites nos documentos
HTTP_ACCEPT_ENCODING
tipos de codificação aceites para os documentos
HTTP_ACCEPT_LANGUAGE
tipos de idiomas aceites para os documentos
HTTP_CONNECTION
tipo de ligação com o servidor. Keep-Alive: o servidor deve manter a ligação aberta após ter enviado a sua resposta
HTTP_KEEP_ALIVE
? tempo máximo de permanência da ligação aberta
HOST
máquina anfitriã consultada pelo cliente
HTTP_USER_AGENT
identidade do cliente
REMOTE_ADDR
endereço IP do cliente
REMOTE_PORT
Porta de comunicação utilizada pelo cliente
SERVER_PROTOCOL
protocolo HTTP utilizado pelo servidor
REQUEST_METHOD
método de consulta utilizado pelo cliente (GET ou POST)
QUERY_STRING
pedido ?param1=val1&param2=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:

Image

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

Image

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:

Image

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

Image

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:

Image

A resposta é diferente:

Image

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:

  1. Ligação ao SGBD
  2. Envio de consultas SQL para uma base de dados SGBD
  3. Processamento dos resultados dessas consultas
  4. 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 au SGBD
$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.
requêtes SQL
$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, ...)
traitement des
 résultats d'un
 select
$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
traitement des 
résultats d'une 
requête de mise à 
jour
$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.
fermeture de la
 connexion
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

Image

  • sem erro

Image

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:

Image

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:

Image

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

Image

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

Image

Windows DSN name 
o nome atribuído à fonte de dados ODBC (odbc-valores)
MySQL host 
o nome do computador que aloja o SGBD MySQL que gere a fonte de dados (localhost)
MySQL database name 
o nome da base de dados MySQL, que é a fonte de dados (dbValeurs)
User 
um utilizador com direitos de acesso suficientes à base de dados MySQL a gerir (admDbValeurs)
Password 
a sua palavra-passe (mdpDbValeurs)

PHP é capaz de trabalhar com controladores ODBC. A tabela seguinte apresenta as funções úteis a conhecer:

connexion au SGBD
$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êtes SQL
$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
traitement des
 résultats d'un
 select
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:

while(odbc_fetch_row($résultats)){
    // processa a linha atual com odbc_result
}//enquanto
fermeture de la 
connexion
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:

Image

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:

Image

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

Image

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

Image

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
formulaire
<form method="POST" >
 
champ de 
saisie
<input type="text" name="txtSaisie" size="20" value="algumas palavras">
$_POST["txtSaisie"]: valor contido no campo txtSaisie do formulário
champ de
 saisie cachée
<input type="password" name="txtMdp" size="20" value="unMotDePasse">
$_POST["txtmdp"]: valor contido no campo txtMdp do formulário
champ de 
saisie
multilignes
<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».
boutons radio
<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.
cases à
cocher
<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.
Combo
<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».
liste à
sélection
unique
<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».
liste à 
sélection
multiple
<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[].
champ caché
<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:

    <form method="POST" action="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.
  if($méthode=="GET")
      $param=$_GET;
      else $param=$_POST;
  • 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

  1. o cliente C1 estabelece uma ligação com o servidor e efetua o seu pedido inicial.
  2. O servidor envia o formulário F1 ao cliente C1 e encerra a ligação estabelecida na etapa 1.

etapa 2

  1. 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.
  2. 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

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

  1. é criada uma ligação única para cada um dos clientes
  2. 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:

session_start()
inicia a sessão à qual pertence o pedido atual. Se este ainda não fizesse parte de uma sessão, esta é criada.
session_id()
identificador da sessão atual
$_SESSION[$var]
dicionário que armazena os dados de uma sessão. Acessível para leitura e escrita
session_destroy()
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:

Image

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:

  1. cycledevie.php para recarregar a página
  2. 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:

Image

O contador foi efetivamente incrementado. O ID da sessão não se alterou. Vamos agora invalidar a sessão:

Image

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

Image

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:

Image

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

Image

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:

http://localhost/poly/sessions/1/cycledevie.php?PHPSESSID=587ce26f943a288d8f41212e30fed13c

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:

$connexion=curl_init($URL)
inicializa um objeto CURL com o URL a ser alcançado
curl_setopt ($connexion,option,valeur)
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
curl_exec($connexion)
estabelece a ligação a $URL com as opções solicitadas. Exibe no ecrã tudo o que o servidor envia
curl_close($connexion)
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
            for($i=0;$i<$N;$i++){
  • 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:
Cookie : 
Transfer-Encoding: chunked

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:

[nombre de caractères du document en hexadécimal]
[document]
[ligne vide]

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

[taille]
[partie de document]
[ligne vide]

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:

Image

Preenche-se o campo do nome:

Image

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

Image

Preenche-se o campo da idade:

Image

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

Image

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:

Image

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:

Image

A aplicação é composta por seis programas:

etape1.php
recorre ao page1.php
page1.php
exibe a página1. O formulário da página1 é processado pelo etape2.php.
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.
page2.php
exibe a página2. O formulário da página2 é processado pelo etape3.php.
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.
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:
    <form name="frmNom" method="POST" action="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.

    <form name="frmAge" method="POST" action="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).
<? // page3.php ?>

<html>
  <head>
    <title>page 3</title>
  </head>
  <body>
    <h3>Page 3/3</h3>
      <table>
        <tr>
          <td>Nom</td>
          <td><font color="green"><? echo $requête->nom ?></font></td>
        </tr>
        <tr>
          <td>Votre âge</td>
          <td><font color="green"><? echo $requête->age ?></font></td>
        </tr>
      </table>
  </body>
</html>