Skip to content

7. Estudo de caso: gerenciamento de um banco de dados de artigos na web

Os códigos deste estudo de caso estão disponíveis |ICI|.

Objetivos:

  • escrever uma classe para gerenciar um banco de dados de artigos
  • escrever um aplicativo web baseado nessa classe
  • apresentar as folhas de estilo
  • propor uma abordagem inicial de metodologia de desenvolvimento para aplicativos web simples
  • apresentar o JavaScript no navegador do cliente

Créditos: A essência deste estudo de caso foi extraída do livro “Les cahiers du programmeur - PHP/MySQL”, de Jean-Philippe Leboeuf, publicado pela editora Eyrolles.

7.1. Introdução

Um comerciante deseja gerenciar os artigos que vende em sua loja. Ele já possui um aplicativo ACCESS que realiza essa tarefa, mas está interessado em explorar as possibilidades da web. Ele tem uma conta em um provedor de acesso à internet que permite que seus clientes instalem scripts PHP em suas pastas pessoais. Isso permite que eles criem sites dinâmicos. Além disso, esses mesmos clientes dispõem de uma conta MySQL que lhes permite criar tabelas capazes de fornecer dados aos seus scripts PHP. Assim, o comerciante possui uma conta MySQL com login “adarticles” e senha “mdparticles”. Ele possui um banco de dados “dbarticles” sobre o qual tem todos os direitos. Nosso comerciante, portanto, dispõe de elementos suficientes para colocar seu gerenciamento de artigos na web. Com a ajuda de vocês, que possuem conhecimentos em desenvolvimento web, ele se lança nessa aventura.

7.2. O banco de dados

Nosso comerciante cria o seguinte esboço da interface inicial da web que ele gostaria de ter:

Image

Haveria dois tipos de usuários:

  • administradores, que poderiam realizar todas as ações na tabela de artigos (adicionar, modificar, excluir, consultar, etc.). Esses usuários poderão utilizar todos os itens do menu acima. Em particular, poderão emitir qualquer consulta SQL por meio da opção [Requête SQL].
  • os usuários normais (que não são administradores), que teriam direitos restritos: direitos de adicionar, modificar, excluir e consultar. Eles podem ter apenas alguns desses direitos, como, por exemplo, apenas o direito de consulta.

Como existem diversos tipos de usuários do banco de dados que não possuem os mesmos direitos, é necessária uma autenticação. É por isso que a página inicial começa com esse processo. Para saber quem é quem e quem tem permissão para fazer o quê, serão utilizadas duas tabelas: USERS e DROITS. A tabela USERS teria a seguinte estrutura:

login
login do usuário, que o identifica de forma exclusiva. Esse campo é a chave primária da tabela.
mdp
a senha do usuário em texto simples
admin 
o caractere 'y' (sim) se o usuário for administrador; caso contrário, o caractere 'n' (não).

O conteúdo da tabela poderia ser o seguinte:

Image

A tabela DROITS especifica os direitos dos usuários que não são administradores presentes na tabela USERS. Sua estrutura é a seguinte:

login
login do usuário, que o identifica de forma exclusiva.
Esse campo é uma chave estrangeira da tabela DROITS e faz referência
à coluna “login” da tabela USERS.
table
o nome da tabela sobre a qual o usuário possui direitos.
ajouter
o caractere 'y' (sim) se o usuário tiver permissão para adicionar registros na tabela,
caso contrário, o caractere 'n' (não).
modifier
direito de modificação: 'y' ou 'n'
supprimer
direito de excluir: 'y' ou 'n'
consulter
direito de consulta: 'y' ou 'n'

O conteúdo da tabela poderia ser o seguinte:

Image

Observações:

  • Um usuário U que conste na tabela USERS e não conste na tabela DROITS não possui nenhum direito.
  • Em nosso exemplo, os usuários terão acesso a apenas uma tabela, a tabela ARTICLES. Mas nosso comerciante, previdente, adicionou o campo “tabela” à estrutura da tabela DROITS para ter a possibilidade de adicionar novas tabelas à sua aplicação posteriormente.
  • Por que gerenciar direitos em nossas próprias tabelas, se partimos do pressuposto de que usaremos um banco de dados MySQL capaz, por si só (e melhor do que nós), de gerenciar esses direitos em suas próprias tabelas? Simplesmente porque nosso comerciante não possui direitos de administração sobre o banco de dados MySQL que lhe permitiriam criar usuários e conceder-lhes direitos. Não nos esqueçamos, de fato, de que o banco de dados MySQL está hospedado em um provedor de acesso e que o comerciante é apenas um simples usuário do mesmo, sem nenhum direito de administração (felizmente). No entanto, ele possui todos os direitos sobre um banco de dados chamado dbarticles, ao qual acessa, por enquanto, com o login admarticles e a senha mdparticles. É nesse banco de dados que se encontram todas as tabelas do aplicativo.

A tabela ARTICLES reúne as informações sobre os artigos vendidos pelo comerciante. Sua estrutura é a seguinte:

code
código do artigo — chave primária da tabela
- exatamente 4 caracteres
nom
nome do artigo
prix
preço
stockActuel
nível atual de estoque
stockMinimum
o nível abaixo do qual deve ser feito um pedido
de reabastecimento deve ser feita

Seu conteúdo, utilizado inicialmente como teste, poderia ser o seguinte:

Image

7.3. As restrições do projeto

O comerciante está migrando aqui um aplicativo local ACCESS para um aplicativo web. Ele não sabe como esse aplicativo ficará nem como evoluirá. No entanto, ele gostaria que o novo aplicativo fosse fácil de usar e escalável. Por esse motivo, seu consultor de TI previu, durante o projeto das tabelas, que poderia haver:

  • vários usuários com diferentes permissões: isso permitirá que o comerciante delegue certas tarefas a outras pessoas sem, por isso, conceder-lhes permissões de administração
  • no futuro, outras tabelas além da tabela ARTICLES

O mesmo consultor faz outras propostas:

  • ele sabe que, no desenvolvimento de software, é preciso separar claramente as camadas de apresentação das camadas de processamento. A arquitetura de um aplicativo web costuma ser a seguinte:

A interface do usuário aqui é um navegador da web, mas também poderia ser um aplicativo autônomo que, por meio da rede, enviaria solicitações HTTP ao serviço web e formataria os resultados que este lhe enviasse. A lógica de aplicação é constituída pelos scripts que processam as solicitações do usuário, neste caso, os scripts PHP. A fonte de dados costuma ser um banco de dados, mas também pode ser um diretório LDAP ou um serviço web remoto. É recomendável que o desenvolvedor mantenha uma grande independência entre essas três entidades, de modo que, se uma delas mudar, as outras duas não precisem mudar, ou apenas minimamente. O consultor de TI do comerciante faz, então, as seguintes propostas:

  • Colocaremos a lógica de negócios do aplicativo em uma classe PHP. Assim, o bloco [Logique applicative] acima será composto pelos seguintes elementos:

No bloco [Logique Applicative], será possível distinguir

  • o bloco [IE=Interface d'Entrée], que é a porta de entrada da aplicação. Ela é a mesma independentemente do tipo de cliente.
  • o bloco [Classes métier], que reúne as classes necessárias à lógica do aplicativo. Elas são independentes do cliente.
  • o bloco dos geradores de páginas de resposta [IS1 IS2 ... IS=Interface de Sortie]. Cada gerador é responsável por formatar os resultados fornecidos pela lógica da aplicação para um determinado tipo de cliente: código HTML para um navegador ou um celular (WAP), código XML para um aplicativo autônomo, etc.

Esse modelo garante uma boa independência em relação aos clientes. Se o cliente mudar ou se quisermos alterar a forma de apresentar os resultados, serão os geradores de saída [IS] que deverão ser criados ou adaptados.

  • Em uma aplicação web, a independência entre a camada de apresentação e a camada de processamento pode ser aprimorada com o uso de folhas de estilo. Elas controlam a apresentação de uma página da Web dentro de um navegador. Para alterar essa apresentação, basta alterar a folha de estilo associada. Não é necessário mexer na lógica de processamento. Portanto, utilizaremos aqui uma folha de estilo.
  • No diagrama acima, é a classe de negócio que fará a interface com a fonte de dados. Por hipótese, essa fonte é, neste caso, um banco de dados MySQL. Para permitir a migração para outro banco de dados, utilizaremos a biblioteca PEAR, que oferece classes de acesso a bancos de dados independentes do tipo real destes. Assim, se nosso comerciante crescer a ponto de poder instalar um servidor web IIS da Microsoft em sua empresa, ele poderá substituir o banco de dados MySQL pelo SQL Server sem precisar (ou precisando muito pouco) alterar a classe de negócio.

7.4. A classe “artigos”

A classe de artigos poderia ser definida da seguinte forma:

<?php

     // classe de artigos que opera em uma base de dados de artigos composta pelas seguintes tabelas
     // artigos: (código, nome, preço, stockActuel, stockMinimum)
     // usuários: (login, senha, admin)
     // permissões: (login, tabela, adicionar, modificar, excluir, consultar)

     // é o usuário da turma que deve fornecer o login/senha que permite realizar todas as operações no banco de dados
     // portanto, ele já possui todos os direitos sobre o banco de dados. Isso implica que não é necessário tomar
     // precauções especiais de segurança neste caso

     // bibliotecas
  require_once 'DB.php';

  class articles{

           // atributos
    var $sDSN;                        // a string de conexão
          var $sDatabase;            // o nome do banco de dados
    var $oDB;                        // conexão com o banco de dados
    var $aErreurs;                // lista de erros
    var $oRésultats;            // resultado de uma consulta SELECT
        var $connecté;                // valor booleano que indica se há ou não conexão com o banco de dados
        var $sQuery;                    // a última consulta executada
    var $sUser;                    // identidade do usuário conectado
    var $bAdmin;                    // vale verdadeiro se o usuário for administrador
    var $dDroits;                // o dicionário de seus direitos: tabela ->> array(consultar, adicionar, excluir, modificar)

     // construtor
    function articles($dDSN,$sUser,$sMdp){

             // $dDSN: dicionário que define a ligação a ser estabelecida
       // $dDSN['sgbd']: o tipo do SGBD ao qual é necessário se conectar
       // $dDSN['host']: o nome do servidor que o hospeda      
       // $dDSN['database']: o nome do banco de dados ao qual é necessário se conectar      
       // $dDSN['admin']: o nome de usuário do proprietário do banco de dados ao qual é necessário se conectar
       // $dDSN['mdpadmin']: a senha dele
       // $sUser: o nome de usuário do usuário que deseja utilizar o banco de dados de artigos
       // $sMdp: sua senha

       // cria em $oDB uma conexão com o banco de dados definido por $dDSN sob a identidade de $dDSN['admin']
       // se a conexão for bem-sucedida e o usuário $sUser for autenticado  
           //carrega as permissões em $bAdmin e $dDroits as permissões do usuário $sUser
           // insere em $sDSN a string de conexão com o banco de dados
           // insere em $sDataBase o nome do banco de dados ao qual se conecta
         // define $connecté como verdadeiro
       // se a conexão falhar ou se o usuário $sUser não for identificado corretamente
           // insere as mensagens de erro adequadas na lista $aErreurs
         // encerra a conexão, se necessário
         // define $connecté como falso 

  ...
    }//criador

    // ------------------------------------------------------------------
    function connect(){
             // (re)conexão com a base
...
    }//conectar

    // ------------------------------------------------------------------
    function disconnect(){
      // encerrando a conexão com a base $sDSN
...
    }//desconectar

    // -------------------------------------------------------------------
    function execute($sQuery,$bAdmin){
            // $sQuery: consulta a ser executada
       // $bAdmin: verdadeiro se a solicitação de execução for feita como administrador
...
    }//executar

    // --------------------------------------------------------------------------
    function addArticle($dArticle){
         // adiciona um item $dArticle (código, nome, preço, stockActuel, stockMinimum) à tabela de itens
   ...
    }//adicionar          

    // ----------------------------------------------------------------------
    function modifyArticle($dArticle){
             // altera um artigo $dArticle (código, nome, preço, stockActuel, stockMinimum) da tabela de itens
...
    }//atualização

    // ----------------------------------------------------------------------
    function deleteArticle($sCode){
             // exclui um artigo da tabela de artigos
       // cujo código é $sCode
...
    }//excluir

    // ----------------------------------------------------------------------
    function vérifierArticle(&$dArticle){
         // verifica a validade de um item $dArticle (código, nome, preço, stockActuel, stockMinimum)
...
    }//verificar

    // --------------------------------------------------------------------------
    function selectArticles($dQuery){
             // executa uma consulta SELECT na tabela de artigos
       // esta possui três componentes
       // lista de colunas em $dQuery['colonnes']
       // filtragem em $dQuery['where']
       // ordem de apresentação em $dQuery['orderby']
...
    }//selectArticles            

        // --------------------------------
    function existeArticle($sCode){
         // retorna TRUE se o item de código $sCode existir na tabela de itens
...
    }//existeArticle

    // --------------------------------------
    function existeUser($sUser,$sMdp){
             // verifica se existe o usuário $sUser com a senha $sMdp
       // retorna (int $iErreur, string $sAdmin, tabela de hash $dDroits)
       // $iErreur = -1 em caso de qualquer erro na operação do banco de dados — a lista $aErreurs é então preenchida
       // $iErreur = 1 se o usuário não for encontrado (ausente ou senha incorreta)
       // $iErreur = 2 se o usuário existir, mas não tiver nenhum direito na tabela de direitos
       // $iErreur = 3 se o usuário existir e for administrador
       // $iErreur = 0 se o usuário existir e não for administrador
       // $sAdmin = “y” se o usuário existir e for administrador ($iErreur == 3); caso contrário, é igual à string vazia
       // $dDroits é o dicionário de direitos do usuário caso ele não seja administrador ($iErreur==0)
       // caso contrário, é uma tabela vazia
       // as chaves do dicionário são as tabelas sobre as quais o usuário possui direitos
       // o valor associado a essa tabela é, por sua vez, um dicionário cujas chaves são os direitos
       // (consultar, adicionar, modificar, excluir) e os valores são as cadeias 'y' (sim) ou 'n' (não), conforme o caso
...
    }//existeUser

    // --------------------------------------
    function getCodes(){
         // retorna a tabela de códigos
....
    }//getCodes    

  }//classifica
?>      

Comentários

  • A classe de artigos utiliza a biblioteca PEAR::DB para acessar o banco de dados, daí o comando
require_once 'DB.php';

Essa inclusão pressupõe que o script DB.php esteja em um dos diretórios da opção include_path do arquivo de configuração PHP.

  • O construtor precisa saber a qual banco de dados se conecta e sob qual identidade. Essas informações são fornecidas a ele no dicionário $dDSN. Vale lembrar que a hipótese inicial era de que o banco de dados se chamava dbarticles e pertencia a um usuário chamado admarticles, com a senha mdparticles. Vale lembrar também que essa aplicação permite vários usuários com diferentes direitos. Há aqui uma ambiguidade a ser esclarecida. A conexão é de fato aberta sob a identidade de admarticles e, no final das contas,é sob essa identidade que serão realizadas todas as operações no banco de dados dbarticles, já que esse é o único nome que o SGBD MySQL conhece e que possui direitos suficientes para gerenciar o banco de dados dbarticles. Para “simular” a existência de diferentes usuários, faremos com que o usuário admarticles atue com os direitos do usuário cujo login ($sUser) e senha ($sMdp) foram passados como parâmetros ao construtor. Assim, antes de realizar uma operação no banco de dados de artigos, verificará-se se o usuário ($sUser, $sMdp) possui os direitos necessários para realizá-la. Se sim, será o usuário admarticles quem a realizará em seu nome.
  • O login e a senha do administrador do banco de dados de artigos devem ser passados para o construtor. Trata-se de uma precaução recomendável. Se essas duas informações fossem inseridas “fixamente” no código da classe, qualquer usuário da classe poderia facilmente se passar pelo administrador do banco de dados de artigos. De fato, uma classe PHP não está protegida. Da mesma forma, o atributo $bAdmin da classe, que indica se o usuário ($sUser, $sMdp) para o qual se está trabalhando é administrador ou não, poderia muito bem ser definido diretamente de fora, como no exemplo a seguir:
$oArticles=new articles($dDSN,$sUser,$sMdp)
// aqui, $sUser foi reconhecido como um usuário não administrador do banco de dados
$oArticle->bAdmin=TRUE;
// agora $sUser tornou-se administrador

PHP não é JAVA nem C# e uma classe PHP é apenas uma estrutura de dados um pouco mais avançada do que um dicionário, mas que não oferece a segurança de uma classe verdadeira, na qual o atributo bAdmin teria sido declarado privado ou protegido, tornando impossível sua modificação externamente. Como o usuário da classe precisa saber o login e a senha do administrador do banco de dados de artigos, somente este último pode utilizar a classe. A operação anterior, portanto, não apresenta mais nenhum interesse para ele. A classe existe apenas para facilitar o desenvolvimento. Uma consequência importante é que não há necessidade de tomar precauções de segurança. Mais uma vez, quem utiliza a classe articles é necessariamente o administrador do banco de dados de artigos.

  • A classe gerencia os erros de conexão com o banco de dados ou qualquer outro erro de maneira única, preenchendo o atributo $aErreurs com a(s) mensagem(ns) de erro. Após cada operação, o usuário da classe deve, portanto, verificar essa lista.
  • Os métodos addArticle, updateArticle, deleteArticle, selectArticles e execute decorrem diretamente do modelo da interface web apresentado anteriormente. Eles correspondem, de fato, às opções do menu proposto. Os métodos addArticle e modifyArticle se baseiam no método vérifierArticle para verificar se o artigo que será adicionado ou modificado possui dados corretos. Seguindo a mesma lógica, o método existeArticle permite verificar se não se está prestes a adicionar um artigo que já existe. Seria possível dispensar esse método caso se utilizasse uma tabela de artigos em que o código fosse a chave primária. Nesse caso, seria o próprio SGBD que sinalizaria a falha na adição devido à existência de um duplicado. Provavelmente, isso seria indicado por meio de uma mensagem de erro pouco legível e em inglês.
  • Um item a ser modificado ou excluído será identificado por seu código, que é único. O método getCodes permite obter todos esses códigos.
  • O método disconnect encerra a conexão com o banco de dados, conexão aberta durante a construção do objeto. Não se vê aqui a utilidade do método connect, que recriará uma conexão com o banco de dados. Isso permitirá abrir e fechar essa conexão à vontade com o mesmo objeto. A utilidade só se torna evidente em conjunto com a aplicação web. Esta criará um objeto “artigos” que será armazenado em uma sessão. Embora a sessão seja capaz de manter a maioria dos atributos do objeto ao longo das sucessivas trocas cliente-servidor, ela não consegue, no entanto, manter o atributo que representa a conexão aberta. Portanto, essa conexão deverá ser reaberta a cada nova troca cliente-servidor. Solicitaremos uma conexão persistente para que a conexão aberta seja armazenada em um pool de conexões e permaneça aberta permanentemente. Assim, quando o script solicitar uma nova conexão, ela será recuperada do pool de conexões. Chegamos, portanto, ao mesmo resultado que se a sessão pudesse armazenar a conexão aberta.
  • O método existeUser permite que o fabricante verifique se o usuário $sUser, identificado pela senha $sMdp, realmente existe. Se sim, o método permite verificar se ele é administrador ou não (conforme indicado na tabela USERS) e armazena essa informação no atributo $bAdmin. Caso não seja administrador, o método recuperará seus direitos da tabela DROITS e os colocará no atributo $dDroits, que é um dicionário com indexação dupla: $dDroits[$table][$droit] vale 'y' se o usuário $sUser tiver o direito $droit sobre a tabela $table e vale 'n' caso contrário.

Escreva a classe articles. Os acessos ao banco de dados serão feitos por meio da biblioteca </mark>[<u><span style="color: #0563c1">PEAR::DB,](https://pear.php.net/package/DB/) que permite independer do tipo exato do banco de dados.

7.5. A estrutura do aplicativo WEB

Agora que temos a classe “de negócio” para gerenciamento do banco de dados de artigos, podemos utilizá-la em diferentes ambientes. Propõe-se aqui utilizá-la em um aplicativo web. Vamos conhecê-lo por meio destas diferentes páginas:

7.5.1. A página padrão da aplicação

Voltemos à página inicial já apresentada:

1234

Image

Todas as páginas da aplicação terão a estrutura acima, a de uma tabela com duas linhas e três colunas, composta por quatro campos:

  • a área 1 forma a primeira linha da tabela. Ela é reservada ao título, eventualmente acompanhado de uma imagem. As três colunas da linha estão aqui unificadas.
  • a segunda linha possui três áreas, uma por coluna:
    • a área 2 contém as opções do menu. Por sua vez, ela contém uma tabela com uma coluna e várias linhas. As opções do menu são colocadas nas linhas da tabela.
    • a área 3 está vazia e serve apenas para separar as áreas 2 e 4. Teria sido possível proceder de outra forma para realizar essa separação.
    • A área 4 é aquela que contém a parte dinâmica da página. É essa parte que muda de uma ação para outra, enquanto as demais permanecem idênticas.

O script PHP que gera essa página-modelo se chamará main.php e poderia ser o seguinte:


<html>
  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>
  <body background="<?php echo $dConfig['urlBackGround'] ?>">
    <table>
      <tr height="60">
        <td colspan="3" align="left" valign="top" >
          <h1><?php echo $main["title"] ?></h1>
        </td>
      </tr>
      <tr>
        <td>
          <table>
            <tr>
              <td class="menutitle" >
                                    <a href="<?php echo  $main["liens"]["login"] ?>" ?>Authentification</a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>
            <tr>
              <td class="menutitle" >
                Utilisation
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo  $main["liens"]["addArticle"] ?>" ?>
                  Ajouter un article
                </a>
                 </td>
            </tr>
            <tr>
              <td class="menublock" >                    
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["updateArticle"] ?>">
                  Modifier un article
                </a>
                    </td>
            </tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["deleteArticle"] ?>">
                  Supprimer un article
                </a>
              </td>
            </tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["selectArticle"] ?>">
                  Lister des articles
                </a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>                
            <tr>
              <td class="menutitle" >
                Administration
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["sql"] ?>" >
                  Requête SQL
                </a>
              </td>
            </tr>
          </table>
        </td>
            <td>  
            <img alt="/" src="../images/pix.gif" width="10" height="1" />
            </td>
            <td>
            <fieldset>
              <legend><?php echo $main["légende"] ?></legend>
            <?php
                include $main["contenu"];
            ?>
          </fieldset>
        </td>
      </tr>
    </table>
  </body>
</html>

Os campos configurados da página foram destacados na lista acima. A página-modelo é configurada de várias maneiras:

  • por meio de um dicionário $main com as seguintes chaves:
    • title: título a ser inserido na área 1 da página
    • links: dicionários dos links a serem gerados na coluna do menu. Esses links estão associados às opções do menu da área 2
    • conteúdo: URL da página a ser exibida na área 4
  • por meio de um dicionário $dConfig que reúne informações extraídas de um arquivo de configuração do aplicativo chamado config.php
  • por meio de classes que fazem parte da folha de estilo utilizada pela página:
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />

A página utiliza aqui as seguintes classes de estilo:

  • menutitle: para uma opção principal do menu
  • menublock: para uma opção secundária do menu

Alterar um dos parâmetros muda a aparência da página. Assim, alterar $main['title'] mudará o título da área 1.

7.5.2. O processamento padrão de uma solicitação de um cliente

O cliente interage com o aplicativo por meio dos links da área 2 da página padrão. Esses links serão do seguinte tipo:

apparticles.php?action=xx&phase=y&PHPSESSID=zzzzzzzzzzzz
action
indica a ação em andamento entre as seguintes:
authentifier
autenticação do cliente
selectArticles
seleção de itens (consulta)
updateArticle
alteração de um artigo
deleteArticle
exclusão de um artigo
sql
Envio de qualquer solicitação SQL (administrador)
phase
uma ação pode ser realizada em várias etapas — indica a etapa atual
PHPSESSID
token de sessão no momento em que esta foi iniciada — permite que o servidor recupere informações armazenadas na sessão durante as trocas anteriores

Da mesma forma, o atributo “action” nos formulários terá o mesmo formato. Por exemplo, na página inicial há um formulário de login na área 4. A tag HTML desse formulário é definida da seguinte forma:

<form name="frmLogin" method="post" action="apparticles.php?action=authentifier&phase=1">

O processamento da solicitação do cliente é realizado pelo script principal do aplicativo, chamado apparticles.php. Sua função é construir a resposta para o cliente. Ele sempre procederá da mesma maneira:

  • com base no nome da ação e na fase em andamento, ele delegará a solicitação a uma função especializada. Essa função processará a solicitação e gerará a página de resposta adequada. Para cada solicitação do cliente, pode haver várias páginas de resposta possíveis: página1, página2, ..., página n. Essas páginas contêm informações que devem ser calculadas pela função. Trata-se, portanto, de páginas parametrizadas. Elas serão geradas pelos scripts page1.php, page2.php, ..., pagen.php.
  • Por uma questão de uniformidade, as partes variáveis das páginas a serem exibidas na área 4 da página-modelo também serão colocadas no dicionário $main.

Suponhamos que, em resposta a uma solicitação, o servidor deva enviar a página pagex.php ao cliente. Ele procederá da seguinte maneira:

  • colocará no dicionário $main os valores necessários para a página pagex.php
  • colocará em $main['contenu'], que designa o URL da página a ser exibida na zona 4 da página-modelo, o URL de pagex.php
  • ele solicitará a exibição da página modelo com a instrução
include "main.php";

A página modelo será então exibida com, na zona 4, o código do script pagex.php, que será avaliado para gerar o conteúdo da zona 4. Vale lembrar que esta é uma simples célula de uma tabela. Portanto, o código HTML gerado por pagex.php não deve começar com as tags <HTML>, <HEAD>, <BODY>, ... Essas já foram emitidas no início da página modelo. Veja, por exemplo, como poderia ser o script login.php que gera a área 4 da página inicial:


<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="submit" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>

Percebe-se que a página:

  • é reduzida a um formulário
  • é configurada tanto pelo dicionário $main quanto pela folha de estilo.

7.5.3. O arquivo de configuração

É sempre recomendável configurar ao máximo as aplicações para evitar ter que mexer no código simplesmente porque se decidiu, por exemplo, alterar o caminho de um script ou de uma imagem. A aplicação principal apparticles.php carregará, portanto, um arquivo de configuração config.php ao iniciar:

     // carregando o arquivo de configuração
  include "config.php";

Nesse arquivo, colocaremos diretivas de configuração destinadas ao PHP e inicializações de variáveis globais:

<?php

     // configuração do PHP
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  ini_set("expose_php","off");
    ini_set("session.use_cookies","0");    // sem cookies

     // configuração básica de artigos
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "admin"=>"admarticles",
        "mdpadmin"=>"mdparticles",
        "host"=>"localhost",
        "database"=>"dbarticles"
    );

   // URLs das páginas
    $dConfig['urlBackGround']="../images/standard.jpg";  
  $dConfig["urlPageStyle"]="mystyle.css";  
  $dConfig["urlAppArticles"]="apparticles.php";
  $dConfig["urlPageMain"]="main.php";
  $dConfig["urlPageLogin"]="login.php";
  $dConfig["urlPageErreurs"]="erreurs.php";
  $dConfig["urlPageInfos"]="infos.php";
  $dConfig["urlPageAddArticle"]="addarticle.php";
  $dConfig["urlPageUpdateArticle1"]="updatearticle1.php";
  $dConfig["urlPageUpdateArticle2"]="updatearticle2.php";
  $dConfig["urlPageDeleteArticle1"]="deletearticle1.php";
  $dConfig["urlPageDeleteArticle2"]="deletearticle2.php";
  $dConfig["urlPageSelectArticle1"]="selectarticle1.php";
  $dConfig["urlPageSelectArticle2"]="selectarticle2.php";
  $dConfig["urlPageSQL1"]="sql1.php";
  $dConfig["urlPageSQL2"]="sql2.php";
  $dConfig["urlPageSQL3"]="sql3.php";

   // links da página principal
  $main["liens"]["login"]="$sUrlAppArticles?action=authentifier&phase=0";  
  $main["liens"]["addArticle"]="$sUrlAppArticles?action=addArticle&phase=0";
  $main["liens"]["updateArticle"]="$sUrlAppArticles?action=updateArticle&phase=0";
  $main["liens"]["deleteArticle"]="$sUrlAppArticles?action=deleteArticle&phase=0";
  $main["liens"]["selectArticle"]="$sUrlAppArticles?action=selectArticle&phase=0";
  $main["liens"]["sql"]="$sUrlAppArticles?action=sql&phase=0";

   // armazenamos $main na configuração
  $dConfig["main"]=$main;    
?>

7.5.4. A folha de estilo associada à página modelo

Vimos que a resposta do servidor tinha um formato único: main.php. É possível notar que esse script gera uma página em bruto, sem efeitos de apresentação. Isso é positivo por várias razões:

  • o desenvolvedor não precisa se preocupar com a apresentação gráfica da página que está criando. Na verdade, ele nem sempre possui as competências necessárias para criar páginas graficamente atraentes. Assim, pode se concentrar inteiramente no código.
  • a manutenção dos scripts é facilitada. Se eles contivessem atributos de apresentação, nem a estrutura do código nem a da apresentação ficariam claras. O aspecto gráfico das páginas é frequentemente delegado a um designer gráfico. Este provavelmente não gostaria de ter que procurar, em um script que não compreende, onde estão os atributos de apresentação que precisa modificar.

No entanto, é preciso se preocupar com o aspecto gráfico das páginas. Afinal, é isso que atrai os internautas para um site. Aqui, a apresentação é delegada a uma folha de estilo. A página main.php indica em seu código a folha de estilo a ser usada para exibi-la:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

A folha de estilo utilizada neste documento é a seguinte:

BODY {
    background : url(../images/standard.jpg);
    border : 2px none #FFDAB9;
    font-family : Garamond;
    font-size : 16px;
    margin-left : 0px;
    padding-left : 20px;
}

INPUT {
    background : #EEE8AA;
    border : 1px solid #EE82EE;
    font-family : Garamond;
    font-size : 18px;
}

INPUT.submit{
    font-family : "Times New Roman";
    font-size : 16px;
    background : #FA8072;
    border : 2px double Green;
    font-weight : bold;
    text-align : center;
    vertical-align : middle;
    cursor : pointer;
}

TD.menutitle{
    background-image : url(../images/menugelgd.gif);
    height : 23px;
    text-align : center;
    vertical-align : middle;
    background : url(../images/menugelgd.gif) no-repeat center;
}

TD.menublock{
    background : url(../images/bandegrismenugd.gif) repeat-x;
    text-align : left;
    vertical-align : middle;
}

A {
    font-family : "Comic Sans MS";
    color : #FF7F50;
    font-size : 15px;
    text-decoration : none;
}

A:HOVER {
    background : #FFA07A;
    color : Red;
}

FIELDSET {
    border : 1px solid #A0522D;
    background : #FFE4C4;
    margin : 10px 10px 10px 10px;
    padding-left : 10px;
    padding-right : 10px;
    padding-bottom : 10px;
}

LEGEND{
    background : #FFA500;
}

TH {
    background : #228B22;
    text-align : center;
    vertical-align : middle;
}

TD.libellé{
    border : 1px solid #008B8B;
    color : #339966;
}

H1 {
    font : bold 20px/30px Garamond;
    color : #FF7F50;
    background : #D1E1F8;
    background-attachment : fixed;
    text-align : center;
    vertical-align : middle;
    font-family : Garamond;
}

SELECT.TEXT {
    background : #6495ED;
    text-align : center;
    color : Aqua;
}

Não entraremos em detalhes sobre essa folha de estilo. Vamos aceitá-la como está. Mais adiante, veremos como criá-la e modificá-la. Existem programas para isso. No entanto, vamos indicar a função dos atributos de apresentação utilizados na folha:

Atributo:
determina a apresentação da tag HTML:
BODY
<BODY>
H1
<H1> (Cabeçalho 1)
A
<A> (Âncora)
A:HOVER
define os atributos de apresentação da âncora quando o usuário passa o mouse sobre ela
FIELDSET
<FIELDSET> — essa tag não é reconhecida por todos os navegadores
LEGEND
<LEGEND> — essa tag não é reconhecida por todos os navegadores
INPUT
<INPUT>
INPUT.TEXT
<INPUT class="TEXT">
INPUT.SUBMIT
<INPUT class="SUBMIT">
TH
<TH> (Cabeçalho da tabela)
TD.menutitle
<TD class="menutitle"> (Dados da tabela)
TD.menublock
<TD class="menublock">
TD.libellé
<TD class="libellé">

Vamos ver, por meio de um exemplo, como essas regras de apresentação podem ser definidas. Neste exemplo, utilizaremos o software TopStyle Lite, disponível gratuitamente no site http://www.bradsoft.com. Depois que a folha de estilo for carregada, aparecerá uma janela com três áreas:

  1. uma área de edição de texto. Os atributos de apresentação podem ser definidos manualmente, desde que se conheçam as regras de escrita das folhas de estilo, que seguem uma norma chamada CSS (Cascading Style Sheets).
  2. a área 2 apresenta as propriedades editáveis do atributo que está sendo criado. Esse é o método mais simples. Ele evita a necessidade de saber o nome exato dos atributos de apresentação, que são muito numerosos
  3. A área 3 mostra a aparência visual do atributo que está sendo criado

Na área 1 acima, vamos copiar e colar o atributo INPUT.submit em um atributo INPUT.fantaisie. Esse atributo definirá a apresentação da tag HTML <INPUT class="fantaisie">

Vamos usar a área 2 para alterar algumas das propriedades do atributo INPUT.fantaisie:

A partir de agora, qualquer tag <INPUT ... class="fantaisie"> encontrada em uma página HTML associada à folha de estilo anterior será exibida conforme o exemplo da área 3 acima.

As folhas de estilo oferecem grandes vantagens. Seu uso permite alterar a aparência de um aplicativo web modificando-o em apenas um único ponto: sua folha de estilo. As folhas de estilo não são reconhecidas por navegadores mais antigos. A diretiva <link ..> abaixo será ignorada por alguns deles:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

Em nosso aplicativo, isso resultará na seguinte página inicial:

Image

Temos aqui uma página mínima, sem elementos gráficos. Poderia ser pior. Algumas versões de navegadores reconhecem as folhas de estilo, mas as interpretam incorretamente. Assim, podemos ter uma página desfigurada e inutilizável. Surge, portanto, a questão do tipo de navegador do cliente. Existem técnicas que ajudam a determinar o tipo de navegador do cliente. Elas não são totalmente confiáveis. É possível, então, escrever diferentes folhas de estilo para diferentes navegadores ou até mesmo criar uma versão sem folha de estilo para os navegadores que as ignoram. Isso, é claro, torna o trabalho de desenvolvimento mais trabalhoso. Esse problema importante foi ignorado aqui.

Com as folhas de estilo, podemos oferecer um ambiente personalizado aos usuários do nosso aplicativo. Poderíamos apresentar a eles uma página com várias opções de estilos de apresentação. Eles poderiam escolher aquele que melhor lhes convier. Essa escolha poderia ser registrada em um banco de dados. Quando o usuário fizer login novamente, poderíamos iniciar o aplicativo com a folha de estilo que ele preferiu.

7.5.5. O módulo de entrada do aplicativo

Os clientes conhecerão apenas o módulo de entrada do aplicativo: apparticles.php. As linhas gerais de seu funcionamento são as seguintes:

  • A solicitação do cliente é recebida e analisada. Ela pode ou não estar configurada. Quando está parametrizada, os parâmetros esperados são os seguintes: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
  • Se a solicitação não estiver configurada ou se os parâmetros recuperados não forem os esperados, o servidor envia como resposta a página de autenticação (login, senha). Assim que o usuário se identificar corretamente, uma sessão é criada. Ela servirá para armazenar informações ao longo das trocas entre cliente e servidor.
  • Se uma solicitação for reconhecida corretamente, ela é processada por um módulo que depende tanto da ação quanto da fase em andamento.
  • Todos os acessos ao banco de dados são feitos por meio da classe de negócios articles.php.
  • O processamento de uma solicitação sempre termina com o envio ao cliente da página main.php, na qual foi especificado, em $main['contenu'], oURL da página a ser inserida na área 4 da página modelo.

A estrutura do script apparticles.php poderia ser a seguinte:

<?php
     // gestão de uma tabela de artigos
  include "config.php";
  include "articles.php";  

  // ação a ser realizada
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // fase eventual
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

  // sessão
  session_start();
  $dSession=$_SESSION["session"];

     // há uma sessão em andamento?
  if(! isset($dSession)){
      // autenticação do usuário
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
     // solicitação incorreta
    authentifier_0($dConfig);        
  }//if — sem sessão

     // recuperando a sessão
  $dSession=unserialize($dSession);

     // processamento da solicitação
     // ----- autenticação
  if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
  if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
  if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);  
     // ----- adição de artigo
  if($sAction=="addarticle" && $sPhase=="0") addArticle_0($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="1") addArticle_1($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="2") addArticle_2($dConfig,$dSession);
     // ----- atualização de item
  if($sAction=="updatearticle" && $sPhase=="0") updateArticle_0($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="1") updateArticle_1($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="2") updateArticle_2($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="3") updateArticle_3($dConfig,$dSession);
     // ----- exclusão de artigo
  if($sAction=="deletearticle" && $sPhase=="0") deleteArticle_0($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="1") deleteArticle_1($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="2") deleteArticle_2($dConfig,$dSession);
    // ----- consulta de artigos
  if($sAction=="selectarticle" && $sPhase=="0") selectArticle_0($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="1") selectArticle_1($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="2") selectArticle_2($dConfig,$dSession);
     // ----- envio de uma solicitação SQL
  if($sAction=="sql" && $sPhase=="0") sql_0($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="1") sql_1($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="2") sql_2($dConfig,$dSession);


    // ação incorreta — é exibida a página de autenticação
  session_destroy();
  authentifier_0($dConfig,"0");
...
?>

Observe os seguintes pontos:

  • as funções que tratam de uma solicitação específica do cliente terminam com a geração da página de resposta e com uma instrução exit que encerra a execução do script apparticles.php. Em outras palavras, não há “retorno” dessas funções.
  • as funções aceitam um ou dois parâmetros:
    • $dConfig é um dicionário que contém informações provenientes do arquivo de configuração config.php. Todas as funções o utilizam.
    • $dSession é um dicionário que contém informações da sessão. Ele só existe quando a sessão foi criada, ou seja, após a autenticação bem-sucedida do usuário. É por isso que as funções de autenticação não possuem esse parâmetro.

7.5.6. A página de erros

Todo aplicativo de software deve saber lidar corretamente com os erros que possam ocorrer. Um aplicativo web não escapa a essa regra. Aqui, em caso de erro, colocaremos a seguinte página erreurs.php na área 4 da página padrão:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($main["erreurs"]);$i++){
            echo "<li>".$main["erreurs"][$i]."</li>\n";
        }//para
    ?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>

Ela apresenta a lista de erros definida em $main['erreurs']. Além disso, pode oferecer um link de retorno, geralmente para a página que precedeu a página de erros. Esse link será definido por um texto $main['lien'] e um URL $main['href']. Para que esse link não apareça, basta inserir uma string vazia em $main['lien']. Aqui está um exemplo de página de erros no caso de o usuário se autenticar incorretamente:

Image

7.5.7. A página de informações

Às vezes, desejamos fornecer ao usuário uma informação simples, por exemplo, que seu login foi bem-sucedido. Para isso, utilizaremos a seguinte página: infos.php:

<?php echo $main["infos"] ?>

Para exibir uma informação em resposta a uma solicitação de um cliente,

  • colocaremos a informação em $main['infos']
  • colocaremos o URL de infos.php em $main['contenu']

Aqui está, por exemplo, a informação retornada quando o usuário se identificou corretamente:

Image

7.6. O funcionamento do aplicativo

Agora temos uma boa noção da estrutura geral do aplicativo a ser desenvolvido. Resta apresentar os percursos do usuário no aplicativo, as ações que ele pode realizar e as respostas que recebe do servidor. Feito isso, poderemos escrever as funções que processam as diferentes solicitações de um cliente. A seguir, apresentaremos o funcionamento do aplicativo por meio das páginas exibidas ao usuário em resposta a algumas dessas ações. Especificaremos, a cada vez, os seguintes pontos:

action utilisateur
ação inicial do usuário que levou à resposta exibida
paramètres envoyés
os parâmetros enviados pelo navegador do cliente ao servidor em resposta à ação manual do usuário
page réponse
o script que gera a área 4 da página modelo

7.6.1. A autenticação

Antes de poder utilizar o aplicativo, o usuário deverá se autenticar por meio da seguinte página:

Image

action utilisateur
1 - solicitação inicial do URL apparticles.php
2 - uso da opção “Autenticação” do menu
3 - solicitação direta do URL articles.php com parâmetros incorretos
paramètres envoyés
1 - sem parâmetros
2 - action=autenticar?fase=0
3 - uma lista de parâmetros incorretos
page réponse
login.php

Na página inicial, o link [Ajouter un article] tem o seguinte formato: action=addarticle?phase=0. Os demais links têm o mesmo formato, com action=(autenticar, atualizarartigo, excluirartigo, selecionarartigo, sql). O usuário preenche o formulário e clica no botão [Connexion]:

Image

A resposta é a seguinte:

Image

action utilisateur
bouton [Connexion]
paramètres envoyés
action=autenticar?fase=1
page réponse
infos.php

O título da página foi alterado para indicar o login do usuário e seus direitos de administrador/usuário. Além disso, todos os links da área 2 foram alterados para refletir o fato de que uma sessão foi iniciada. O parâmetro PHPSESSID=[PHPSESSID] foi adicionado a eles.

Se o servidor não conseguir identificar o cliente, este receberá uma resposta diferente:

Image

action utilisateur
bouton [Connexion]
paramètres envoyés
action=autenticar?fase=1
page réponse
erreurs.php

O link [Retour à la page de login] é um link para o URL apparticles.php?action=authentifier&phase=2&txtLogin=x. Esse link redireciona o cliente para a página de login, onde o campo de login é preenchido com o valor do parâmetro txtLogin:

Image

action utilisateur
lien [Retour à la page de login]
paramètres envoyés
action=autenticar?phase=2&txtLogin=x
page réponse
login.php

7.6.2. Adicionar um artigo

O link do menu [Ajouter un article] leva à seguinte página na área 4 da página modelo:

Image

action utilisateur
lien [Ajouter un article]
paramètres envoyés
action=addArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
addarticle.php

O usuário preenche os campos e envia tudo para o servidor clicando no botão [Ajouter], que é do tipo submit. Nenhuma verificação é feita no lado do cliente. É o servidor que realiza essas verificações. Ele pode enviar, em resposta, uma página de erros, como no exemplo abaixo:

Solicitação
Resposta
action utilisateur
bouton [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

O link [Retour à la page d'ajout d'article] permite voltar à página de preenchimento:

Solicitação
Resposta
action utilisateur
lien [Retour à la page d'ajout d'article]
paramètres envoyés
action=addArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
article.php

Se a adição for realizada sem erros, o usuário receberá uma mensagem de confirmação:

Solicitação
Resposta
action utilisateur
bouton [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.3. Visualização de artigos

O link do menu [Lister des articles] leva à seguinte página na área 4 da página padrão:

Image

action utilisateur
link do menu [Lister des articles]
paramètres envoyés
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
select1.php

Uma consulta SELECT [colonnes] FROM articles WHERE [where] ORDER BY [orderby] será emitida na tabela de artigos, onde [colonnes], [where] e [orderby] são os valores dos campos acima. Por exemplo:

Solicitação
Resposta
action utilisateur
bouton [Afficher]
paramètres envoyés
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
select2.php

A solicitação pode estar incorreta; nesse caso, o cliente recebe uma página de erros:

Solicitação
Resposta

Em ambos os casos (com ou sem erros), o link [Retour à la page de sélection d'articles] permite retornar à página select1.php:

Solicitação
Resposta
action utilisateur
lien [Retour à la page de sélection d'articles]
paramètres envoyés
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
select1.php

7.6.4. Alteração de itens

O link do menu [Modifier un article] exibe a seguinte página na área 4 da página-modelo:

Image

action utilisateur
link de menu [Modifier un article]
paramètres envoyés
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
updatearticle1.php

Selecione o código do artigo a ser alterado na lista suspensa e execute [OK] para alterar o artigo com esse código:

Solicitação
Resposta
action utilisateur
bouton [OK]
paramètres envoyés
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

Depois de acessar a ficha do artigo a ser alterado, o usuário pode fazer suas alterações:

Solicitação
Resposta
action utilisateur
bouton [Modifier]
paramètres envoyés
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

O usuário pode cometer erros durante a edição:

Solicitação
Resposta

O link [Retour à la page de modification d'article] permite voltar à página de preenchimento:

Image

action utilisateur
lien [Retour à la page de modification d'article]
paramètres envoyés
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

7.6.5. Exclusão de um artigo

O link do menu [Supprimer un article] leva à seguinte página na área 4 da página modelo:

Image

action utilisateur
link do menu [Supprimer un article]
paramètres envoyés
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
deletearticle1.php

O usuário seleciona o código do artigo a ser excluído em uma lista suspensa:

Solicitação
Resposta
action utilisateur
bouton [OK]
paramètres envoyés
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
deletearticle2.php

O usuário confirma a exclusão do artigo com o botão [Supprimer]:

Solicitação
Resposta
action utilisateur
bouton [Supprimer]
paramètres envoyés
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.6. Envio de solicitações de administrador

O link do menu [Requête SQL] leva à seguinte página na área 4 da página padrão:

Image

action utilisateur
link do menu [Requête SQL]
paramètres envoyés
action=sql?phase=0&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

Digite o texto da consulta SQL no campo de entrada e use o botão [Exécuter] para executá-la. Somente um administrador pode emitir essas consultas, conforme mostra o exemplo a seguir:

Solicitação
Resposta
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

O link [Retour à la page d'émission de requêtes SQL] permite voltar à página de preenchimento:

Image

action utilisateur
lien [Retour à la page d'émission de requêtes SQL]
paramètres envoyés
action=sql?phase=2&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

Se você for administrador e a consulta estiver sintaticamente correta:

Solicitação

obtém-se o resultado da consulta:

Resposta
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
sql2.php

É possível enviar consultas para atualizar as tabelas:

Solicitação
Resposta
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.7. Tarefas a realizar

Escrever os scripts e funções necessários para a aplicação:

identificador
tipo
função
apparticles.php
script
o ponto de entrada para o processamento das solicitações dos clientes
authentifier_0
função
processa a solicitação configurada com action=autenticar&fase=0
authentifier_1
função
processa a solicitação com os parâmetros action=autenticar&fase=1
authentifier_2
função
processa a solicitação com os parâmetros action=autenticar&fase=2
addarticle_0
função
processa a solicitação com os parâmetros action=addArticle&phase=0
addarticle_1
função
processa a solicitação configurada com action=addArticle&phase=1
addarticle_2
função
processa a solicitação configurada com action=addArticle&phase=2
updatearticle_0
função
processa a solicitação configurada com action=updatearticle&phase=0
updatearticle_1
função
processa a solicitação com os parâmetros action=updatearticle&phase=1
updatearticle_2
função
processa a solicitação com os parâmetros action=updatearticle&phase=2
updatearticle_3
função
processa a solicitação com os parâmetros action=updatearticle&phase=3
deletearticle_0
função
processa a solicitação com os parâmetros action=deletearticle&phase=0
deletearticle_1
função
processa a solicitação com os parâmetros action=deletearticle&phase=1
deletearticle_2
função
processa a solicitação com os parâmetros action=deletearticle&phase=2
selectarticle_0
função
processa a solicitação com os parâmetros action=selectarticle&phase=0
selectarticle_1
função
processa a solicitação configurada com os parâmetros action=selectarticle&phase=1
selectarticle_2
função
processa a solicitação configurada com os parâmetros action=selectarticle&phase=2
sql_0
função
processa a solicitação configurada com os parâmetros action=sql&phase=0
sql_1
função
processa a solicitação configurada com os parâmetros action=sql&phase=1
sql_2
função
processa a solicitação configurada com action=sql&phase=2
main.php
script
gera a página padrão
login.php
script
gera a página de login
erreurs.php
script
gera a página de erros
infos.php
script
gera a página de informações
addarticle.php
script
gera a página de adição de um artigo
updatearticle1.php
script
gera a página 1 da edição de um artigo
updatearticle2.php
script
gera a página 2 da edição de um artigo
deletearticle1.php
script
gera a página 1 da exclusão de um artigo
deletearticle2.php
script
gera a página 2 da exclusão de um artigo
select1.php
script
gera a página 1 da seleção de artigos
select2.php
script
gera a página 2 da seleção de artigos
sql1.php
script
gera a página 1 do envio de solicitações
sql2.php
script
gera a página 2 da emissão de solicitações

7.7. Aprimorar o aplicativo

Neste momento, temos um aplicativo que cumpre sua função com uma usabilidade aceitável. Vamos aprimorá-lo em diferentes aspectos:

  • o SGBD
  • sua segurança
  • seu visual
  • seu desempenho

7.7.1. Alterar o tipo do banco de dados

Nosso estudo partiu do pressuposto de que o SGBD utilizado era o MySQL. Altere para SGBD e demonstre que a única modificação a ser feita está na definição da variável $dDSN no arquivo de configuração config.php.

7.7.2. Melhorar a segurança

Ao desenvolver uma aplicação web, nunca se deve partir do pressuposto de que o cliente é um navegador e que a solicitação que ele nos envia é controlada pelo formulário que lhe foi enviado antes dessa solicitação. Qualquer programa pode ser cliente de uma aplicação web e, portanto, enviar qualquer solicitação, com ou sem parâmetros, para a aplicação. Por isso, a aplicação deve verificar tudo.

Se analisarmos o código do script apparticles.php, percebemos

  • que nenhuma ação além da autenticação pode ocorrer sem uma sessão. Essa sessão só existe se o usuário tiver conseguido se autenticar. Vale lembrar que uma sessão é identificada por uma sequência de caracteres bastante longa, chamada de token de sessão, que tem o seguinte formato: 176a43609572907333118333edf6d1fb. Esse token pode ser enviado ao aplicativo de diversas maneiras, por exemplo, utilizando um URL configurado:

apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb. 

Um programa que solicitasse repetidamente o URL anterior, variando o token aleatoriamente na esperança de encontrar o token correto, provavelmente levaria muitos dias para gerar a combinação correta, tamanha a quantidade de combinações possíveis. Até lá, como a sessão tem duração limitada, ela provavelmente já terá sido encerrada. Outro risco seria que o token, ao ser transmitido em texto simples pela rede, fosse interceptado. O risco é real. Nesse caso, pode-se utilizar uma conexão criptografada entre o servidor e seu cliente.

  • de modo que, uma vez iniciada a sessão, apenas determinadas ações sejam permitidas. Um token URL configurado com action=tricher&phase=0&PHPSESSID=[PHPSESSID] seria rejeitado, pois a ação 'tricher' não é uma ação permitida. Quando os parâmetros (ação, fase) não são reconhecidos, nosso aplicativo responde com a página de autenticação.

No entanto, o aplicativo não verifica se as ações autorizadas se sucedem corretamente. Por exemplo, as duas ações a seguir:

  1. action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
  2. action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]

são duas ações autorizadas. No entanto, a ação 2 não está autorizada a seguir a ação 1.

Como acompanhar a sequência das solicitações URL feitas pelo navegador do cliente?

É possível utilizar duas variáveis PHP: $_SERVER['REQUEST_URI] e $_SERVER['HTTP_REFERER], que são duas informações enviadas pelos navegadores dos clientes em seus cabeçalhos HTTP.

$_SERVER['REQUEST_URI]: É o URI solicitado pelo cliente. Por exemplo

/apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

$_SERVER['HTTP_REFERER]: Este é o URL que estava sendo exibido no navegador antes do novo URL que o navegador está solicitando (o URI anterior). Por exemplo, se o navegador que visualizou o URI mencionado anteriormente fizer uma nova solicitação a um servidor, a variável $_SERVER['HTTP_REFERER'] desse servidor terá o valor

http://máquina:porta//apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

Para verificar se duas ações do nosso aplicativo ocorrem em sequência, pode-se proceder da seguinte forma:

Na ação 1:

  • anotamos o URI solicitado (URI1) e o registramos na sessão

Na ação 2:

  • recupera-se o HTTP-REFERER da ação 2. A partir disso, deduz-se o URI (URI2) a partir do URL que estava visualizado anteriormente no navegador que fez a solicitação.
  • Recupera-se o URI (URI1), que estava armazenado na sessão e que corresponde ao URI da ação solicitada anteriormente ao servidor
  • Se a ação 2 seguir a ação 1, então deve-se ter URI2 = URI1. Caso contrário, a ação solicitada seria recusada e a página de autenticação seria exibida.
  • Registra-se na sessão o URI e o URI2 da ação em andamento para verificação da ação seguinte. E assim por diante.

Veja um exemplo. Após a autenticação, seleciona-se o link [Ajouter un article]:

Image

O URL desta página é:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Diretamente no campo [Adresse] do navegador, alteramos o URL da seguinte maneira:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Assim, obtemos a página de autenticação:

Image

Isso merece uma explicação. Quando solicitamos um URL digitando diretamente sua identidade no campo de endereço do navegador, este não envia o cabeçalho HTTP_REFERER. Nosso aplicativo, portanto, não encontra o URI da ação anterior, URI, que havia sido armazenado na sessão. Ele então retorna a página de autenticação como resposta.

Esse mecanismo é eficaz para navegadores, mas não para um cliente programado. Este pode enviar o cabeçalho HTTP_REFERER que quiser. Assim, ele pode “enganar”, alegando que passou por determinada etapa, quando na verdade não o fez. É preciso, portanto, garantir que a sequência das etapas seja respeitada. Assim, se a ação solicitada for action=addArticle&phase=1 (preenchimento), a ação anterior deve ser necessariamente action=deleteArticle&phase=0 (solicitação inicial da página de preenchimento) ou action=addArticle&phase=2 (retorno ao preenchimento após adição incorreta). Da mesma forma, se a ação solicitada for action=addArticle&phase=2 (adição), então a ação anterior deve ser action=addArticle&phase=1 (preenchimento). É possível obrigar o usuário a respeitar essas sequências.

Enquanto o primeiro mecanismo é geral e pode ser aplicado a qualquer aplicativo, o segundo requer uma codificação específica para cada aplicativo e é mais complexo: é preciso analisar todas as ações possíveis do usuário e suas sequências. É possível armazenar essas sequências em um dicionário, conforme mostra o código a seguir:

  // autenticação
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
         array('action'=>'authentifier','phase'=>'0'),
    array('action'=>'authentifier','phase'=>'2')
  );
  $dPrec['authentifier']['2']=array(
         array('action'=>'authentifier','phase'=>'1'),
  );

   // adição de artigo
  $dPrec['addarticle']['0']=array();  
  $dPrec['addarticle']['1']=array(
         array('action'=>'addarticle','phase'=>'0'),
    array('action'=>'addarticle','phase'=>'2')
  );
  $dPrec['addarticle']['2']=array(
         array('action'=>'addarticle','phase'=>'1'),
  );

   // alteração de artigo
  $dPrec['updatearticle']['0']=array();  
  $dPrec['updatearticle']['1']=array(
         array('action'=>'updatearticle','phase'=>'0'),
  );
  $dPrec['updatearticle']['2']=array(
         array('action'=>'updatearticle','phase'=>'1'),
    array('action'=>'updatearticle','phase'=>'3')
  );
  $dPrec['updatearticle']['3']=array(
         array('action'=>'updatearticle','phase'=>'2'),
  );

   // exclusão de artigo
  $dPrec['deletearticle']['0']=array();  
  $dPrec['deletearticle']['1']=array(
         array('action'=>'deletearticle','phase'=>'0'),
  );
  $dPrec['deletearticle']['2']=array(
         array('action'=>'deletearticle','phase'=>'1'),
  );

      // seleção de itens
  $dPrec['selectarticle']['0']=array();  
  $dPrec['selectarticle']['1']=array(
         array('action'=>'selectarticle','phase'=>'0'),
    array('action'=>'selectarticle','phase'=>'2')
  );
  $dPrec['selectarticle']['2']=array(
         array('action'=>'selectarticle','phase'=>'1'),
  );

      // solicitação de administrador
  $dPrec['sql']['0']=array();  
  $dPrec['sql']['1']=array(
         array('action'=>'sql','phase'=>'0'),
    array('action'=>'sql','phase'=>'2')
  );
  $dPrec['sql']['2']=array(
         array('action'=>'sql','phase'=>'1'),
  );

$dPrec['action']['phase'] é uma tabela que contém as ações que podem preceder a ação e a fase que servem de índice para o dicionário. Essas ações precedentes também são representadas por um dicionário com duas chaves: ‘ação’ e ‘fase’. Se uma ação puder ser precedida por qualquer ação, então $dPrec['action']['phase'] será uma tabela vazia. A ausência de uma ação no dicionário significa que ela não é permitida. Consideremos a ação “autenticar” acima:

  // autenticação
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
         array('action'=>'authentifier','phase'=>'0'),
    array('action'=>'authentifier','phase'=>'2')
  );
  $dPrec['authentifier']['2']=array(
         array('action'=>'authentifier','phase'=>'1'),
  );

O código acima significa que a ação action=authentifier&phase=0 pode ser precedida por qualquer ação, que a ação action=authentifier&phase=1 pode ser precedida pela ação action=authentifier&phase=0 ou pela ação action=authentifier&phase=2 e que a ação action=authentifier&phase=2 pode ser precedida pela ação action=authentifier&phase=1.

Escreva a seguinte função:

  // ---------------------------------------------------------------
  function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
       // verifica se a ação em andamento ($sAction, $sPhase) pode ser realizada após a ação anterior
         // armazenada em $dSession['précédent']
         // o dicionário de sequências permitidas está em $dConfig['précédents']
         // retorna TRUE se a sequência for possível, FALSE caso contrário
....

Esta função permite que o aplicativo principal verifique se a sequência de ações está correta:

<?php
     // gerenciamento de uma tabela de itens
  include "config.php";
  include "articles.php";  

  // sessão
  session_start();
  $dSession=$_SESSION["session"];

   // ação a ser realizada
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // fase eventual da ação
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

     // há uma sessão em andamento?  
  if(! isset($dSession)){
      // autenticação do usuário
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);   
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);    
     // ação anômala
    authentifier_0($dConfig);        
  }//if – sem sessão

     // recuperando a sessão
  $dSession=unserialize($dSession);

     // a sequência de ações está normal?
  if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
     // sequência anormal
    authentifier_0($dConfig);        
  }//if

     // processamento das ações
  if($sAction=="authentifier"){
   if($sPhase=="0") authentifier_0($dConfig);  
   if($sPhase=="1") authentifier_1($dConfig);  
   if($sPhase=="2") authentifier_2($dConfig);  
  }//if     
  if($sAction=="addarticle"){
...

7.7.3. Atualizar o “visual”

Lembremo-nos de que uma das condições estabelecidas durante o estudo desta aplicação era que ela deveria ser evolutiva. Suponhamos que, após algumas semanas, percebamos que a ergonomia da aplicação precisa ser aprimorada. Modifique a aplicação de forma que a estrutura e a apresentação da página padrão sejam alteradas. As modificações ocorrerão em dois locais:

  • no script main.php, que define a estrutura da página padrão. Atualize-a.
  • na folha de estilo que define a aparência do aplicativo. Altere-a.

7.7.4. Melhorar o desempenho

Por enquanto, optamos por um navegador cliente leve: ele se limita apenas à apresentação. É possível fazer com que ele execute processamentos incluindo scripts nas páginas da Web que lhe são enviadas. Esses scripts podem ser em diferentes linguagens, notadamente VBScript e JavaScript. O Internet Explorer e o Netscape dominam o mercado de navegadores em uma proporção próxima a 60/40. Além disso, o IE existe apenas no ambiente Windows e não no Unix, por exemplo, onde o Netscape é predominante. O Netscape não executa scripts VBScript nativamente, enquanto os dois navegadores executam scripts JavaScript. Como o Netscape ainda ocupa uma parcela significativa do mercado de navegadores, os scripts VBScript devem ser evitados. Portanto, o JavaScript é geralmente utilizado nos scripts do lado do cliente.

São delegadas aos scripts do lado do cliente as operações nas quais o servidor não precisa intervir. Em nosso aplicativo, seria interessante que o navegador do cliente só enviasse uma solicitação ao servidor após verificá-la. Assim, é desnecessário enviar ao servidor uma solicitação de autenticação quando o usuário deixou o campo [login] em branco no formulário de autenticação. É preferível avisar ao usuário que sua solicitação está incorreta:

Image

Observe-se que isso não impedirá que o servidor verifique se o campo de login está preenchido, pois seu cliente não é necessariamente um navegador e, portanto, a verificação anterior pode não ter sido realizada. Partir do pressuposto de que o cliente é um navegador representa um grande risco para a segurança do aplicativo.

Analise os diferentes momentos em que o navegador envia informações ao servidor e, quando essas informações puderem ser verificadas, escreva uma ou mais funções em JavaScript que permitam ao navegador verificar a validade das informações antes de enviá-las ao servidor.

Retomando o exemplo anterior, o script login.php, que gera a página de autenticação, passa a ser o seguinte:


<script language="javascript">
    function check(){
       // verifica-se se há realmente um login
    with(document.frmLogin){
        champs=/^\s*$/.exec(txtLogin.value);
      if(champs!=null){
          // sem login
        alert("Vous n'avez pas indiqué de login");
        txtLogin.focus();
        return;
      }//if
       // os dados estão presentes — eles são enviados ao servidor
      submit();
    }//com
  }//verificação
</script>   
    
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="button" onclick="check()" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>    

7.8. Para aprofundar o assunto

Para concluir, apresentamos algumas sugestões para aprofundar este estudo de caso:

  • seria interessante verificar se a página padrão dessa aplicação poderia ser transformada em uma classe. Essa classe poderia, então, ser utilizada em outras aplicações.
  • Nosso aplicativo é bem adequado para clientes do tipo navegador, mas menos para clientes do tipo “aplicativo autônomo”. Estes devem:
    • criar uma conexão TCP com o servidor
    • “comunicar-se” com ele usando HTTP
    • analisar suas respostas HTML para encontrar as informações desejadas, já que o cliente autônomo provavelmente não estará interessado no código de apresentação HTML destinado aos navegadores.

Seria interessante que nosso aplicativo gerasse XML em vez de HTML. Seus clientes poderiam, então, ser tanto navegadores (bastante recentes, é claro) quanto aplicativos autônomos. Estes últimos não teriam nenhuma dificuldade em encontrar as informações que procuram, já que a resposta XML do servidor não conteria nenhuma informação de apresentação, apenas conteúdo.

  • Certamente seria necessário considerar os acessos simultâneos à base de artigos. Há pelo menos dois pontos a serem esclarecidos:
  1. o SGBD utilizado pelo aplicativo gerencia corretamente o acesso simultâneo a um mesmo artigo? Por exemplo, o que acontece se dois usuários editarem o mesmo artigo ao mesmo tempo (eles clicam no botão [Modifier] simultaneamente)? Isso provavelmente depende do SGBD subjacente.
  2. Atualmente, nosso aplicativo não lida com acessos simultâneos. No entanto, o banco de dados deve permanecer em um estado coerente, mesmo que possam ocorrer imprevistos. Consideremos a seguinte sequência de eventos:
      • o usuário U1 inicia a edição de um artigo
      • o usuário U2 inicia a exclusão do mesmo artigo pouco depois
      • cada uma das duas ações requer trocas entre cliente e servidor. Dependendo da forma de trabalho de cada um, o usuário U2 pode concluir seu trabalho antes de U1. Quando este último for concluir suas alterações e validá-las por meio de [Modifier], receberá a página de informações como resposta, com o código SGBD indicando que [0 ligne(s) ont été modifiées], pois a página que ele pretendia alterar foi excluída nesse intervalo. O usuário provavelmente ficará surpreso. Do ponto de vista da usabilidade, seria sem dúvida preferível exibir uma página que sinalizasse melhor o erro. Além disso, poderia-se considerar oferecer ao usuário acesso exclusivo a um artigo assim que ele iniciasse sua atualização. Outro usuário que quisesse atualizar o mesmo artigo receberia a resposta de que outra atualização já está em andamento. Isso representará um problema se o primeiro usuário demorar para confirmar sua atualização: os demais ficarão bloqueados. Há soluções a serem encontradas que dependerão em grande parte das capacidades do SGBD utilizado. A Oracle, por exemplo, possui mais recursos nessa área do que o MySQL.