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:

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:
![]() |
|
O conteúdo da tabela poderia ser o seguinte:

A tabela DROITS especifica os direitos dos usuários que não são administradores presentes na tabela USERS. Sua estrutura é a seguinte:
![]() |
|
O conteúdo da tabela poderia ser o seguinte:

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:
![]() |
|
Seu conteúdo, utilizado inicialmente como teste, poderia ser o seguinte:

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

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:
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:
indica a ação em andamento entre as seguintes:
| |||||||||||
uma ação pode ser realizada em várias etapas — indica a etapa atual | |||||||||||
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:
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
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:
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> | |
<H1> (Cabeçalho 1) | |
<A> (Âncora) | |
define os atributos de apresentação da âncora quando o usuário passa o mouse sobre ela | |
<FIELDSET> — essa tag não é reconhecida por todos os navegadores | |
<LEGEND> — essa tag não é reconhecida por todos os navegadores | |
<INPUT> | |
<INPUT class="TEXT"> | |
<INPUT class="SUBMIT"> | |
<TH> (Cabeçalho da tabela) | |
<TD class="menutitle"> (Dados da tabela) | |
<TD class="menublock"> | |
<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:
- 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).
- 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
- 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:

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:

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

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:
ação inicial do usuário que levou à resposta exibida | |
os parâmetros enviados pelo navegador do cliente ao servidor em resposta à ação manual do usuário | |
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:

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 | |
1 - sem parâmetros 2 - action=autenticar?fase=0 3 - uma lista de parâmetros incorretos | |
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]:

A resposta é a seguinte:

bouton [Connexion] | |
action=autenticar?fase=1 | |
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:

bouton [Connexion] | |
action=autenticar?fase=1 | |
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:

lien [Retour à la page de login] | |
action=autenticar?phase=2&txtLogin=x | |
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:

lien [Ajouter un article] | |
action=addArticle?phase=0&PHPSESSID=[PHPSESSID] | |
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 |
![]() | ![]() |
bouton [Ajouter] | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
erreurs.php |
O link [Retour à la page d'ajout d'article] permite voltar à página de preenchimento:
Solicitação | Resposta |
![]() | ![]() |
lien [Retour à la page d'ajout d'article] | |
action=addArticle?phase=2&PHPSESSID=[PHPSESSID] | |
article.php |
Se a adição for realizada sem erros, o usuário receberá uma mensagem de confirmação:
Solicitação | Resposta |
![]() | ![]() |
bouton [Ajouter] | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
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:

link do menu [Lister des articles] | |
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID] | |
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 |
![]() |
bouton [Afficher] | |
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID] | |
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 |
![]() |
lien [Retour à la page de sélection d'articles] | |
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID] | |
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:

link de menu [Modifier un article] | |
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID] | |
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 |
![]() | ![]() |
bouton [OK] | |
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
Depois de acessar a ficha do artigo a ser alterado, o usuário pode fazer suas alterações:
Solicitação | Resposta |
![]() | ![]() |
bouton [Modifier] | |
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID] | |
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:

lien [Retour à la page de modification d'article] | |
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID] | |
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:

link do menu [Supprimer un article] | |
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID] | |
deletearticle1.php |
O usuário seleciona o código do artigo a ser excluído em uma lista suspensa:
Solicitação | Resposta |
![]() | ![]() |
bouton [OK] | |
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID] | |
deletearticle2.php |
O usuário confirma a exclusão do artigo com o botão [Supprimer]:
Solicitação | Resposta |
![]() | ![]() |
bouton [Supprimer] | |
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID] | |
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:

link do menu [Requête SQL] | |
action=sql?phase=0&PHPSESSID=[PHPSESSID] | |
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 |
![]() | ![]() |
bouton [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
erreurs.php |
O link [Retour à la page d'émission de requêtes SQL] permite voltar à página de preenchimento:

lien [Retour à la page d'émission de requêtes SQL] | |
action=sql?phase=2&PHPSESSID=[PHPSESSID] | |
sql1.php |
Se você for administrador e a consulta estiver sintaticamente correta:
Solicitação |
![]() |
obtém-se o resultado da consulta:
Resposta |
![]() |
bouton [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
sql2.php |
É possível enviar consultas para atualizar as tabelas:
Solicitação |
![]() |
Resposta |
![]() |
bouton [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.7. Tarefas a realizar
Escrever os scripts e funções necessários para a aplicação:
identificador | tipo | função |
script | o ponto de entrada para o processamento das solicitações dos clientes | |
função | processa a solicitação configurada com action=autenticar&fase=0 | |
função | processa a solicitação com os parâmetros action=autenticar&fase=1 | |
função | processa a solicitação com os parâmetros action=autenticar&fase=2 | |
função | processa a solicitação com os parâmetros action=addArticle&phase=0 | |
função | processa a solicitação configurada com action=addArticle&phase=1 | |
função | processa a solicitação configurada com action=addArticle&phase=2 | |
função | processa a solicitação configurada com action=updatearticle&phase=0 | |
função | processa a solicitação com os parâmetros action=updatearticle&phase=1 | |
função | processa a solicitação com os parâmetros action=updatearticle&phase=2 | |
função | processa a solicitação com os parâmetros action=updatearticle&phase=3 | |
função | processa a solicitação com os parâmetros action=deletearticle&phase=0 | |
função | processa a solicitação com os parâmetros action=deletearticle&phase=1 | |
função | processa a solicitação com os parâmetros action=deletearticle&phase=2 | |
função | processa a solicitação com os parâmetros action=selectarticle&phase=0 | |
função | processa a solicitação configurada com os parâmetros action=selectarticle&phase=1 | |
função | processa a solicitação configurada com os parâmetros action=selectarticle&phase=2 | |
função | processa a solicitação configurada com os parâmetros action=sql&phase=0 | |
função | processa a solicitação configurada com os parâmetros action=sql&phase=1 | |
função | processa a solicitação configurada com action=sql&phase=2 | |
script | gera a página padrão | |
script | gera a página de login | |
script | gera a página de erros | |
script | gera a página de informações | |
script | gera a página de adição de um artigo | |
script | gera a página 1 da edição de um artigo | |
script | gera a página 2 da edição de um artigo | |
script | gera a página 1 da exclusão de um artigo | |
script | gera a página 2 da exclusão de um artigo | |
script | gera a página 1 da seleção de artigos | |
script | gera a página 2 da seleção de artigos | |
script | gera a página 1 do envio de solicitações | |
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:
- action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
- 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
$_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
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]:

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:

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:

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































