7. Caso di studio: gestione di un database di articoli sul web
I codici di questo caso di studio sono disponibili |ICI|.
Obiettivi:
- scrivere una classe per la gestione di un database di articoli
- scrivere un'applicazione web basata su questa classe
- introdurre i fogli di stile
- proporre un approccio metodologico di base per lo sviluppo di semplici applicazioni web
- introdurre JavaScript nel browser client
Crediti: L'essenza di questo caso di studio è stata tratta dal libro "Les cahiers du programmeur - PHP/MySQL" di Jean-Philippe Leboeuf, edito da Eyrolles.
7.1. Introduzione
Un commerciante desidera gestire gli articoli che vende nel proprio negozio. Dispone già di un'applicazione ACCESS che svolge questo compito, ma è attratto dall'avventura del web. Ha un account presso un provider di accesso a Internet che consente ai propri clienti di installare script PHP nelle loro cartelle personali. Ciò permette loro di creare siti web dinamici. Inoltre, questi stessi clienti dispongono di un account MySQL che consente loro di creare tabelle in grado di fornire dati ai propri script PHP. Il commerciante dispone quindi di un account MySQL con nome utente «adarticles» e password «mdparticles». Possiede un database «dbarticles» sul quale ha tutti i diritti. Il nostro commerciante ha quindi gli elementi necessari per pubblicare online la propria gestione degli articoli. Con l’aiuto di chi, tra voi, possiede competenze nello sviluppo web, si lancia in questa avventura.
7.2. Il database
Il nostro commerciante realizza il seguente mockup dell’interfaccia web di benvenuto che vorrebbe:

Ci sarebbero due tipi di utenti:
- gli amministratori, che potrebbero eseguire qualsiasi operazione sul database degli articoli (aggiungere, modificare, eliminare, consultare, ...). Questi potranno utilizzare tutte le voci del menu sopra riportato. In particolare, potranno inviare qualsiasi richiesta SQL tramite l’opzione [Requête SQL].
- gli utenti normali (non amministratori) che avrebbero diritti limitati: diritti di aggiungere, modificare, eliminare, consultare. Potrebbero avere solo alcuni di questi diritti, ad esempio il solo diritto di consultazione.
Poiché esistono diversi tipi di utenti del database che non dispongono degli stessi diritti, è necessaria l’autenticazione. Ecco perché la pagina iniziale inizia con questa procedura. Per distinguere chi è chi e chi ha il diritto di fare cosa, verranno utilizzate due tabelle: USERS e DROITS. La tabella USERS avrebbe la seguente struttura:
![]() |
|
Il contenuto della tabella potrebbe essere il seguente:

La tabella DROITS specifica i diritti degli utenti non amministratori presenti nella tabella USERS. La sua struttura è la seguente:
![]() |
|
Il contenuto della tabella potrebbe essere il seguente:

Note:
- Un utente U presente nella tabella USERS e assente dalla tabella DROITS non ha alcun diritto.
- Nel nostro esempio, gli utenti avranno accesso a una sola tabella, la tabella ARTICLES. Tuttavia, il nostro commerciante, lungimirante, ha comunque aggiunto il campo «tabella» alla struttura della tabella DROITS per darsi la possibilità di aggiungere in seguito nuove tabelle alla propria applicazione.
- Perché gestire i diritti nelle nostre tabelle quando partiamo dal presupposto che useremo un database MySQL in grado esso stesso (e meglio di noi) di gestire tali diritti nelle proprie tabelle? Semplicemente perché il nostro commerciante non dispone dei diritti di amministrazione sul database MySQL che gli consentirebbero di creare utenti e assegnare loro dei diritti. Non dimentichiamo infatti che il database MySQL è ospitato presso un provider di accesso e che il commerciante ne è solo un semplice utente senza alcun diritto di amministrazione (fortunatamente). Dispone tuttavia di tutti i diritti su un database denominato dbarticles, al quale al momento accede con il login admarticles e la password mdparticles. È in questo database che si trovano tutte le tabelle dell’applicazione.
La tabella ARTICLES raccoglie le informazioni sugli articoli venduti dal commerciante. La sua struttura è la seguente:
![]() |
|
Il contenuto, utilizzato inizialmente a titolo di prova, potrebbe essere il seguente:

7.3. I vincoli del progetto
Il commerciante sta migrando un'applicazione locale ACCESS in un'applicazione web. Non sa quale sarà il futuro di quest'ultima né come si evolverà. Vorrebbe tuttavia che la nuova applicazione fosse facile da usare e scalabile. Per questo motivo, il suo consulente informatico ha previsto, durante la progettazione delle tabelle, la possibilità di avere:
- diversi utenti con diritti diversi: ciò consentirà al commerciante di delegare determinati compiti ad altre persone senza per questo concedere loro diritti di amministrazione
- in futuro, altre tabelle oltre alla tabella ARTICLES
Lo stesso consulente formula altre proposte:
- sa che nello sviluppo del software è necessario separare nettamente i livelli di presentazione da quelli di elaborazione. L’architettura di un’applicazione web è spesso la seguente:
![]() |
L'interfaccia utente è in questo caso un browser web, ma potrebbe anche trattarsi di un'applicazione autonoma che, tramite la rete, invierebbe richieste HTTP al servizio web e formatterebbe i risultati che quest'ultimo le invia. La logica applicativa è costituita dagli script che elaborano le richieste dell’utente, in questo caso gli script PHP. La fonte dei dati è spesso un database, ma può anche essere una directory LDAP o un servizio web remoto. È nell’interesse dello sviluppatore mantenere un’ampia indipendenza tra queste tre entità, in modo che, se una di esse dovesse cambiare, le altre due non debbano subire modifiche o ne subiscano solo in misura minima. Il consulente informatico del commerciante formula quindi le seguenti proposte:
- La logica di business dell’applicazione verrà inserita in una classe PHP. Pertanto, il blocco [Logique applicative] sopra riportato sarà costituito dai seguenti elementi:
![]() |
Nel blocco [Logique Applicative], si potrà distinguere
- il blocco [IE=Interface d'Entrée], che costituisce la porta d’accesso all’applicazione. È lo stesso indipendentemente dal tipo di cliente.
- il blocco [Classes métier], che raggruppa le classi necessarie alla logica dell’applicazione. Queste sono indipendenti dal cliente.
- il blocco dei generatori delle pagine di risposta [IS1 IS2 ... IS=Interface de Sortie]. Ogni generatore ha il compito di formattare i risultati forniti dalla logica applicativa per un determinato tipo di cliente: codice HTML per un browser o un telefono (WAP), codice XML per un'applicazione autonoma, ...
Questo modello garantisce una buona indipendenza rispetto ai client. Sia che il client cambi o che si voglia modificare il modo in cui vengono presentati i risultati, saranno i generatori di output [IS] che dovranno essere creati o adattati.
- In un’applicazione web, l’indipendenza tra il livello di presentazione e il livello di elaborazione può essere migliorata utilizzando i fogli di stile. Questi ultimi regolano la presentazione di una pagina web all’interno di un browser. Per modificare tale presentazione, è sufficiente cambiare il foglio di stile associato. Non è necessario intervenire sulla logica di elaborazione. In questo caso si utilizzerà quindi un foglio di stile.
- Nel diagramma sopra riportato, sarà la classe di business a fungere da interfaccia con la fonte di dati. Per ipotesi, tale fonte è qui un database MySQL. Per consentire il passaggio a un altro database, utilizzeremo la libreria PEAR, che offre classi di accesso ai database indipendenti dal tipo effettivo di questi ultimi. Pertanto, se il nostro commerciante dovesse arricchirsi al punto da poter installare un server web IIS di Microsoft nella propria azienda, potrà sostituire il database MySQL con SQL Server senza dover modificare (o modificando solo in minima parte) la classe di business.
7.4. La classe «articoli»
La classe articoli potrebbe essere definita come segue:
<?php
// classe articoli che opera su una base dati articoli composta dalle seguenti tabelle
// articoli: (codice, nome, prezzo, stockActuel, stockMinimum)
// utenti: (login, password, admin)
// diritti: (login, tabella, aggiungi, modifica, elimina, visualizza)
// è l'utente della classe che deve fornire username e password per poter eseguire qualsiasi operazione sul database
// quindi dispone già di tutti i diritti sul database. Ciò implica che non è necessario adottare
// particolari precauzioni di sicurezza in questo caso
// librerie
require_once 'DB.php';
class articles{
// attributi
var $sDSN; // la stringa di connessione
var $sDatabase; // il nome del database
var $oDB; // connessione al database
var $aErreurs; // elenco degli errori
var $oRésultats; // risultato di una query SELECT
var $connecté; // valore booleano che indica se si è connessi o meno al database
var $sQuery; // l'ultima query eseguita
var $sUser; // identificativo dell'utente connesso
var $bAdmin; // vero se l'utente è amministratore
var $dDroits; // il dizionario dei suoi diritti: tabella ->> array(visualizza, aggiungi, elimina, modifica)
// costruttore
function articles($dDSN,$sUser,$sMdp){
// $dDSN: dizionario che definisce il collegamento da stabilire
// $dDSN['sgbd']: il tipo di SGBD a cui è necessario connettersi
// $dDSN['host']: il nome del computer host che lo ospita
// $dDSN['database']: il nome del database a cui è necessario connettersi
// $dDSN['admin']: il nome utente del proprietario del database a cui ci si deve connettere
// $dDSN['mdpadmin']: la sua password
// $sUser: nome utente dell'utente che desidera utilizzare il database degli articoli
// $sMdp: la sua password
// crea in $oDB una connessione al database definito da $dDSN con l'identità di $dDSN['admin']
// se la connessione va a buon fine e l'utente $sUser viene autenticato
// carica i diritti in $bAdmin e $dDroits i diritti dell'utente $sUser
// inserisce in $sDSN la stringa di connessione al database
// inserisce in $sDataBase il nome del database a cui ci si connette
// imposta $connecté su vero
// se la connessione fallisce o se l'utente $sUser non è identificato correttamente
// inserisce i messaggi di errore appropriati nell'elenco $aErreurs
// chiude la connessione se necessario
// imposta $connecté su falso
...
}//costruttore
// ------------------------------------------------------------------
function connect(){
// (ri)connessione alla base
...
}//connessione
// ------------------------------------------------------------------
function disconnect(){
// si chiude la connessione alla base $sDSN
...
}//disconnessione
// -------------------------------------------------------------------
function execute($sQuery,$bAdmin){
// $sQuery: richiesta da eseguire
// $bAdmin: vero se la richiesta di esecuzione è come amministratore
...
}//esegui
// --------------------------------------------------------------------------
function addArticle($dArticle){
// aggiunge un articolo $dArticle (codice, nome, prezzo, stockActuel, stockMinimum) alla tabella degli articoli
...
}//aggiungi
// ----------------------------------------------------------------------
function modifyArticle($dArticle){
// modifica un articolo $dArticle (codice, nome, prezzo, stockActuel, stockMinimum) della tabella degli articoli
...
}//update
// ----------------------------------------------------------------------
function deleteArticle($sCode){
// elimina un articolo dalla tabella degli articoli
// di cui si conosce il codice $sCode
...
}//elimina
// ----------------------------------------------------------------------
function vérifierArticle(&$dArticle){
// verifica la validità di un articolo $dArticle (codice, nome, prezzo, stockActuel, stockMinimum)
...
}//verifica
// --------------------------------------------------------------------------
function selectArticles($dQuery){
// esegue una query SELECT sulla tabella degli articoli
// questa ha tre componenti
// elenco delle colonne in $dQuery['colonnes']
// filtro in $dQuery['where']
// ordine di presentazione in $dQuery['orderby']
...
}//selectArticles
// --------------------------------
function existeArticle($sCode){
// restituisce TRUE se l'articolo con codice $sCode esiste nella tabella degli articoli
...
}//existeArticle
// --------------------------------------
function existeUser($sUser,$sMdp){
// verifica l'esistenza dell'utente $sUser con la password $sMdp
// restituisce (int $iErreur, string $sAdmin, tabella hash $dDroits)
// $iErreur = -1 in caso di errore durante l'elaborazione del database - in tal caso viene compilato l'elenco $aErreurs
// $iErreur = 1 se l'utente non viene trovato (assente o password errata)
// $iErreur = 2 se l'utente esiste ma non ha alcun diritto nella tabella dei diritti
// $iErreur = 3 se l'utente esiste ed è amministratore
// $iErreur = 0 se l'utente esiste e non è amministratore
// $sAdmin="y" se l'utente esiste ed è amministratore ($iErreur==3), altrimenti è uguale alla stringa vuota
// $dDroits è il dizionario dei diritti dell'utente se non è amministratore ($iErreur==0)
// altrimenti è un array vuoto
// le chiavi del dizionario sono le tabelle su cui l'utente ha dei diritti
// il valore associato a questa tabella è a sua volta un dizionario in cui le chiavi sono i diritti
// (visualizza, aggiungi, modifica, elimina) e i valori sono le stringhe 'y' (sì) o 'n' (no) a seconda dei casi
...
}//existeUser
// --------------------------------------
function getCodes(){
// restituisce la tabella dei codici
....
}//getCodes
}//classifica
?>
Commenti
- La classe articoli utilizza la libreria PEAR::DB per accedere al database, da cui il comando
Tale inclusione presuppone che lo script DB.php si trovi in una delle directory specificate dall'opzione include_path del file di configurazione di PHP.
- Il generatore deve sapere a quale database connettersi e con quale identità. Queste informazioni gli vengono fornite nel dizionario $dDSN. Ricordiamo che l’ipotesi di partenza era che il database si chiamasse dbarticles e appartenesse a un utente denominato admarticles con la password mdparticles. Ricordiamo inoltre che questa applicazione consente l’accesso a più utenti con diritti diversi. Vi è qui un’ambiguità da chiarire. La connessione viene effettivamente aperta con l’identità admarticles e, in definitiva,è con questa identità che verranno eseguite tutte le operazioni sul database dbarticles, poiché è l’unico nome conosciuto da SGBD MySQL che disponga dei diritti sufficienti per gestire il database dbarticles. Per «simulare» l’esistenza di diversi utenti, faremo operare l’utente admarticles con i diritti dell’utente il cui login ($sUser) e la cui password ($sMdp) sono stati passati come parametri al costruttore. Pertanto, prima di eseguire un'operazione sul database degli articoli, si verificherà che l'utente ($sUser, $sMdp) disponga effettivamente dei diritti necessari per eseguirla. In caso affermativo, sarà l'utente admarticles a eseguirla per suo conto.
- Il nome utente e la password dell’amministratore del database degli articoli devono essere passati al costruttore. Si tratta di una precauzione opportuna. Se queste due informazioni fossero inserite «in modo rigido» nel codice della classe, qualsiasi utente della classe potrebbe facilmente spacciarsi per amministratore del database degli articoli. Infatti, una classe PHP non è protetta. Inoltre, l’attributo $bAdmin della classe, che indica se l’utente ($sUser, $sMdp) per cui si sta lavorando è amministratore o meno, potrebbe benissimo essere impostato direttamente dall’esterno come nell’esempio seguente:
$oArticles=new articles($dDSN,$sUser,$sMdp)
// qui $sUser è stato riconosciuto come utente non amministratore del database
$oArticle->bAdmin=TRUE;
// ora $sUser è diventato amministratore
PHP non è JAVA né in C# e una classe PHP è solo una struttura di dati leggermente più evoluta di un dizionario, ma che non offre la sicurezza di una vera e propria classe in cui l'attributo bAdmin sarebbe stato dichiarato privato o protetto, rendendo impossibile la sua modifica dall'esterno. Poiché l’utente della classe deve conoscere il nome utente e la password dell’amministratore del database degli articoli, solo quest’ultimo può utilizzare la classe. L’operazione precedente non presenta quindi più alcun interesse per lui. La classe è lì esclusivamente per offrirgli facilità di sviluppo. Una conseguenza importante è che non è necessario adottare precauzioni di sicurezza. Ancora una volta, chi utilizza la classe articles è necessariamente l’amministratore del database degli articoli.
- La classe gestisce gli errori di connessione al database o qualsiasi altro errore in modo univoco, compilando l’attributo $aErreurs con il messaggio o i messaggi di errore. Dopo ogni operazione, l’utente della classe deve quindi controllare questo elenco.
- I metodi addArticle, updateArticle, deleteArticle, selectArticles ed execute derivano direttamente dal modello dell’interfaccia web presentato in precedenza. Essi corrispondono infatti alle opzioni del menu proposto. I metodi addArticle e modifyArticle si basano sul metodo vérifierArticle per verificare che l’articolo che sta per essere aggiunto o modificato contenga dati corretti. Sempre nello stesso spirito, il metodo existeArticle consente di verificare che non si stia per aggiungere un articolo già esistente. Si potrebbe fare a meno di questo metodo se si utilizza una tabella di articoli in cui il codice funge da chiave primaria. In tal caso, sarà lo stesso SGBD a segnalare il fallimento dell’aggiunta a causa di un duplicato. Probabilmente lo segnalerà con un messaggio di errore poco leggibile e in inglese.
- Un articolo da modificare o eliminare sarà identificato dal suo codice, che è univoco. Il metodo getCodes consente di ottenere tutti questi codici.
- Il metodo disconnect chiude la connessione al database, aperta durante la creazione dell’oggetto. Non si vede qui l’utilità del metodo connect, che ricrea una connessione con il database. Ciò consentirà di aprire e chiudere questa connessione a piacimento con lo stesso oggetto. L’utilità emerge solo in combinazione con l’applicazione web. Quest’ultima creerà un oggetto articoli che memorizzerà in una sessione. Sebbene la sessione sia in grado di conservare la maggior parte degli attributi dell’oggetto nel corso dei successivi scambi client-server, non è tuttavia in grado di mantenere l’attributo che rappresenta la connessione aperta. Quest’ultima dovrà quindi essere riaperta ad ogni nuovo scambio client-server. Si richiederà una connessione persistente affinché la connessione aperta venga memorizzata in un pool di connessioni e rimanga aperta in modo permanente. In questo modo, quando lo script richiederà una nuova connessione, questa verrà recuperata dal pool di connessioni. Si ottiene quindi lo stesso risultato che si avrebbe se la sessione fosse stata in grado di memorizzare la connessione aperta.
- Il metodo existeUser consente al produttore di verificare se l'utente $sUser, identificato dalla password $sMdp, esiste effettivamente. In caso affermativo, il metodo consente di verificare se l'utente è amministratore o meno (come indicato nella tabella USERS) e memorizza tale informazione nell'attributo $bAdmin. Se non è amministratore, il metodo recupera i suoi diritti dalla tabella DROITS e li inserisce nell’attributo $dDroits, che è un dizionario a doppia indicizzazione: $dDroits[$table][$droit] è pari a 'y' se l'utente $sUser dispone del diritto $droit sulla tabella $table e vale 'n' in caso contrario.
Scrivere la classe «articoli». Gli accessi al database avverranno tramite la libreria PEAR::DB, che consente di prescindere dal tipo esatto del database.
7.5. La struttura dell’applicazione WEB
Ora che disponiamo della classe "di business" per la gestione del database degli articoli, possiamo utilizzarla in diversi ambienti. Qui proponiamo di utilizzarla in un'applicazione web. Scopriamola attraverso queste diverse pagine:
7.5.1. La pagina tipo dell’applicazione
Torniamo alla pagina iniziale già presentata:
1234

Tutte le pagine dell’applicazione avranno la struttura sopra riportata, ovvero quella di una tabella a due righe e tre colonne composta da quattro aree:
- l’area 1 costituisce la prima riga della tabella. È riservata al titolo, eventualmente accompagnato da un’immagine. Le tre colonne della riga sono qui unite.
- la seconda riga presenta tre aree, una per ogni colonna:
- l'area 2 contiene le opzioni del menu. A sua volta, contiene una tabella a una colonna e più righe. Le opzioni del menu sono collocate nelle righe della tabella.
- l'area 3 è vuota e serve solo a separare le aree 2 e 4. Si sarebbe potuto procedere in modo diverso per realizzare questa separazione.
- L'area 4 è quella che contiene la parte dinamica della pagina. È questa parte che cambia da un'azione all'altra, mentre le altre rimangono identiche.
Lo script PHP che genera questa pagina tipo si chiamerà main.php e potrebbe essere il seguente:
<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>
Le aree configurate della pagina sono state evidenziate nell'elenco sopra riportato. La pagina tipo è configurata in diversi modi:
- tramite un dizionario $main con le seguenti chiavi:
- title: titolo da inserire nell'area 1 della pagina
- liens: dizionari dei link da generare nella colonna del menu. Questi link sono associati alle opzioni del menu dell’area 2
- contenuto: URL della pagina da visualizzare nell'area 4
- tramite un dizionario $dConfig che raccoglie informazioni tratte da un file di configurazione dell’applicazione denominato config.php
- tramite classi facenti parte del foglio di stile utilizzato dalla pagina:
La pagina utilizza qui le seguenti classi di stile:
- menutitle: per un'opzione principale del menu
- menublock: per un'opzione secondaria del menu
Modificando uno dei parametri si modifica l'aspetto della pagina. Pertanto, modificando $main['title'] si modificherà il titolo dell'area 1.
7.5.2. Elaborazione tipica di una richiesta di un cliente
Il cliente interagisce con l’applicazione tramite i link dell’area 2 della pagina tipo. Questi link saranno del tipo seguente:
indica l’azione in corso tra le seguenti:
| |||||||||||
un'azione può essere eseguita in più fasi - indica la fase in corso | |||||||||||
token di sessione all'avvio della stessa - consente al server di recuperare le informazioni memorizzate nella sessione durante gli scambi precedenti |
Allo stesso modo, l’attributo «action» nei moduli avrà la stessa forma. Ad esempio, nella pagina iniziale è presente un modulo di accesso nell’area 4. Il tag HTML di questo modulo è definito come segue:
L'elaborazione della richiesta del client viene eseguita dallo script principale dell'applicazione denominato apparticles.php. Il suo compito è quello di costruire la risposta da inviare al client. Procederà sempre allo stesso modo:
- in base al nome dell'azione e alla fase in corso, delegherà la richiesta a una funzione specializzata. Quest'ultima elaborerà la richiesta e genererà la pagina di risposta adeguata. Per ogni richiesta del cliente, possono esserci diverse pagine di risposta possibili: pagina1, pagina2, ..., paginan. Queste pagine contengono informazioni che devono essere calcolate dalla funzione. Si tratta quindi di pagine parametrizzate. Saranno generate dagli script page1.php, page2.php, ..., pagen.php.
- Per motivi di uniformità, anche le parti variabili delle pagine da visualizzare nell’area 4 della pagina tipo saranno inserite nel dizionario $main.
Supponiamo che, in risposta a una richiesta, il server debba inviare la pagina pagex.php al client. Procederà nel modo seguente:
- inserirà nel dizionario $main i valori necessari per la pagina pagex.php
- inserirà in $main['contenu'], che indica il URL della pagina da visualizzare nell’area 4 della pagina modello, l'URL di pagex.php
- richiederà la visualizzazione della pagina modello con l'istruzione
La pagina modello verrà quindi visualizzata con, nell'area 4, il codice dello script pagex.php che verrà valutato per generare il contenuto dell'area 4. Si ricordi che quest'ultima è una semplice cella di una tabella. È quindi necessario che il codice HTML generato da pagex.php non inizi con i tag <HTML>, <HEAD>, <BODY>, .... Questi sono già stati emessi all’inizio della pagina modello. Ecco, ad esempio, come potrebbe apparire lo script login.php che genera l’area 4 della pagina iniziale:
<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>
Si nota che la pagina:
- è ridotta a un modulo
- è configurata sia dal dizionario $main che dal foglio di stile.
7.5.3. Il file di configurazione
È sempre consigliabile configurare il più possibile le applicazioni per evitare di dover intervenire sul codice semplicemente perché, ad esempio, si è deciso di modificare il percorso di uno script o di un'immagine. L'applicazione principale apparticles.php caricherà quindi un file di configurazione config.php all'avvio:
In questo file inseriremo le direttive di configurazione destinate a PHP e le inizializzazioni delle variabili globali:
<?php
// configurazione di PHP
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("expose_php","off");
ini_set("session.use_cookies","0"); // nessun cookie
// configurazione di base degli articoli
$dConfig["DSN"]=array(
"sgbd"=>"mysql",
"admin"=>"admarticles",
"mdpadmin"=>"mdparticles",
"host"=>"localhost",
"database"=>"dbarticles"
);
// URL delle pagine
$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";
// link della pagina principale
$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";
// si memorizza $main nella configurazione
$dConfig["main"]=$main;
?>
7.5.4. Il foglio di stile associato alla pagina tipo
Abbiamo visto che la risposta del server aveva un formato unico, ovvero main.php. Si è potuto notare che questo script produce una pagina grezza priva di effetti di presentazione. Questo è un aspetto positivo per diversi motivi:
- lo sviluppatore non deve preoccuparsi della presentazione grafica della pagina che sta creando. Infatti, non possiede necessariamente le competenze per realizzare pagine grafiche accattivanti. In questo modo può concentrarsi interamente sul codice.
- la manutenzione degli script è facilitata. Se questi contenessero attributi di presentazione, né la struttura del codice né quella della presentazione risulterebbero chiare. L’aspetto grafico delle pagine è spesso affidato a un grafico. Quest’ultimo probabilmente non gradirebbe dover cercare in uno script che non comprende dove si trovano gli attributi di presentazione che deve modificare.
È tuttavia necessario prestare attenzione all’aspetto grafico delle pagine. Infatti, è proprio questo che attira gli utenti verso un sito. In questo caso, la presentazione è affidata a un foglio di stile. La pagina main.php indica nel proprio codice il foglio di stile da utilizzare per visualizzarla:
<head>
<title>Gestion d'articles</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
Il foglio di stile utilizzato in questo documento è il seguente:
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;
}
Non entreremo nei dettagli di questo foglio di stile. Lo accetteremo così com'è. Più avanti vedremo come costruirlo e modificarlo. Esistono programmi appositi per farlo. Tuttavia, indichiamo il ruolo degli attributi di presentazione utilizzati nel foglio:
Attributo: | regola la presentazione del tag HTML: |
<BODY> | |
<H1> (Intestazione 1) | |
<A> (Anchor) | |
imposta gli attributi di visualizzazione dell'ancora quando l'utente ci passa sopra con il mouse | |
<FIELDSET> - questo tag non è riconosciuto da tutti i browser | |
<LEGEND> - questo tag non è riconosciuto da tutti i browser | |
<INPUT> | |
<INPUT class="TEXT"> | |
<INPUT class="SUBMIT"> | |
<TH> (Intestazione tabella) | |
<TD class="menutitle"> (Dati della tabella) | |
<TD class="menublock"> | |
<TD class="libellé"> |
Vediamo con un esempio come si possono scrivere queste regole di formattazione. In questo esempio utilizzeremo il software TopStyle Lite, disponibile gratuitamente all'indirizzo http://www.bradsoft.com. Una volta caricato il foglio di stile, si apre una finestra suddivisa in tre aree:
- un'area di modifica del testo. Gli attributi di formattazione possono essere definiti manualmente, a condizione di conoscere le regole di scrittura dei fogli di stile che seguono uno standard denominato CSS (Cascading Style Sheets).
- l’area 2 presenta le proprietà modificabili dell’attributo in fase di creazione. È il metodo più semplice. Evita di dover conoscere il nome esatto degli attributi di presentazione, che sono molto numerosi
- L'area 3 mostra l'aspetto visivo dell'attributo in fase di creazione
![]() |
Nell’area 1 sopra riportata, copiamo e incolliamo l’attributo INPUT.submit in un attributo INPUT.fantaisie. Questo attributo definirà la presentazione del tag HTML <INPUT class="fantaisie">
![]() |
Utilizziamo l'area 2 per modificare alcune delle proprietà dell'attributo INPUT.fantaisie:
![]() |
D'ora in poi, ogni tag <INPUT ... class="fantaisie"> presente in una pagina HTML associata al foglio di stile precedente verrà visualizzato come nell'esempio dell'area 3 sopra riportata.
I fogli di stile offrono notevoli vantaggi. Il loro utilizzo consente di modificare l’aspetto di un’applicazione web intervenendo su un unico elemento: il foglio di stile. I fogli di stile non sono supportati dai browser meno recenti. La direttiva <link ..> riportata di seguito verrà ignorata da alcuni di essi:
<head>
<title>Gestion d'articles</title>
<link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
</head>
Nella nostra applicazione, questo genererà la seguente pagina iniziale:

Si tratta di una pagina minimale senza elementi grafici. Potrebbe andare peggio. Alcune versioni dei browser riconoscono i fogli di stile ma li interpretano in modo errato. Si può quindi ottenere una pagina deturpata e inutilizzabile. Si pone quindi la questione del tipo di browser client. Esistono tecniche che aiutano a determinare il tipo di browser client, ma non sono del tutto affidabili. Si possono quindi scrivere diversi fogli di stile per diversi browser o addirittura scrivere una versione senza fogli di stile per i browser che li ignorano. Ciò appesantisce ovviamente il lavoro di sviluppo. Questo importante problema è stato qui ignorato.
Grazie ai fogli di stile, è possibile offrire un'esperienza personalizzata agli utenti della nostra applicazione. Potremmo presentare loro una pagina che proponga diversi stili di visualizzazione tra cui scegliere. Gli utenti potrebbero selezionare quello che preferiscono. Questa scelta potrebbe essere salvata in un database. Quando l'utente effettua nuovamente l'accesso, l'applicazione potrebbe avviarsi con il foglio di stile che ha indicato come preferenza.
7.5.5. Il modulo di immissione dati dell’applicazione
I clienti conosceranno dell’applicazione solo il suo modulo di ingresso: apparticles.php. Le linee generali del suo funzionamento sono le seguenti:
- La richiesta del cliente viene recuperata e analizzata. Questa può essere configurata o meno. Quando è parametrizzata, i parametri previsti sono i seguenti: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
- Se la richiesta non è configurata o se i parametri recuperati non corrispondono a quelli previsti, il server invia come risposta la pagina di autenticazione (login, password). Non appena l’utente si sarà identificato correttamente, verrà creata una sessione. Questa servirà a memorizzare le informazioni durante tutti gli scambi client-server.
- Se una richiesta viene riconosciuta correttamente, viene elaborata da un modulo che dipende sia dall’azione che dalla fase in corso.
- Tutti gli accessi al database avvengono tramite la classe di business articles.php.
- L’elaborazione di una richiesta termina sempre con l’invio al cliente della pagina main.php, nella quale è stato specificato in $main['contenu'] l’URL della pagina da inserire nell’area 4 della pagina modello.
La struttura dello script apparticles.php potrebbe essere la seguente:
<?php
// gestione di una tabella articoli
include "config.php";
include "articles.php";
// azione da intraprendere
$sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
$sAction=strtolower($sAction);
// fase eventuale
$sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";
// sessione
session_start();
$dSession=$_SESSION["session"];
// c'è una sessione in corso?
if(! isset($dSession)){
// autenticazione dell'utente
if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
// richiesta non corretta
authentifier_0($dConfig);
}//if - nessuna sessione
// si recupera la sessione
$dSession=unserialize($dSession);
// elaborazione della richiesta
// ----- autenticazione
if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
// ----- aggiunta di un articolo
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);
// ----- aggiornamento articolo
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);
// ----- eliminazione di un articolo
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);
// ----- consultazione articoli
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);
// ----- invio di una richiesta 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);
// azione errata - viene visualizzata la pagina di autenticazione
session_destroy();
authentifier_0($dConfig,"0");
...
?>
Si notino i seguenti punti:
- le funzioni che gestiscono una richiesta specifica del cliente terminano con la generazione della pagina di risposta e con un'istruzione exit che conclude l'esecuzione dello script apparticles.php. In altre parole, non si "torna indietro" da queste funzioni.
- le funzioni accettano uno o due parametri:
- $dConfig è un dizionario contenente informazioni provenienti dal file di configurazione config.php. Tutte le funzioni lo utilizzano.
- $dSession è un dizionario contenente informazioni sulla sessione. Esiste solo quando la sessione è stata creata, ovvero dopo che l'autenticazione dell'utente è andata a buon fine. Per questo motivo le funzioni di autenticazione non dispongono di questo parametro.
7.5.6. La pagina degli errori
Ogni applicazione software deve essere in grado di gestire correttamente gli errori che possono verificarsi. Un’applicazione web non fa eccezione a questa regola. In questo caso, in presenza di un errore, inseriremo la seguente pagina erreurs.php nell’area 4 della pagina tipo:
Les erreurs suivantes se sont produites :
<ul>
<?php
for($i=0;$i<count($main["erreurs"]);$i++){
echo "<li>".$main["erreurs"][$i]."</li>\n";
}//per
?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>
Presenta l'elenco degli errori definito in $main['erreurs']. Inoltre, può proporre un link di ritorno, in genere alla pagina precedente alla pagina degli errori. Tale link sarà definito da una dicitura $main['lien'] e da un URL $main['href']. Per evitare che compaia questo link, sarà sufficiente inserire una stringa vuota in $main['lien']. Ecco un esempio di pagina di errore nel caso in cui l’utente effettui un accesso non corretto:

7.5.7. La pagina informativa
A volte si vorrà fornire all’utente una semplice informazione, ad esempio che l’autenticazione è andata a buon fine. A tal fine si utilizzerà la seguente pagina infos.php:
Per visualizzare un'informazione in risposta a una richiesta di un cliente,
- inseriremo l'informazione in $main['infos']
- inseriremo il valore di URL, ricavato da infos.php, in $main['contenu']
Ecco, ad esempio, le informazioni restituite quando l'utente ha effettuato correttamente l'autenticazione:

7.6. Il funzionamento dell’applicazione
Ora abbiamo un'idea chiara della struttura generale dell'applicazione da realizzare. Resta da illustrare il percorso dell'utente all'interno dell'applicazione, le azioni che può compiere e le risposte che riceve dal server. Una volta fatto ciò, potremo scrivere le funzioni che gestiscono le diverse richieste di un cliente. Di seguito illustreremo il funzionamento dell’applicazione attraverso le pagine presentate all’utente in risposta ad alcune di queste azioni. Specificheremo ogni volta i seguenti punti:
azione iniziale dell’utente che ha portato alla risposta visualizzata | |
i parametri inviati dal browser del cliente al server in risposta all’azione manuale dell’utente | |
lo script che genera l'area 4 della pagina modello |
7.6.1. L'autenticazione
Prima di poter utilizzare l'applicazione, l'utente dovrà autenticarsi tramite la seguente pagina:

1 - richiesta iniziale di URL apparticles.php 2 - utilizzo dell'opzione Autenticazione dal menu 3 - richiesta diretta di URL articles.php con parametri errati | |
1 - nessun parametro 2 - action=autenticare?phase=0 3 - un elenco di parametri errati | |
login.php |
Nella pagina iniziale, il link [Ajouter un article] ha la seguente forma: action=addarticle?phase=0. Gli altri link hanno la stessa forma con action=(autenticare, aggiornare articolo, eliminare articolo, selezionare articolo, sql). L'utente compila il modulo e utilizza il pulsante [Connexion]:

La risposta è la seguente:

bouton [Connexion] | |
action=autenticare?phase=1 | |
infos.php |
Il titolo della pagina è stato modificato per indicare il nome utente e i suoi diritti di amministratore/utente. Inoltre, tutti i link dell'area 2 sono stati modificati per riflettere il fatto che è stata avviata una sessione. È stato aggiunto il parametro PHPSESSID=[PHPSESSID].
Se il server non è riuscito a identificare il client, quest’ultimo riceverà una risposta diversa:

bouton [Connexion] | |
azione=autenticare?fase=1 | |
erreurs.php |
Il link [Retour à la page de login] è un link che rimanda a URL apparticles.php?action=authentifier&phase=2&txtLogin=x. Questo link reindirizza l'utente alla pagina di accesso, dove il campo di accesso viene precompilato con il valore del parametro txtLogin:

lien [Retour à la page de login] | |
action=autenticare?phase=2&txtLogin=x | |
login.php |
7.6.2. Aggiunta di un articolo
Il link del menu [Ajouter un article] porta alla pagina seguente nell'area 4 della pagina tipo:

lien [Ajouter un article] | |
action=addArticle?phase=0&PHPSESSID=[PHPSESSID] | |
addarticle.php |
L'utente compila i campi e invia il tutto al server tramite il pulsante [Ajouter], che è di tipo submit. Non viene effettuata alcuna verifica sul lato client. È il server a occuparsene. Il server può inviare in risposta una pagina di errore come nell'esempio riportato di seguito:
Richiesta | Risposta |
![]() | ![]() |
bouton [Ajouter] | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
erreurs.php |
Il link [Retour à la page d'ajout d'article] consente di tornare alla pagina di inserimento:
Richiesta | Risposta |
![]() | ![]() |
lien [Retour à la page d'ajout d'article] | |
action=addArticle?phase=2&PHPSESSID=[PHPSESSID] | |
article.php |
Se l'aggiunta avviene senza errori, l'utente riceve un messaggio di conferma:
Richiesta | Risposta |
![]() | ![]() |
bouton [Ajouter] | |
action=addArticle?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.3. Visualizzazione degli articoli
Il link del menu [Lister des articles] porta alla pagina seguente nell'area 4 della pagina tipo:

link del menu [Lister des articles] | |
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID] | |
select1.php |
Verrà emessa una query select [colonnes] from articles where [where] orderby [orderby] sulla tabella degli articoli, dove [colonnes], [where] e [orderby] sono i valori dei campi sopra indicati. Ad esempio:
Richiesta |
![]() |
Risposta |
![]() |
bouton [Afficher] | |
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID] | |
select2.php |
La richiesta potrebbe essere errata; in tal caso, al cliente verrà visualizzata una pagina di errore:
Richiesta |
![]() |
Risposta |
![]() |
In entrambi i casi (con o senza errori), il link [Retour à la page de sélection d'articles] consente di tornare alla pagina select1.php:
Richiesta |
![]() |
Risposta |
![]() |
lien [Retour à la page de sélection d'articles] | |
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID] | |
select1.php |
7.6.4. Modifica degli articoli
Il link del menu [Modifier un article] porta alla pagina seguente nell'area 4 della pagina modello:

collegamento di menu [Modifier un article] | |
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID] | |
updatearticle1.php |
Si seleziona il codice dell'articolo da modificare dall'elenco a discesa e si digita [OK] per modificare l'articolo con quel codice:
Richiesta | Risposta |
![]() | ![]() |
bouton [OK] | |
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
Una volta ottenuta la scheda dell'articolo da modificare, l'utente può apportare le modifiche:
Richiesta | Risposta |
![]() | ![]() |
bouton [Modifier] | |
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID] | |
infos.php |
L'utente potrebbe commettere degli errori durante la modifica:
Richiesta | Risposta |
![]() | ![]() |
Il link [Retour à la page de modification d'article] consente di tornare alla pagina di inserimento:

lien [Retour à la page de modification d'article] | |
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID] | |
updatearticle2.php |
7.6.5. Eliminazione di un articolo
Il link del menu [Supprimer un article] rimanda alla seguente pagina nell'area 4 della pagina tipo:

collegamento del menu [Supprimer un article] | |
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID] | |
deletearticle1.php |
L'utente seleziona il codice dell'articolo da eliminare da un elenco a discesa:
Richiesta | Risposta |
![]() | ![]() |
bouton [OK] | |
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID] | |
deletearticle2.php |
L'utente conferma l'eliminazione dell'articolo con il pulsante [Supprimer]:
Richiesta | Risposta |
![]() | ![]() |
bouton [Supprimer] | |
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.6. Richieste dell'amministratore
Il link del menu [Requête SQL] porta alla seguente pagina nell'area 4 della pagina tipo:

link del menu [Requête SQL] | |
action=sql?phase=0&PHPSESSID=[PHPSESSID] | |
sql1.php |
Si digita il testo della query SQL nel campo di immissione e si utilizza il pulsante [Exécuter] per eseguirla. Solo un amministratore può inviare queste query, come mostra l'esempio seguente:
Richiesta | Risposta |
![]() | ![]() |
bouton [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
erreurs.php |
Il link [Retour à la page d'émission de requêtes SQL] consente di tornare alla pagina di inserimento:

lien [Retour à la page d'émission de requêtes SQL] | |
action=sql?phase=2&PHPSESSID=[PHPSESSID] | |
sql1.php |
Se si è amministratori e la richiesta è sintatticamente corretta:
Richiesta |
![]() |
si ottiene il risultato della richiesta:
Risposta |
![]() |
bouton [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
sql2.php |
È possibile inviare richieste di aggiornamento delle tabelle:
Richiesta |
![]() |
Risposta |
![]() |
bouton [Exécuter] | |
action=sql?phase=1&PHPSESSID=[PHPSESSID] | |
infos.php |
7.6.7. Lavoro da svolgere
Scrivere gli script e le funzioni necessari per l'applicazione:
nome utente | tipo | ruolo |
script | il punto di ingresso per l'elaborazione delle richieste dei clienti | |
funzione | elabora la richiesta con i parametri action=autenticare&fase=0 | |
funzione | elabora la richiesta con i parametri action=autenticare&fase=1 | |
funzione | elabora la richiesta con i parametri action=autenticare&fase=2 | |
funzione | elabora la richiesta con i parametri action=addArticle&phase=0 | |
funzione | elabora la richiesta con i parametri action=addArticle&phase=1 | |
funzione | elabora la richiesta con i parametri action=addArticle&phase=2 | |
funzione | elabora la richiesta con i parametri action=updatearticle&phase=0 | |
funzione | elabora la richiesta con i parametri action=updatearticle&phase=1 | |
funzione | elabora la richiesta con i parametri action=updatearticle&phase=2 | |
funzione | elabora la richiesta con i parametri action=updatearticle&phase=3 | |
funzione | elabora la richiesta con i parametri action=deletearticle&phase=0 | |
funzione | elabora la richiesta con i parametri action=deletearticle&phase=1 | |
funzione | elabora la richiesta con i parametri action=deletearticle&phase=2 | |
funzione | elabora la richiesta con i parametri action=selectarticle&phase=0 | |
funzione | elabora la richiesta con i parametri action=selectarticle&phase=1 | |
funzione | elabora la richiesta con i parametri action=selectarticle&phase=2 | |
funzione | elabora la richiesta con i parametri action=sql&phase=0 | |
funzione | elabora la richiesta con i parametri action=sql&phase=1 | |
funzione | elabora la richiesta con i parametri action=sql&phase=2 | |
script | genera la pagina tipo | |
script | genera la pagina di accesso | |
script | genera la pagina degli errori | |
script | genera la pagina delle informazioni | |
script | genera la pagina di aggiunta di un articolo | |
script | genera la pagina 1 della modifica di un articolo | |
script | genera la pagina 2 della modifica di un articolo | |
script | genera la pagina 1 della cancellazione di un articolo | |
script | genera la pagina 2 relativa alla cancellazione di un articolo | |
script | genera la pagina 1 della selezione degli articoli | |
script | genera la pagina 2 della selezione di articoli | |
script | genera la pagina 1 dell'invio delle richieste | |
script | genera la pagina 2 dell'invio delle richieste |
7.7. Migliorare l'applicazione
A questo punto disponiamo di un'applicazione che svolge le funzioni previste con un'ergonomia accettabile. Procederemo a migliorarla sotto diversi aspetti:
- la SGBD
- la sua sicurezza
- il suo aspetto
- le sue prestazioni
7.7.1. Modifica del tipo di database
Il nostro studio partiva dal presupposto che il SGBD utilizzato fosse MySQL. Modifichi il valore in SGBD e dimostri che l’unica modifica da apportare riguarda la definizione della variabile $dDSN nel file di configurazione config.php.
7.7.2. Migliorare la sicurezza
Durante lo sviluppo di un'applicazione web, non bisogna mai dare per scontato che il client sia un browser e che la richiesta che ci invia sia controllata dal modulo che gli è stato inviato prima di tale richiesta. Qualsiasi programma può fungere da client di un'applicazione web e quindi inviare all'applicazione qualsiasi richiesta, con o senza parametri. L'applicazione deve quindi verificare tutto.
Se si esamina il codice dello script apparticles.php, si nota
- che nessuna azione, ad eccezione dell’autenticazione, può avvenire senza una sessione. Questa esiste solo se l’utente è riuscito ad autenticarsi. Ricordiamo che una sessione è identificata da una stringa di caratteri piuttosto lunga chiamata token di sessione, che ha la seguente forma: 176a43609572907333118333edf6d1fb. Questo token può essere inviato all’applicazione in vari modi, ad esempio utilizzando un URL configurato:
apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb.
Un programma che richiedesse ripetutamente il precedente URL variando il token in modo casuale nella speranza di trovare quello corretto, impiegherebbe probabilmente molti giorni prima di generare la combinazione giusta, dato l’elevato numero di combinazioni possibili. A quel punto, poiché la sessione ha una durata limitata, molto probabilmente sarà già terminata. Un altro rischio sarebbe che il token, trasmesso in chiaro sulla rete, venisse intercettato. Il rischio è reale. È quindi possibile utilizzare una connessione crittografata tra il server e il client.
- una volta avviata la sessione, siano consentite solo determinate azioni. Un token URL configurato con action=tricher&phase=0&PHPSESSID=[PHPSESSID] verrebbe rifiutato poiché l’azione «tricher» non è consentita. Quando i parametri (azione, fase) non vengono riconosciuti, la nostra applicazione risponde con la pagina di autenticazione.
Tuttavia, l’applicazione non verifica se le azioni autorizzate si susseguono correttamente. Ad esempio, le due azioni seguenti:
- action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
- action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]
sono due azioni autorizzate. Tuttavia, l’azione 2 non è autorizzata a seguire l’azione 1.
Come monitorare la sequenza delle richieste URL inviate dal browser del cliente?
È possibile avvalersi di due variabili PHP: $_SERVER['REQUEST_URI] e $_SERVER['HTTP_REFERER], che sono due informazioni inviate dai browser client nelle loro intestazioni HTTP.
$_SERVER['REQUEST_URI]: è l'URI richiesta dal client. Ad esempio
$_SERVER['HTTP_REFERER]: Si tratta dell'URL che era visualizzato nel browser prima del nuovo URL che il browser sta richiedendo (il precedente URI). Ad esempio, se il browser che ha visualizzato il file URI menzionato in precedenza effettua una nuova richiesta a un server, la variabile $_SERVER['HTTP_REFERER'] di quest’ultimo avrà come valore
Per verificare che due azioni della nostra applicazione si susseguano nell’ordine corretto, si può procedere come segue:
Durante l'azione 1:
- si prende nota del codice URI richiesto (URI1) e lo si registra nella sessione
Durante l'azione 2:
- si recupera il codice HTTP-REFERER dell'azione 2. Da questo si ricava il codice URI (URI2) dal codice URL che era stato precedentemente visualizzato nel browser che effettua la richiesta.
- Si recupera il codice URI (URI1) che era memorizzato nella sessione e che corrisponde al codice URI dell’azione richiesta in precedenza al server
- Se l'azione 2 segue l'azione 1, allora si deve avere URI2 = URI1. In caso contrario, si rifiuterà di eseguire l'azione richiesta e verrà visualizzata la pagina di autenticazione.
- Si registra nella sessione il codice URI URI2 dell'azione in corso per la verifica dell'azione successiva. E così via.
Ecco un esempio. Dopo l’autenticazione, si seleziona il link [Ajouter un article]:

Il codice URL di questa pagina è:
http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb
Direttamente nel campo [Adresse] del browser, modifichiamo il codice URL nel modo seguente:
http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb
Otteniamo quindi la pagina di autenticazione:

Questo merita una spiegazione. Quando si richiede un URL digitando direttamente l’identificativo nel campo dell’indirizzo del browser, quest’ultimo non invia l’intestazione HTTP_REFERER. La nostra applicazione non trova quindi l’URI dell’azione precedente, URI, che aveva memorizzato nella sessione. Restituisce quindi la pagina di autenticazione come risposta.
Questo meccanismo è efficace per i browser, ma non lo è affatto per un client programmato. Quest’ultimo può inviare l’intestazione HTTP_REFERER che desidera. Può quindi “barare” affermando di aver effettivamente completato una determinata fase, quando in realtà non l’ha fatto. È quindi necessario assicurarsi che la sequenza delle fasi sia rispettata. Pertanto, se l’azione richiesta è action=addArticle&phase=1 (inserimento dati), l’azione precedente deve necessariamente essere action=deleteArticle&phase=0 (richiesta iniziale della pagina di inserimento dati) oppure action=addArticle&phase=2 (ritorno alla pagina di inserimento dati dopo un’aggiunta errata). Allo stesso modo, se l’azione richiesta è action=addArticle&phase=2 (aggiunta), allora l’azione precedente deve essere action=addArticle&phase=1 (inserimento). È possibile imporre all’utente di rispettare queste sequenze.
Mentre il primo meccanismo è generico e può essere applicato a qualsiasi applicazione, il secondo richiede una codifica specifica per ogni applicazione ed è più complesso: occorre esaminare tutte le possibili azioni dell’utente e le relative sequenze. È possibile memorizzare queste ultime in un dizionario, come mostra il codice seguente:
// autenticazione
$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'),
);
// aggiunta di un articolo
$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'),
);
// modifica articolo
$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'),
);
// eliminazione articolo
$dPrec['deletearticle']['0']=array();
$dPrec['deletearticle']['1']=array(
array('action'=>'deletearticle','phase'=>'0'),
);
$dPrec['deletearticle']['2']=array(
array('action'=>'deletearticle','phase'=>'1'),
);
// selezione articoli
$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'),
);
// richiesta amministratore
$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'] è una tabella che contiene le azioni che possono precedere l'azione e la fase che fungono da indice per il dizionario. Anche queste azioni precedenti sono rappresentate da un dizionario con due chiavi: «azione» e «fase». Se un’azione può essere preceduta da qualsiasi altra azione, allora $dPrec['action']['phase'] sarà una tabella vuota. L’assenza di un’azione nel dizionario significa che essa non è consentita. Consideriamo l’azione «autenticare» sopra riportata:
// autenticazione
$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'),
);
Il codice sopra riportato indica che l'azione action=authentifier&phase=0 può essere preceduta da qualsiasi azione, che l'azione action=authentifier&phase=1 può essere preceduta da action=authentifier&phase=0 o da action=authentifier&phase=2 e che l'azione action=authentifier&phase=2 può essere preceduta dall'azione action=authentifier&phase=1.
Scrivere la seguente funzione:
// ---------------------------------------------------------------
function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
// verifica se l'azione in corso ($sAction, $sPhase) può seguire l'azione precedente
// memorizzata in $dSession['précédent']
// il dizionario delle sequenze consentite si trova in $dConfig['précédents']
// restituisce TRUE se la concatenazione è possibile, FALSE in caso contrario
....
Questa funzione consente all'applicazione principale di verificare che la sequenza delle azioni sia corretta:
<?php
// gestione di una tabella di articoli
include "config.php";
include "articles.php";
// sessione
session_start();
$dSession=$_SESSION["session"];
// azione da intraprendere
$sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
$sAction=strtolower($sAction);
// eventuale fase dell'azione
$sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";
// c'è una sessione in corso?
if(! isset($dSession)){
// autenticazione dell'utente
if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
// azione anomala
authentifier_0($dConfig);
}//if - nessuna sessione
// si recupera la sessione
$dSession=unserialize($dSession);
// La sequenza delle azioni è normale?
if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
// sequenza anomala
authentifier_0($dConfig);
}//if
// elaborazione delle azioni
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. Evoluzione del "look"
Ricordiamo che una delle condizioni poste durante lo studio di questa applicazione era che dovesse essere evolutiva. Supponiamo che, dopo alcune settimane, ci si accorga che l’ergonomia dell’applicazione debba essere migliorata. Modificate l’applicazione in modo tale da cambiare la struttura e la presentazione della pagina tipo. Le modifiche avverranno in due punti:
- nello script main.php che definisce la struttura della pagina tipo. Aggiornate quest’ultima.
- nel foglio di stile che definisce l’aspetto dell’applicazione. Modificatelo.
7.7.4. Migliorare le prestazioni
Per il momento, abbiamo optato per un browser client leggero: non fa altro che occuparsi della presentazione. È possibile fargli eseguire delle elaborazioni includendo degli script nelle pagine Web che gli vengono inviate. Questi possono essere scritti in diversi linguaggi, in particolare VBScript e JavaScript. Internet Explorer e Netscape dominano il mercato dei browser in un rapporto di circa 60/40. Inoltre, IE esiste solo nell’ambiente Windows e non su Unix, ad esempio, dove predomina Netscape. Netscape non esegue in modo nativo gli script VBScript, mentre entrambi i browser eseguono gli script JavaScript. Poiché Netscape occupa ancora una quota significativa del mercato dei browser, è consigliabile evitare gli script VBScript. È quindi JavaScript che viene generalmente utilizzato negli script lato client.
Vengono delegati agli script lato client le operazioni in cui il server non deve intervenire. Nella nostra applicazione, sarebbe opportuno che il browser client inviasse una richiesta al server solo dopo averla verificata. In questo modo, è superfluo inviare al server una richiesta di autenticazione se l’utente ha lasciato vuoto il campo [login] nel modulo di autenticazione. È preferibile avvisare l’utente che la sua richiesta è errata:

Si noti che ciò non impedirà al server di verificare che il campo login non sia vuoto, poiché il suo client non è necessariamente un browser e quindi la verifica precedente potrebbe non essere stata effettuata. Partire dal presupposto che il client sia un browser rappresenta un rischio significativo per la sicurezza dell’applicazione.
Esaminate i diversi momenti in cui il browser invia informazioni al server e, quando queste possono essere verificate, scrivete una o più funzioni JavaScript che consentano al browser di verificare la validità delle informazioni prima del loro invio al server.
Riprendendo l’esempio precedente, lo script login.php che genera la pagina di autenticazione diventa il seguente:
<script language="javascript">
function check(){
// si verifica che sia stato effettuato il login
with(document.frmLogin){
champs=/^\s*$/.exec(txtLogin.value);
if(champs!=null){
// nessun login
alert("Vous n'avez pas indiqué de login");
txtLogin.focus();
return;
}//if
// i dati sono presenti - li si invia al server
submit();
}//con
}//controllo
</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. Per approfondire
Per concludere, segnaliamo alcuni spunti per approfondire questo caso di studio:
- sarebbe interessante verificare se la pagina tipo di questa applicazione possa essere implementata come classe. In tal caso, potrebbe essere riutilizzata in altre applicazioni.
- la nostra applicazione è ben adatta a client di tipo browser, ma meno a client di tipo "Applicazione autonoma". Questi ultimi devono:
- creare una connessione TCP con il server
- "comunicare" con esso tramite HTTP
- analizzare le sue risposte HTML per trovare le informazioni desiderate, poiché il client autonomo probabilmente non sarà interessato al codice di presentazione HTML destinato ai browser.
Sarebbe interessante che la nostra applicazione generasse XML anziché HTML. I suoi clienti potrebbero quindi essere indifferentemente browser (comunque abbastanza recenti) o applicazioni autonome. Queste ultime non avrebbero alcuna difficoltà a trovare le informazioni che cercano, poiché la risposta XML del server non conterrebbe alcuna informazione di presentazione, ma solo contenuto.
- Sarebbe sicuramente necessario prestare attenzione agli accessi simultanei al database degli articoli. Ci sono almeno due punti da chiarire:
- il codice SGBD utilizzato dall’applicazione gestisce correttamente l’accesso simultaneo allo stesso articolo? Ad esempio, cosa succede se due utenti modificano lo stesso articolo contemporaneamente (premendo il pulsante [Modifier] nello stesso momento)? Probabilmente dipende dal SGBD sottostante.
- Attualmente la nostra applicazione non gestisce gli accessi simultanei. Tuttavia, il database dovrebbe rimanere in uno stato coerente, anche se sono da aspettarsi delle sorprese. Consideriamo la seguente sequenza di eventi:
- l’utente U1 accede alla modifica di un articolo
- l'utente U2 avvia la procedura di eliminazione dello stesso articolo poco dopo
- Ciascuna delle due azioni richiede scambi client-server. A seconda del modo di lavorare di ciascuno, l’utente U2 potrebbe terminare il proprio lavoro prima di U1. Quando quest'ultimo terminerà le sue modifiche e le convaliderà tramite [Modifier], riceverà in risposta la pagina informativa, in cui SGBD gli indicherà che [0 ligne(s) ont été modifiées], poiché la pagina che intendeva modificare è stata nel frattempo eliminata. L’utente ne rimarrà senza dubbio sorpreso. Dal punto di vista dell’ergonomia, sarebbe sicuramente preferibile visualizzare una pagina che segnali meglio l’errore. Inoltre, si potrebbe prevedere di offrire all’utente l’accesso esclusivo a un articolo non appena avvia la modifica dello stesso. A un altro utente che volesse modificare lo stesso articolo verrebbe segnalato che è già in corso un’altra modifica. Ciò costituirà un problema se il primo utente tarda a confermare la propria modifica: gli altri rimarranno bloccati. Occorre trovare delle soluzioni che dipenderanno in larga misura dalle capacità del SGBD utilizzato. Oracle, ad esempio, dispone di maggiori capacità in questo ambito rispetto al MySQL.































