Capitolo 7.   Web, basi di dati e linguaggio PHP

La possibilità di interfacciare una base di dati relazionale con il Web è senza dubbio assai interessante in quanto unisce la potenza, l'affidabilità, la sicurezza dei moderni RDBMS (Relational Data Base Management System) con la facilità d'uso e la diffusione ormai universale degli strumenti di navigazione sia in Internet che nelle Intranet aziendali.

I campi di impiego di questa tecnologia sono numerosissimi: si pensi ad esempio alla possibilità di fornire ai cittadini l'accesso via Internet a dati di interesse pubblico o personale, oppure, all'interno di una azienda, alla gestione di archivi con interfacce basate sui normali, e molto «amichevoli», programmi di navigazione, con le quali si riducono al minimo i problemi di compatibilità e di installazione sui computer degli utilizzatori.

7.1   Architetture three-tier e pattern MVC

Le applicazioni Web si basano generalmente su una architettura a tre livelli (three-tier):

Tale architettura è schematizzata nella figura 7.1.

Figura 7.1.

figure/pagine-web-schema-3tier

Tra i tre componenti il dialogo avviene secondo il modello client-server, nel senso che l'interfaccia è cliente della logica applicativa che a sua volta è cliente della gestione dati persistenti.

Il motivo dell'adozione di una architettura con dei livelli distinti risiede essenzialmente nella possibilità di progettare, costruire, manutenere ciascuno dei moduli in modo indipendente dagli altri.

Non necessariamente i vari livelli devono appartenere a nodi di rete distinti; spesso anzi i due serventi, Web e RDBMS, sono installati sulla stessa macchina.

Generalmente è invece in esecuzione su nodi distinti il livello front-end a meno che non si effettuino prove di collegamento direttamente sulla macchina servente (identificata in tal caso come localhost) cosa che può avvenire generalmente nelle fasi di realizzazione o di prova dei programmi.

Quando si parla di livelli architetturali delle applicazioni Web si fa spesso riferimento anche allo schema (pattern) MVC (Model View Controller) proposto originariamente con il linguaggio Smalltalk e ora molto diffuso per lo sviluppo di interfacce grafiche in applicazioni orientate agli oggetti.

Il suo uso è presente in numerose tecnologie attuali, anche in ambito Web: nei framework basati su PHP (Symfony, Zend Framework), su Ruby (Ruby on Rails), su Python (Django), su Java (Swing, Struts), o su .NET di Microsoft.

Con l'appena citato termine framework (piano di lavoro) si intende una struttura di supporto per la progettazione e realizzazione del software basata su librerie usabili con uno o più linguaggi e corredate da strumenti di aiuto alla programmazione come editor avanzati e debugger.

I principali vantaggi dell'uso dei framework risiedono nella standardizzazione delle operazioni più comuni per lo sviluppo del software e nella possibilità di riutilizzare componenti già pronti senza doverli riprogettare e riscrivere ogni volta da zero.

Anche lo schema MVC comprende tre livelli con compiti distinti:

Lo schema non è in contrasto con quanto richiesto in una applicazione three-tier in quanto implica la separazione fra la logica applicativa che spetta al controller e al model, e l'interfaccia utente che è a carico della view.

In qualche caso si tende anzi a confondere lo schema MVC con le architetture a tre livelli; ciò non è corretto per almeno due motivi:

Figura 7.2.

figure/pagine-web-schema-mvc

Dal punto di vista storico, si può inoltre notare che l'architettura three-tier si presenta negli anni '90 nell'ambito delle applicazioni distribuite, con la separazione dei tre livelli su piattaforme distinte, mentre MVC nasce nel decennio precedente, presso lo Xerox PARC, inizialmente per applicazioni non distribuite.

Maggiori dettagli sulle architetture three-tier e, in generale, sulle applicazioni multi-livello, sono disponibili nel capitolo 10 dedicato alla programmazione distribuita.

Nei prossimi paragrafi esaminiamo più da vicino i metodi e gli strumenti utili alla gestione dei dati di un database tramite un'interfaccia Web.

7.2   Strumenti necessari per la gestione di basi di dati via Web

Gli strumenti scelti per trattare questo argomento e per produrre qualche semplice esempio fanno tutti parte del software libero.

Ovviamente questa non è l'unica scelta che poteva essere fatta, data la presenza sul mercato di molte alternative proprietarie anche tecnicamente molto valide; la preferenza per questa categoria di programmi è però ampiamente motivata da tutta una serie di considerazioni per approfondire le quali si consiglia di consultare i numerosi documenti, dedicati a tali argomenti, sul sito <http://www.linuxdidattica.org>.

Qui elenchiamo brevemente solo alcuni dei motivi più importanti per la scelta di programmi e strumenti liberi:

Gli strumenti cui si fa riferimento nel proseguo di questo paragrafo sono:

Anche se in questa sede non vengono forniti dettagli sul reperimento e l'installazione dei pacchetti software necessari (come al solito si rimanda ad appositi testi o alla documentazione presente anche in Internet o ai manuali in linea in ambiente GNU/Linux), forniamo un elenco degli stessi facendo riferimento alla piattaforma Ubuntu-7.04:

Supponiamo di avere installato con successo tutto il software sulla macchina su cui effettuare le prove e di avere quindi il servente Apache che risponde all'indirizzo http://localhost; dotato di tutto il necessario per l'esecuzione di script PHP5 e per l'interfacciamento a basi di dati MySQL e PostgreSQL.

Segnaliamo che è possibile lavorare con questi strumenti anche in ambiente MS-Windows (versioni superiori alle 9x o alla home) in quanto esistono pacchetti Apache, MySQL, PostgreSQL, PHP5 anche per questa piattaforma.

Inoltre esistono prodotti come EasyPHP o XAMPP installando i quali si hanno a disposizione in MS-Windows, già pronti all'uso: un servente Apache, un servente MySQL, un interprete PHP5 e l'applicazione phpmyadmin per la gestione via Web delle basi di dati, cui si accede collegandosi a http://localhost/mysql.

A proposito dei due RDBMS prescelti segnaliamo come il primo sia nettamente più utilizzato nell'ambito delle applicazioni di rete: molti siti di commercio elettronico si appoggiano infatti su archivi gestiti con MySQL.

Per sottolineare questo fatto facciamo notare che è stato addirittura coniato un acronimo: LAMP, le cui lettere indicano i componenti che sono alla base di molte applicazioni Web esistenti: Linux, Apache, MySQL, PHP.

Tra i motivi della grande diffusione di MySQL ci sono l'estrema semplicità di configurazione e gestione e le buone prestazioni specialmente per basi di dati di dimensioni non elevate (come quelle su cui si basano solitamente i negozi in rete).

Tali pregi hanno superato, per gli sviluppatori, i difetti che erano presenti almeno nelle versioni precedenti alla 5.0 e che consistono principalmente nella non completa aderenza allo standard SQL (Structured Query Language), nell'assenza di viste logiche, di trigger, di selezioni annidate.

Nel caso di PostgreSQL, tali difetti non erano presenti neanche nelle prime versioni, ma questo non gli ha permesso di godere delle preferenze degli addetti ai lavori (non è il primo caso, in ambito informatico, in cui un prodotto superiore dal punto di vista tecnologico ha meno successo di uno con caratteristiche inferiori).

In queste dispense, per eliminare ogni possibile discussione, si è scelto di considerare entrambi i prodotti.

Nei due RDBMS la gestione fisica dei dati non è banale e non prevede la memorizzazione degli stessi in su un unico file per ogni base di dati, come avviene invece per altri gestori di basi di dati (ad esempio Access di Microsoft o SQLite); quindi, a differenza che in questi ultimi, per fare salvataggi di sicurezza dei dati di un archivio, non è sufficiente salvare un file, ma occorre utilizzare gli appositi strumenti messi a disposizione dai serventi RDBMS.

La trattazione su MySQL e PostgreSQL è ovviamente parziale: vengono introdotti solo i comandi necessari alla gestione di piccole basi di dati in vista di una successiva interrogazione con il linguaggio PHP; si considerano note le nozioni relative alle basi di dati relazionali in generale, al loro progetto (passando per i modelli concettuale e logico), alla sintassi e alle regole del linguaggio SQL.

7.3   Basi di dati con MySQL

MySQL è un sistema di gestione di basi di dati relazionali multi-piattaforma distribuito da MySQL AB come software libero sotto licenza GPLv2 (General Public License version 2).

7.3.1   Attivazione del servente MySQL

Una volta installato MySQL con successo si può iniziare ad usarlo senza grosse difficoltà; la directory predefinita a partire dalla quale vengono salvati gli archivi è /var/lib/mysql e l'amministratore della base di dati è, almeno inizialmente, l'utente root (si rimanda al paragrafo 6.4.1 per quanto riguarda il modo in cui si possono acquisire i privilegi di tale utente in Ubuntu).

Prima di iniziare si deve attivare il demone mysqld con il comando (eseguito sempre con i diritti di root):

/etc/init.d/mysql start

Con comandi analoghi contenenti, invece di start, restart, stop, status si ottiene il riavvio, la fermata, lo stato corrente del demone.

Si tenga comunque presente che, in caso di corretta installazione del pacchetto, il demone viene inserito tra quelli attivati automaticamente all'accensione della macchina grazie alla presenza del file /etc/rc2.d/S19mysql che è un collegamento simbolico a /etc/init.d/mysql; in mancanza si può provvedere a definire tale collegamento con i comandi:

cd /etc/rc2.d

ln -s ../init.d/mysql .

Il servente MySQL risponde alle richieste sulla porta 3306; questa è l'impostazione predefinita modificabile intervenendo nel file di configurazione /etc/mysql/my.cnf cambiando la riga:

port           = 3306

7.3.2   Dialogo con il servente MySQL con mysql

Per verificare il funzionamento del servente MySQL possiamo collegarci ad esso come utente root eseguendo mysql che è il programma di interfaccia in modalità testuale.

Il comando prevede numerose opzioni, qui elenchiamo solo le più importanti:

Per avere tutte le informazioni sull'uso di questo comando si può consultare il manuale in linea eseguendo:

man mysql

Vediamo un esempio di comando per il collegamento a un servente MySQL:

mysql -h localhost -u root -p mysql

si riceve la richiesta di inserire la password per l'utente root e, se non ci sono errori, ci si collega alla base di dati mysql (quella che contiene tutte le informazioni amministrative del servente).

La stringa di invito dei comandi (prompt dei comandi) di MySQL ha il seguente aspetto:

mysql> 

Per chiudere la connessione occorre digitare il comando:

mysql> \q

Si può anche eseguire mysql senza alcuna opzione e senza indicazione della base di dati da utilizzare; in questo caso si effettua un collegamento alla macchina locale senza socket di rete ma usando il cosiddetto «Unix-domain socket», l'utente usato ha stesso nome di quello correntemente collegato al sistema GNU/Linux e la base di dati da utilizzare è indefinita.

Per cambiare la base di dati corrente si esegue il comando (l'utente deve avere i permessi di uso su di essa come mostrato più avanti):

mysql> use nome_basi_dati

Per creare una base di dati si esegue:

mysql> CREATE DATABASE nome_base_dati;

e per cancellarla:

mysql> DROP DATABASE nome_base_dati;

Lavorare come utente root è quasi sempre inopportuno per motivi di sicurezza e questo vale anche nel caso di MySQL; vediamo allora come si crea un utente, chiamato dba, «normale» per GNU/Linux, ma dotato di tutti i privilegi necessari per essere l'amministratore del RDBMS.

Prima di tutto creiamo (da utente root) l'utente nel sistema GNU/Linux, supponendo ovviamente che non esista già:

adduser dba

inserendo la password, confermandola e rispondendo alle altre richieste di informazioni sull'utente che sono comunque non essenziali.

Poi colleghiamoci a MySQL e definiamo anche qui il nuovo utente:

mysql> CREATE USER dba;

quindi assegniamogli i privilegi:

mysql> GRANT ALL PRIVILEGES ON *.* TO dba@"%" \
  \ IDENTIFIED BY 'parola_segreta' WITH GRANT OPTION;

spieghiamo brevemente le parti salienti del comando:

A questo punto si può chiudere il collegamento come utente root e accedere da una console al sistema GNU/Linux come utente dba, oppure da utente root eseguire il cambio di identità con il comando:

su dba

Quindi ci si collega a MySQL:

mysql -h localhost -u dba -p mysql

Notiamo che in questo caso l'uso di «-u dba» è superfluo ma non provoca alcun errore.

Si possono creare altri utenti, altre basi di dati e assegnare i relativi privilegi; ad esempio per assegnarli all'utente fulvio per la base di dati prova:

mysql> GRANT ALL PRIVILEGES ON prova.* TO fulvio@"%"\
  \IDENTIFIED BY 'parola_segreta';

Per togliere questi privilegi:

mysql> REVOKE ALL PRIVILEGES ON prova.* FROM fulvio@"%";

7.3.3   Comandi utili per usare MySQL

Elenchiamo una serie di informazioni utili all'amministrazione e all'uso di un servente MySQL, sottolineando come esse siano solo una piccola parte di quelle necessarie e come presuppongano, in qualche caso, la conoscenza delle regole del linguaggio SQL:

Si noti che le parole riservate del linguaggio SQL si scrivono in maiuscolo anche se vengono riconosciute pure in minuscolo e che i comandi SQL, a differenza di quelli di mysql, devono sempre terminare con «;».

A proposito della possibilità di connettersi a MySQL da remoto, si osservi che essa non è indispensabile nel contesto delle applicazioni Web che si interfacciano con una base di dati; infatti, siccome il servente MySQL è quasi sempre residente sulla stessa macchina del servente Web, per quest'ultimo è sufficiente connettersi «localmente» a MySQL.

7.3.4   Creazione di una tabella in MySQL per le prove

Creiamo, come utente dba, con le modalità mostrate in precedenza, l'utente fulvio per MySQL e una base di dati con stesso nome assegnando gli opportuni privilegi.

Colleghiamoci poi a MySQL come utente fulvio e creiamo in tale base di dati una tabella di nome rubrica per le successive prove di gestione via Web con PHP; la tabella contiene alcuni indirizzi e numeri telefonici e ha una chiave costituita da codice auto-incrementante:

mysql -h localhost -u fulvio -p fulvio

mysql> CREATE TABLE rubrica(codice INT AUTO_INCREMENT, cognome\
  \VARCHAR(20), nome VARCHAR(20),

    -> indirizzo VARCHAR(40), telefono VARCHAR(15), data_nas DATE,\
  \email VARCHAR(30), PRIMARY KEY (codice));

come si vede il comando può essere spezzato su più righe; le righe successive alla prima sono righe di continuazione come evidenziato anche dalla stringa di invito che cambia aspetto.

Il comando viene considerato concluso quando si incontra il simbolo «;».

Inseriamo alcune registrazioni all'interno della tabella con i comandi:

mysql> INSERT INTO rubrica (cognome, nome, indirizzo, telefono,\
  \data_nas, email)

    -> VALUES('Paperino', 'Paolino', 'via Roma 1 Paperopoli',

    -> '010011234567','1980-01-01','paperino.paolino@maxplanck.it');

mysql> INSERT INTO rubrica (cognome, nome, indirizzo, telefono,\
  \data_nas, email)

    -> VALUES('Rossi', 'Mario', 'via Roma 34 Treviso',

    -> '04220101010','1981-01-01','rossi.mario@maxplanck.it');

Si noti come i valori del campo codice non vengano inseriti perché, essendo esso auto-incrementante, viene opportunamente valorizzato dal servente MySQL.

Verifichiamo i dati inseriti con il comando:

mysql> SELECT * FROM rubrica;

che dovrebbe dare il risultato mostrato in figura 7.6.

Figura 7.6.

figure/pagine-web-esemysql

Le stesse operazioni possono essere fatte mediante il più amichevole programma phpmyadmin utilizzabile, dopo l'installazione e la configurazione per le quali si rimanda alla documentazione del programma stesso, collegandosi all'URI: http://localhost/phpmyadmin.

7.4   Basi di dati con PostgreSQL

PostgreSQL è un sistema di gestione basi di dati relazionali multi-piattaforma molto avanzato e quasi del tutto rispondente allo standard SQL; è stato sviluppato originariamente dal dipartimento di informatica dell'Università di Berkeley in California ed è distribuito come software libero con una licenza speciale.

7.4.1   Attivazione del servente PostgreSQL

Una volta installato PostgreSQL con successo si può iniziare ad usarlo senza grosse difficoltà; la directory predefinita a partire dalla quale vengono salvati gli archivi è /var/lib/postgresql/8.2/main/ e l'amministratore della base di dati è l'utente postgres al quale è consigliabile assegnare una password, operazione che deve essere svolta dall'utente root di Linux (si rimanda al paragrafo 6.4.1 per quanto riguarda il modo in cui si possono acquisire i privilegi di tale utente in Ubuntu).

Prima di iniziare si deve attivare il demone postgres con il comando (eseguito sempre con i diritti dell'utente root):

/etc/init.d/postgresql-8.2 start

Con comandi analoghi contenenti, invece di start, restart, stop, status si ottiene il riavvio, la fermata, lo stato corrente del demone.

Si tenga comunque presente che, in caso di corretta installazione del pacchetto, il demone viene inserito tra quelli attivati automaticamente all'accensione della macchina grazie alla presenza del file /etc/rc2.d/S19postgresql-8.2 che è un collegamento simbolico a /etc/init.d/postgresql-8.2; in mancanza si può provvedere a definire tale collegamento con i comandi:

cd /etc/rc2.d

ln -s ../init.d/postgresql-8.2 .

Il servente PostgreSQL risponde alle richieste sulla porta 5432; questa è l'impostazione predefinita modificabile intervenendo nel file di configurazione /etc/postgresql/8.2/main/postgresql.conf cambiando la riga:

port = 5432

La prima volta che viene eseguito il programma di amministrazione delle basi di dati, vengono creati i file fondamentali per la loro gestione.

Per verificare il buon funzionamento del servente PostgreSQL creiamo (da utente root) un utente nel sistema GNU/Linux con nome, ad esempio, fulvio, supponendo ovviamente che non esista già:

adduser fulvio

Poi ci si connette su una console come utente postgres, oppure da utente root si esegue il cambio di identità con il comando:

su postgres

e si crea l'utente fulvio anche per PostgreSQL:

createuser fulvio

Il sistema pone a questo punto delle domande relative al nuovo utente: è opportuno rispondere in modo che non sia un superuser e che sia abilitato a creare basi di dati ma non altri utenti.

Se si deve eliminare un utente PostgreSQL il comando (da eseguire sempre come utente postgres) è:

dropuser nome_utente

7.4.2   Dialogo con il servente PostgreSQL con psql

Una volta creato l'utente desiderato (fulvio) possiamo accreditarci con le sue credenziali ed eseguire psql che è il programma di interfaccia, in modalità testuale, verso il servente PostgreSQL.

Il comando prevede numerose opzioni, qui elenchiamo solo le più importanti:

Per avere tutte le informazioni sull'uso di questo comando si può consultare il manuale in linea eseguendo:

man psql

Vediamo un esempio di comando per il collegamento a un servente PostgreSQL:

psql -h localhost -U fulvio -W template1

si riceve la richiesta di inserire la password per l'utente fulvio e, se non ci sono errori, ci si collega alla base di dati template1 (una di quelle di esempio già presenti sul servente).

La stringa di invito dei comandi (prompt dei comandi) di PostgreSQL contiene il nome della base di dati corrente e ha il seguente aspetto:

template1=> 

Per chiudere la connessione occorre digitare il comando:

template1=> \q

Si può anche eseguire psql senza alcuna opzione e senza indicazione della base di dati da utilizzare; in questo caso si effettua un collegamento alla macchina locale senza socket di rete ma usando il cosiddetto «Unix-domain socket», l'utente usato ha stesso nome di quello correntemente collegato al sistema GNU/Linux e la base di dati da utilizzare ha lo stesso nome dell'utente.

Questa base di dati deve ovviamente esistere e l'utente deve potersi collegare senza inserire password, altrimenti il tentativo di collegamento non ha successo.

Un utente può eventualmente creare la propria base di dati predefinita (o qualsiasi altra) nel modo seguente:

createdb nome_base_dati

Per cancellare una base di dati il comando è invece:

dropdb nome_base_dati

7.4.3   Comandi utili per usare PostgreSQL

Elenchiamo a questo punto una serie di informazioni utili all'amministrazione e all'uso di un servente PostgreSQL, sottolineando, ancora una volta, come esse siano solo una piccola parte di quelle necessarie e come presuppongano la conoscenza delle regole del linguaggio SQL:

Si può notare che, ancora una volta, le parole riservate del linguaggio SQL si scrivono in maiuscolo, anche se non è obbligatorio, e che i comandi SQL, a differenza di quelli di psql, devono sempre terminare con «;».

Anche relativamente alla possibilità di connettersi a PostgreSQL da remoto vale la stessa considerazione fatta in precedenza per MySQL: non è indispensabile nel contesto delle applicazioni Web che si interfacciano con una base di dati; infatti, siccome il servente PostgreSQL è quasi sempre residente sulla stessa macchina del servente Web, per quest'ultimo è sufficiente connettersi «localmente» a PostgreSQL.

7.4.4   Creazione di una tabella in PostgreSQL per le prove

Creiamo, come utente fulvio nella base di dati omonima, una tabella di nome rubrica per le successive prove di gestione via Web con PHP; la tabella contiene alcuni indirizzi e numeri telefonici e ha una chiave costituita da codice auto-incrementante:

fulvio=> CREATE TABLE rubrica(codice serial, cognome VARCHAR(20),\
  \nome VARCHAR(20),

fulvio(> indirizzo VARCHAR(40), telefono VARCHAR(15),\
  \data_nas DATE, email VARCHAR(30));

come si vede il comando può essere spezzato su più righe; le righe successive alla prima sono righe di continuazione come evidenziato anche dalla stringa di invito che cambia aspetto.

Il comando viene considerato concluso quando si incontra il simbolo «;».

Inseriamo alcune registrazioni all'interno della tabella con i comandi:

fulvio=> INSERT INTO rubrica (cognome, nome, indirizzo,\
  \delefono, data_nas, email)

fulvio-> VALUES('Paperino', 'Paolino', 'via Roma 1 Paperopoli',

fulvio(> '010011234567','1980-01-01','paperino.paolino@maxplanck.it');

fulvio=> INSERT INTO rubrica (cognome, nome, indirizzo,\
  \telefono, data_nas, email)

fulvio-> VALUES('Rossi', 'Mario', 'via Roma 34 Treviso',

fulvio(> '04220101010','1981-01-01','rossi.mario@maxplanck.it');

Si noti come i valori del campo codice non vengano inseriti perché, essendo esso auto-incrementante, viene opportunamente valorizzato dal servente PostgreSQL.

Verifichiamo i dati inseriti con il comando:

fulvio=> SELECT * FROM rubrica;

che dovrebbe dare il risultato mostrato in figura 7.12.

Figura 7.12.

figure/pagine-web-esepsql

Le stesse operazioni possono essere fatte mediante il più amichevole programma phppgadmin utilizzabile, dopo l'installazione e la configurazione per le quali si rimanda alla documentazione del programma stesso, collegandosi all'URI: http://localhost/phppgadmin.

7.5   Introduzione al linguaggio PHP

Il PHP (inizialmente Personal Home Pages, adesso PHP Hypertext Preprocessor) nasce nel 1995 per mano di Rasmus Lerdorf allo scopo di semplificare la gestione di alcune pagine HTML.

La primissima versione era originata dal alcuni script Perl ma successivamente Lerdorf ne scrisse una versione in linguaggio c.

Le tappe più importanti della successiva evoluzione sono state:

Si tratta di un linguaggio interpretato, multi-piattaforma, che viene utilizzato principalmente per dotare di capacità elaborativa i documenti HTML (anche se esiste la possibilità di eseguire script da linea di comando grazie al pacchetto php5-cli).

Le istruzioni PHP sono incorporate all'interno dei sorgenti HTML e vengono eseguite dal lato servente grazie alla presenza del motore Zend.

Il linguaggio si presta particolarmente per la scrittura di applicazioni Web per l'amministrazione di basi di dati; i gestori di basi di dati supportati sono moltissimi e fra essi troviamo MySQL, PostgreSQL, Oracle, Sybase, Informix.

Una trattazione completa del linguaggio PHP non rientra negli scopi di queste dispense; ci limitiamo a illustrare le sue caratteristiche di base, aiutandoci con degli esempi, per essere poi in grado di realizzare dei banali interfacciamenti a basi di dati MySQL e PostgreSQL.

Qualche dettaglio in più viene fornito sulla gestione delle sessioni e sulla prevenzione della «SQL injection» che sono argomenti molto importanti ma non sempre considerati in modo adeguato nei testi, nei manuali e in genere nella documentazione riguardante l'uso di PHP.

7.6   Cenni alla sintassi del linguaggio PHP

I comandi PHP si inseriscono all'interno di un documento HTML in qualunque posizione ma si tengono separati dal resto del sorgente racchiudendoli tra le stringhe ;<?php e ?>.

I documenti contenenti comandi PHP devono avere estensione .php e non .html; il viceversa non è vero: si possono cioè scrivere documenti privi di istruzioni PHP il cui nome ha estensione .php senza temere alcun malfunzionamento (semplicemente, malgrado l'estensione, essi non vengono elaborati dal motore Zend di PHP).

Vediamo subito un esempio che permette anche di verificare il buon funzionamento del PHP sulla nostra macchina: definiamo nella directory radice del servente Web (solitamente /var/www/) un file chiamato phpinfo.php contenente questa unica riga:

<?php phpinfo(); ?>

Se si apre il documento collegandosi a http://localhost/phpinfo.php e se tutto funziona correttamente, si ottiene una risposta, mostrata parzialmente nella figura 7.14, contenente numerose informazioni riguardanti il PHP, il servente Web ed altro ancora.

Figura 7.14.

figure/pagine-web-php-ese0001

Il linguaggio PHP può essere utilizzato anche indipendentemente dalla connessione a basi di dati, come evidenziato dal seguente esempio in cui vediamo un documento HTML per con un modulo per l'immissione di dati, arricchito con alcuni comandi PHP; i dati di input vengono passati ad un altro file e trattati ancora con istruzioni PHP.

Ecco il sorgente del primo documento:

      1 <html> 
      2 <head>
      3 <title>Modulo 1 con php</title>
      4 </head> 
      5 <body> 
      6 <h2>Modulo gestito con PHP</h2> 
      7 <p/> 
      8 Sei collegato al servente Web: 
      9 <b><?php 
     10 //echo $SERVER_NAME; 
     11 echo $_SERVER['SERVER_NAME']
     12 ?></b> 
     13 <p/>&nbsp;all'indirizzo: 
     14 <b><?php echo $_SERVER['SERVER_ADDR']; ?></b> 
     15 <p/>Servente fornito da: 
     16 <b><?php echo $_SERVER['SERVER_SOFTWARE']; ?></b> 
     17 <p/>Sei collegato dall'indirizzo:
     18 <b><?php echo $_SERVER['REMOTE_ADDR']; ?></b> 
     19 <p/><br/>
     20 Inserisci i tuoi dati anagrafici 
     21 <p/>
     22 <form action="./modulo1_ris.php" method="post"> 
     23 <p/>Cognome: <input type="text" name="cognome" size="20" maxlength="20"/>
     24 <p/>Nome: <input type="text" name="nome" size="20" maxlength="20"&/gt; 
     25 <p/>Eta': <input type="radio" name="eta" value="1-18"/>1-18 
     26 &nbsp;&nbsp;<input type="radio" name="eta" value="19-40"/>19-40
     27 &nbsp;&nbsp;<input type="radio" name="eta" value="41-65"/>41-65 
     28 &nbsp;&nbsp;<input type="radio" name="eta" value="66-99"/>66-99 
     29 <p/>E-mail: <input type="text" name="email" size="30" maxlength="30"/> 
     30 <p/><input type="reset" value="Reset"/> 
     31 <input type="submit" value="Invia"/> 
     32 </form>
     33 </body> 
     34 </html> 

Come al solito i numeri di riga sono stati aggiunti per facilitare la spiegazione delle varie parti del sorgente; prima però di commentarlo in dettaglio forniamo alcune informazioni sulla sintassi dei comandi PHP che ha grandi somiglianze con quella del linguaggio c:

Altre caratteristiche il linguaggio PHP le ha invece ereditate dal Perl:

Notiamo inoltre che, come il Perl, anche PHP è un linguaggio «debolmente tipizzato»; ciò significa che non occorre dichiarare il tipo delle variabili prima di utilizzarle e che le conversioni di tipo eventualmente necessarie vengono fatte, quando possibile, automaticamente a tempo di esecuzione.

Vediamo adesso qualche dettaglio riguardante il documento precedente:

L'aspetto del documento è quello mostrato nella figura 7.18.

Figura 7.18.

figure/pagine-web-php-ese0002

Molto importante è notare che, se si visualizza il sorgente della pagina con l'apposita funzione disponibile nel programma di navigazione, il codice PHP non risulta visibile; esso infatti non viene inviato alla macchina cliente visto che non è compito di quest'ultima l'elaborazione di quelle istruzioni.

Si noti che i dati del modulo vengono inviati a modulo1-ris.php che risiede nella stessa directory del documento di partenza; qui infatti non è obbligatorio definire una directory apposita, come la /cgi-bin usata per i CGI, dove memorizzare gli script PHP.

Il file per l'elaborazione dei dati e la creazione della risposta non è altro che un ulteriore pagina HTML corredato da istruzioni PHP; il suo contenuto è:

      1 <html>
      2 <head>
      3 <title>Modulo 1 risposta con php</title>
      4 </head>
      5 <body>
      6 <h2>Risposta gestita con PHP</h2>
      7 <p/>
      8 Sei collegato al servente Web:
      9 <b><?php echo $_SERVER['SERVER_NAME'] ?></b>
     10 &nbsp;all'indirizzo:
     11 <b><?php echo $_SERVER['SERVER_ADDR']; ?></b>
     12 <p/>
     13 Servente fornito da:
     14 <b><?php echo $_SERVER['SERVER_SOFTWARE']; ?></b>
     15 <p/>
     16 Sei collegato dall'indirizzo: 
     17 <b><?php echo $_SERVER['REMOTE_ADDR']; ?></b>
     18 <p/><br/>
     19 Hai inserito i seguenti dati:
     20 <!-- <p/>Cognome: <?php echo $cognome; ?> -->
     21 <?php 
     22 if ($_SERVER['REQUEST_METHOD']=="post") {
     23    $cognome=$_POST['cognome'];
     24    $nome=$_POST['nome'];
     25    $eta=$_POST['eta'];
     26    $email=$_POST['email'];
     27 }
     28 else {   
     29    $cognome=$_GET['cognome'];
     30    $nome=$_GET['nome'];
     31    $eta=$_GET['eta'];
     32    $email=$_GET['email'];
     33 }
     34 ?>
     35 <p/>Cognome:<b> <?php print("$cognome"); ?>
     36 <!-- <p/>Cognome:<b> <?php print('$cognome'); ?> -->
     37 <?php print("</b><p/>Nome:<b> $nome"); ?>
     38 </b><p/>Eta':<b> <?php echo $eta; ?>
     39 </b><p/>E-mail:<b> <?php echo $email; ?>
     40 </b><p/><br/>
     41 <?php
     42 $var1 = strstr($_SERVER['HTTP_USER_AGENT'], "Mozilla");
     43 if (!strstr($var1, "compatible") and  ($var1)) {
     44    echo "Stai usando il browser Mozilla o uno da esso derivato"; }
     45 elseif (strstr($var1, "MSIE")) {
     46       echo "Stai usando il browser Internet Explorer"; }
     47    else {
     48       echo "Stai usando un browser diverso da Explorer e Mozilla"; }
     49 ?>
     50 </body>
     51 </html>

La prima parte ricalca quella del primo documento; successivamente vengono visualizzati i dati inviati dal modulo.

Notiamo subito che, a differenza di quanto avviene con i CGI, non è necessario far precedere il contenuto della risposta dall'intestazione HTTP e dalla riga vuota in quanto questo a questo provvede automaticamente il motore PHP nel momento in cui viene incontrata la prima istruzione di output (echo o print()).

Commentiamo più in dettaglio le parti più significative del sorgente:

L'aspetto della risposta è quello mostrato nella figura 7.20.

Figura 7.20.

figure/pagine-web-php-ese0002r

Se il modulo di provenienza dei dati prevede la possibilità di più valori per uno stesso campo (caso di un controllo <select> con attributo multiple), occorre attribuire al campo un nome terminante con «[]» in modo che in PHP siano poi disponibili i valori immessi all'interno di un vettore; se ad esempio il nome nel modulo è scelte[] in PHP abbiamo che $_REQUEST['scelte'] è un vettore e non un singolo valore.

7.7   Validazione dell'input con PHP (cenno alle espressioni regolari)

Le istruzioni PHP possono essere anche utilizzate per controllare la correttezza dei dati inseriti in un modulo.

In certi casi però controlli di questo tipo vengono svolti direttamente sulla macchina cliente con codice eseguito su quest'ultima usando ad esempio JavaScript.

Questa soluzione presenta l'indubbio vantaggio di far diminuire il carico di lavoro del servente ma non è sempre praticabile: se ad esempio si deve controllare la presenza di un codice prodotto in un archivio residente sulla macchina remota, non si può fare a meno di utilizzare un modulo di elaborazione che venga eseguito su quest'ultima e cioè lato servente.

Inoltre l'utente che sta usando la procedura potrebbe aver disabilitato l'esecuzione di codice JavaScript sul suo programma di navigazione, rendendo così inefficaci i controlli inseriti sul lato cliente.

Facendo riferimento all'esempio del paragrafo precedente, supponiamo di voler controllare che il campo denominato email sia non vuoto e sia un indirizzo di posta elettronica scritto correttamente; il controllo viene fatto usando istruzioni PHP da aggiungere ovviamente al file modulo1_ris.php.

Vengono mostrate le singole istruzioni da utilizzare lasciando al lettore l'individuazione delle opportune modifiche alla logica di elaborazione necessarie al loro inserimento nel file in oggetto.

Il controllo sulla valorizzazione del campo email può essere fatto con la funzione strlen() che restituisce la lunghezza in byte dell'argomento:

if (strlen($email)==0) {
   echo "<p/>Il campo E-mail è obbligatorio; torna al modulo e correggi!"
}
else {
   ........ 

Per quanto riguarda invece la correttezza dell'indirizzo di posta possiamo supporre che esso debba avere la seguente forma:

Per controllare che la variabile sia aderente a questo formato (nel gergo tecnico si dice che si ha un match tra essa e la sequenza di cui sopra, chiamata pattern) si utilizza uno strumento molto efficace, proposto originariamente per il linguaggio Perl, ma ormai esportato in molti altri linguaggi: le espressioni regolari (regexp).

Le espressioni regolari sono tanto potenti quanto ostiche, specialmente per i non esperti, anche a causa della loro sintassi davvero succinta e per niente amichevole.

Una loro trattazione, anche solo superficiale, richiederebbe probabilmente una dispensa apposita; ci limitiamo quindi a pochi esempi, fra i quali includiamo la soluzione da adottare per il controllo del nostro indirizzo di posta, confidando che questo piccolo «assaggio» possa spingere chi necessita di approfondimenti a consultare la vasta mole di documentazione reperibile sull'argomento.

La funzione da usare per le espressioni regolari è preg_match() che, nella forma più comune ha due argomenti: il pattern e la stringa da controllare; se la stringa soddisfa il pattern la funzione ritorna vero, altrimenti ritorna falso.

Ecco dunque l'istruzione per effettuare il controllo di correttezza sull'indirizzo di posta elettronica:

if (!preg_match("/^[^@ ]{2,}@[^@ \.]+\.[^@ \.]{2,}$/",$email)) {
   echo "<p/>Il campo E-mail non è valido; torna al modulo e correggi!"
}
else {
   ......

Nella tabella seguente cerchiamo di fornire una spiegazione del pattern scandendolo da sinistra a destra e sottolineando ancora una volta che la mole di nozioni trascurate nella spiegazione è nettamente superiore a quella delle nozioni presenti:

Simbolo nel pattern Spiegazione
^ inizio della stringa, essa deve iniziare come specificato nel proseguo del pattern;
[^@ ] un carattere diverso da «@» e da spazio; le parentesi quadrate indicherebbero la scelta di uno qualsiasi dei caratteri in esse contenuti e il carattere «^» indica la negazione;
{2,} il carattere o il gruppo di caratteri indicati in precedenza devono essere ripetuti due o più volte;
@ deve esserci il carattere «@»;
[^@ \.] un carattere diverso da «@», da spazio e da punto; il carattere punto viene indicato mascherandolo con il carattere di escape «\» perché è un carattere speciale per i pattern (significherebbe «qualsiasi carattere»);
+ quanto appena indicato deve essere ripetuto una o più volte (equivale a {1,});
\. deve esserci il carattere punto;
[^@ \.] un carattere diverso da «@», da spazio e da punto;
{2,} quanto appena indicato deve essere ripetuto due o più volte;
$ fine della stringa, essa deve finire secondo quanto appena indicato nel pattern.

Come ulteriore esempio supponiamo di voler controllare la validità del campo cognome del modulo del paragrafo precedente; la regola che deve essere soddisfatta è che siano presenti solo caratteri alfabetici minuscoli (eccetto la prima maiuscola), il carattere spazio e l'apostrofo (semplifichiamo leggermente la questione imponendo che nei cognomi composti sia maiuscolo solo il primo carattere).

$cognome=stripslashes($cognome);
if (!preg_match("/^[A-Z]{1}[ a-z']*$/",$cognome)) {
   echo "<p/>Il campo Cognome non è valido; torna al modulo e correggi!"
}
else {
   ......

In questo esempio vediamo anche l'uso della funzione stripslashes() che permette di eliminare il carattere «\» aggiunto automaticamente nell'input per mascherare eventuali «'».

Esiste anche la funzione addslahes() che permette invece di aggiungere il carattere «\» prima di caratteri che possono causare problemi se presenti in campi da inserire in una base di dati; si tratta dei caratteri «'», «"» e «\».

Anche in questo caso utilizziamo una tabella per la spiegazione del pattern per il controllo del cognome:

Simbolo nel pattern Spiegazione
^ inizio della stringa;
[A-Z] un carattere qualsiasi compreso tra «A» e «Z», quindi maiuscolo;
{1} il carattere o il gruppo di caratteri indicati in precedenza devono essere presenti solo una volta;
[ a-z'] un carattere spazio o alfabetico minuscolo o apostrofo;
* quanto appena indicato deve essere ripetuto zero o più volte;
$ fine della stringa.

Infine ipotizziamo di dover controllare un numero di telefono (contenuto nella variabile $telefono) imponendo che debba essere costituito da un insieme di due, tre o quattro cifre, seguite dal segno meno e da almeno altre 4 cifre.

if (!preg_match("/^[0-9]{2,4}-[0-9]{4,}$/",$telefono)) {
   echo "<p/>Il campo Telefono non è valido; torna al modulo e correggi!"
}
else {
   ......

Ed ecco la tabella per la spiegazione del pattern:

Simbolo nel pattern Spiegazione
^ inizio della stringa;
[0-9] un carattere qualsiasi compreso tra «0» e «9», quindi una cifra;
{2,4} il carattere o il gruppo di caratteri indicati in precedenza devono essere presenti da due a quattro volte;
- deve esserci il segno meno;
[0-9] una cifra;
{4,} quanto indicato in precedenza deve essere presente quattro o più volte;
$ fine della stringa.

7.8   Le sessioni in PHP

In questo paragrafo prendiamo in esame un argomento molto interessante: la gestione delle sessioni in PHP; prima di entrare nei dettagli e di illustrare qualche esempio è però opportuno introdurre il concetto di stato di una applicazione e le motivazioni per cui le sessioni sono importanti per la gestione dello stato nelle applicazioni Web.

7.8.1   Il concetto di stato di un applicazione

Quando si utilizza una normale applicazione su un singolo sistema di elaborazione (quindi senza coinvolgere la rete come accade invece per le applicazioni Web) non si riesce a cogliere immediatamente l'esistenza di due entità al suo interno:

In molti casi questi due componenti appaiono indistinti, ma ogni volta che l'utente interagisce con l'interfaccia (ad esempio inserendo dei dati o effettuando delle scelte su un menu) essa comunica con la logica di programmazione la quale svolge certe elaborazioni ed eventualmente fornisce una risposta ancora all'interfaccia utente.

Possiamo anche dire che ogni azione svolta con l'interfaccia utente provoca un cambiamento nello «stato» dell'applicazione, cioè alla condizione in cui si trovano, istante per istante, i dati e le variabili dell'applicazione stessa.

Questo cambiamento di stato può essere provocato anche da altri fattori indipendenti dalle azioni dell'utente e previsti direttamente nella logica di programmazione.

Il punto interessante è però il seguente: mentre nelle applicazioni tradizionali la comunicazione tra interfaccia e logica di programmazione avviene con continuità e quindi la gestione dei cambiamenti di stato non è problematica e talvolta non viene neppure affrontata in modo esplicito, nelle applicazioni Web siamo costretti ad affrontare il problema in quanto il protocollo HTTP è «privo di stato».

Questo significa che non esiste alcun meccanismo automatico che tenga traccia dei cambiamenti di stato degli oggetti dell'interfaccia utente (i moduli visualizzati con il browser sulle macchine clienti) e li comunichi alla logica di programmazione (ad esempio script PHP eseguiti sul servente) e viceversa.

Nelle applicazioni Web la comunicazione tra interfaccia e logica di programmazione non è automatica ma guidata dall'utente e talvolta può essere anche molto lenta.

Inoltre uno script della logica di programmazione (supponiamo scritto in PHP) viene attivato solo al momento della richiesta dell'utente e dell'invio dei dati e quando termina vengono persi i valori di tutti i dati e di tutte le variabili usate nell'elaborazione; anche se l'utente ha l'impressione che l'applicazione sia ancora attiva (perché vede comparire sul suo browser una qualche risposta) in effetti nessun programma è in quel momento in esecuzione ed una nuova attivazione dello script non può avere a disposizione i valori dei dati dell'elaborazione precedente.

Questo naturalmente avviene a meno che non si individui un meccanismo che permetta di mantenere lo stato nell'ambito di una applicazione che usa il protocollo HTTP facendo in modo che tutti i dati dell'applicazione (o almeno quelli che interessano) siano persistenti.

Si tratta quindi di trovare il modo di attivare e gestire una «sessione di lavoro» che consiste in tutte le interazioni che l'utente svolge dal momento in cui si collega la prima volta all'applicazione residente sul servente (eventualmente accreditandosi), al momento in cui chiude il collegamento oppure scade un contatore di tempo di inattività.

7.8.2   Mantenere lo stato nelle applicazioni con PHP

Esistono vari metodi per mantenere lo stato usando il linguaggio PHP; illustriamoli brevemente eccetto quello considerato migliore (sessioni native in PHP) che viene approfondito maggiormente:

7.8.3   Sessioni native in PHP

L'idea fondamentale che sta alla base della gestione delle sessioni in PHP è quella di memorizzare i dati, che devono rimanere persistenti nell'ambito di queste ultime, sul servente; in questo modo si elimina la necessità di comunicare i dati stessi al cliente con i conseguenti problemi di riservatezza o di ingombro.

Al cliente viene comunque assegnata una chiave chiamata «identificatore di sessione» che gli permette di identificarsi univocamente per una certa sessione e di vedersi associare i relativi dati persistenti.

In PHP la chiave di sessione si chiama SID (Session Identifier) ed è un numero intero associato ad una e una sola sessione.

Schematizzando al massimo la gestione di una sessione nativa in PHP può quindi essere così riassunta:

La figura 7.28 rappresenta quanto appena illustrato.

Figura 7.28.

figure/pagine-web-schema-sessioni

Le funzioni da usare per gestire le sessioni in PHP sono le seguenti:

Si tenga presente che una sessione viene avviata automaticamente al momento che si esegue per la prima volta una session_register() anche in mancanza di una session_start().

Si osservi anche che la funzione session_start() deve essere invocata in ogni pagina in cui si vuole fare riferimento a quella sessione.

Le istruzioni che provocano l'avvio di una sessione devono essere posizionate in un punto dello script PHP che non interferisca con l'invio automatico di intestazioni HTTP e quindi prima di qualsiasi istruzione PHP che emetta un output e anche di qualsiasi contenuto HTML scritto esternamente al codice PHP; la cosa migliore da fare è allora definire un blocco PHP apposito proprio all'inizio del file.

In certi casi (ad esempio quando più applicazioni sono ospitate in un unico servente) può essere importante che il file con i dati di sessione venga salvato in una posizione separata per ogni applicazione; in questo modo si può evitare il rischio che altri script PHP accedano ai dati di un'altra sessione.

A tal fine si deve usare la direttiva ini_set() prima di session_start() nel modo seguente:

    ini_set("session.save_path", "percorso_in_cui_salvare");
    

Concludiamo questa panoramica sulle sessioni in PHP con un esempio in cui gestiamo un contatore di accessi ad un sito Web (o, più in piccolo, ad una pagina).

Il valore del contatore viene memorizzato in un file, ovviamente sul servente (in questo modo accenniamo anche all'uso dei file in PHP) e letto e incrementato ad ogni nuova richiesta di scaricamento della pagina.

Se questo meccanismo fosse gestito senza uso di sessioni avremmo un inconveniente molto grave: ogni volta che la pagina viene ri-visitata senza essere usciti dalla sessione di lavoro (ad esempio con un semplice «rinfresco» oppure tornando indietro dopo avere seguito un collegamento che si origina da essa) otterremmo un incremento del contatore, cosa evidentemente non corretta (a meno che non vogliamo «gonfiare» i dati di accesso al nostro sito).

Con l'uso delle sessioni, invece, l'incremento del contatore può essere fatto solo in presenza di una sessione appena avviata e non nelle visite successive nell'ambito della stessa sessione.

Vediamo dunque il listato del file, chiamato conta_sisess.php, che gestisce il contatore delle visite correttamente:

      1 <?php
      2 session_start();
      3 session_register('sessione');
      4 $nomefile="conta.txt";
      5 if (!file_exists($nomefile)) {
      6    $fp=fopen($nomefile,"w");
      7    fclose($fp);
      8 }
      9 $fp=fopen($nomefile,"r");
     10 $num=fread($fp,4);
     11 fclose($fp);
     12 $sess=$_SESSION['sessione'];
     13 $sess++;
     14 $_SESSION['sessione']=$sess;
     15 if ($sess == 1)
     16 {
     17    $fp=fopen($nomefile,"w");
     18    $num++;
     19    fwrite($fp,$num,4);
     20    fclose($fp);
     21 }
     22 $sid=session_id();
     23 print ("<html><head><title>Contatore con sessione</title></head>\n");
     24 print ("<body><h2 style=\"text-align:center;\">Pagina\
  \con contatore</h2>\n"); 25 print ("<p/>Numero visite (con gestione sessione - SID = $sid) = $num\n"); 26 print ("<p/>Clicca <a href=\"chiudi_sess.php\">qui</a> per\
  \chiudere la sessione\n"); 27 print ("</body></html>"); 28 ?>

Commentiamo il listato servendoci dei numeri di riga aggiunti per questo scopo:

Nella figura 7.31 viene mostrato l'aspetto della risposta accompagnato dalla finestra di visualizzazione del relativo sorgente.

Figura 7.31.

figure/pagine-web-php-ese0003

Aggiungiamo anche i listati dei file chiudi_sess.php, da attivare, per chiudere la sessione, tramite collegamento presente nella pagina appena mostrata, e conta_nosess.php che è la versione di pagina con contatore di accessi non corretta in quanto non gestita con le sessioni.

Per questi due file non viene mostrato l'aspetto in caso di apertura con il browser e non vengono commentate le istruzioni in quanto non contengono sostanziali novità rispetto a quanto illustrato finora.

Questo è il listato di chiudi_sess.php:

<?php
session_start();
session_destroy();
print ("<html><head><title>Chiusura sessione</title></head><body>\n");
print ("Sessione chiusa! Arrivederci\n");
print ("</body></html>");
?>

Mentre questo è conta_nosess.php:

<?php
$nomefile="conta.txt";
if (!file_exists($nomefile)) {
   $fp=fopen($nomefile,"w");
   fclose($fp);
}
$fp=fopen($nomefile,"r");
$num=fread($fp,4);
fclose($fp);
$fp=fopen($nomefile,"w");
$num++;
fwrite($fp,$num,4);
fclose($fp);
print ("<html><head><title>Contatore senza sessione</title></head>\n");
print ("<body><h2 style=\"text-align:center;\">Pagina\
  \con contatore disonesto</h2>\n"); print ("<p/>Numero visite (senza gestione sessione) = $num\n"); print ("</body></html>"); ?>

7.9   Pagine PHP che richiamano se stesse

Una tecnica molto diffusa, nell'ambito della applicazioni scritte in PHP, è quella di scrivere pagine che svolgono sia il compito di raccolta dati attraverso un modulo, sia quello di elaborazione invio della risposta.

Prima di tutto occorre fare in modo che lo script invii i dati a se stesso; a tale scopo occorre inserire "$_SERVER['PHP_SELF']" (variabile globale contenente il nome dello script PHP in esecuzione) come valore dell'attributo action del modulo.

Usando poi una campo nascosto del modulo come variabile flag si riesce a discriminare se lo script viene attivato per la prima volta, e allora deve emettere il modulo per l'input dei dati, oppure se è stato richiamato dal modulo, e allora deve elaborare questi ultimi e, eventualmente, emettere una risposta.

La logica di funzionamento dello script è quella rappresentata nello schema visibile nella figura 7.34, realizzato in «stile» diagramma di flusso.

Figura 7.34.

figure/pagine-web-schema-modulo-self

Nell'esempio viene anche fatto uso di campi riguardanti data e ora in modo da poter illustrare alcune funzioni PHP dedicate a questo tipo di dati.

      1 <?php
      2 print ("<html><head><title>Modulo autocontrollante</title></head>\n");
      3 print ("<body>\n");
      4 $giorno=date("d/m/Y");
      5 $ora=date("H:m:s");
      6 $PHP_SELF=$_SERVER['PHP_SELF']; 
      7 // se nascosto settato prende i valori in arrivo dal form
      8 if (isset($_REQUEST['nascosto'])) {
      9    $cogn=$_REQUEST['cognome'];
     10    $nom=$_REQUEST['nome'];
     11    $datan=$_REQUEST['data_nas'];
     12    $g=substr($datan,0,2);
     13    $m=substr($datan,2,2);
     14    $a=substr($datan,4,4);
     15 
     16 // controlli
     17    $controlli=1;
     18    if (!preg_match("/^[A-Z]{1}[ a-z']*$/",$cogn)) {
     19       echo "<p/>Il campo Cognome non è valido!\n";
     20       $controlli=0;
     21    }
     22    if (!preg_match("/^[A-Z]{1}[ a-z']*$/",$nom)) {
     23       echo "<p/>Il campo Nome non è valido!\n";
     24       $controlli=0;
     25    }  
     26    if(!checkdate($m,$g,$a)) {
     27       print("<p/>Errore nella data!\n");
     28       $controlli=0;
     29    }    
     30 }
     31 // se controlli ok e nascosto settato -- tutto ok      
     32 if (($controlli) and (isset($_REQUEST['nascosto']))) {
     33    print ("<h2>Dati inviati</h2>\n");
     34    print ("<p/>Cognome: $cogn\n");
     35    print ("<p/>Nome: $nom\n");
     36    print ("<p/>Data di nascita: $g/$m/$a\n");
     37    print ("<p/>Grazie. Clicca <a href=\"$PHP_SELF\">qui</a>\
  \per tornare al modulo iniziale"); 38 } 39 else { 40 // emette il modulo 41 print ("<h1>Modulo raccolta dati</h1>\n"); 42 print ("<p/>Treviso, $giorno $ora<p/>\n"); 43 print ("<h2>Inserisci i tuoi dati</h2>\n"); 44 print ("<p/><form action=\"$PHP_SELF\" method=\"post\">\n"); 45 print ("Cognome: <input type=\"text\" size=\"15\" name=\"cognome\"\
  \ value=\"$cogn\"/>\n"); 46 print ("<p/>Nome: <input type=\"text\" size=\"15\" name=\"nome\"\
  \ value=\"$nom\"/>\n"); 47 print ("<p/>Data di nascita (ggmmaaaa): <input type=\"text\"\
  \ size=\"8\" maxlength=\"8\" name=\"data_nas\" value=\"$datan\"/>\n"); 48 print ("<input type=\"hidden\" name=\"nascosto\" value=\"1\"/>\n"); 49 print ("<p/><input type=\"submit\" value=\"Invia\"/>\n"); 50 print ("<input type=\"reset\" value=\"Reset\"/>\n"); 51 print ("</form>\n"); 52 } 53 print ("</body></html>"); 54 ?>

Vediamo le parti più significative del listato:

Nelle due successive figure 7.36 e 7.37 vediamo il caso in cui si hanno errori nell'input e quello in cui l'immissione si conclude positivamente.

Figura 7.36.

figure/pagine-web-php-ese0003a

Figura 7.37.

figure/pagine-web-php-ese0003b

7.10   Gestione degli errori in PHP

Come per tutti gli altri linguaggi di programmazione, anche programmando in PHP è possibile commettere tre tipi di errori:

Nelle due successive figure, 7.38 e 7.39 vediamo quello che appare nella finestra del browser rispettivamente in caso di un errore di sintassi e di un errore a tempo di esecuzione (si vuole aprire un file che non esiste).

Figura 7.38.

figure/pagine-web-php-ese0004

Figura 7.39.

figure/pagine-web-php-ese0005

Come si vede, nel secondo caso l'errore non è fatale e lo script prosegue ugualmente fornendo un risultato ovviamente non corretto.

Quando una procedura scritta in PHP viene resa attiva presso gli utenti (si dice che passa «in produzione»), è lecito aspettarsi che tutti gli errori di sintassi siano stati eliminati; gli errori a tempo di esecuzione sono invece più subdoli perché possono manifestarsi solo in certe condizioni (il file da aprire esisteva fino a ieri e oggi non esiste più); in questo caso un buon programmatore dovrebbe prevedere opportuni controlli all'interno dello script in modo da intercettare le possibili situazioni di errore gestendole con opportuni messaggi all'utente («L'archivio xyz, fondamentale per la procedura si è danneggiato; contattare l'amministratore del sistema»).

Nell'ambito della gestione degli errori possono essere proficuamente utilizzate i comandi break, per forzare l'uscita da un blocco di istruzioni (ad esempio da un ciclo), e exit per forzare la conclusione dello script.

Se lo sviluppatore è ragionevolmente certo di avere previsto la gestione di tutti gli errori a tempo di esecuzione della sua procedura (o almeno dei più probabili), può essere opportuno disabilitare la visualizzazione sul browser dei messaggi di errore PHP che può generare sconcerto nell'utente oltre che fornirgli informazioni inopportune.

Per fare questo occorre intervenire nel file di configurazione /etc/php5/apache2/php.ini per cambiare la riga:

display_errors = On

ponendo il valore a Off.

Questa modifica fa si che, in caso di errore, l'utente riceva una pagina bianca; ciò probabilmente non fa diminuire il suo sconcerto, ma almeno evita che gli vengano visualizzate informazioni inopportune.

Avendo disattivato la visualizzazione dei messaggi di errore può essere opportuno attivare la loro registrazione in un file apposito del servente; per fare questo occorre (sempre nel file di configurazione) cambiare la riga:

log_errors = Off

ponendo il valore a On e togliere il commento (che in questo file si ottiene con il «;») alla riga:

;error_file = filename

inserendo il nome del file desiderato (ad esempio /var/log/php.log) al posto di filename; il file in prescelto deve essere scrivibile dall'utente www-data per i motivi ormai più volte spiegati.

Rimanendo nella sezione di gestione degli errori del file di configurazione, può servire intervenire sulla riga della variabile error_reporting per alterare il livello di errori che vengono segnalati; a tale proposito sono presenti nel file molte righe commentate che spiegano in dettaglio i valori che possono essere assegnati.

7.11   Inclusione di codice e scrittura di funzioni in PHP

Quando si realizzano applicazioni Web di una certa portata, può essere molto utile definire una volta per tutte alcune caratteristiche comuni a tutte le pagine del sito oppure porzioni di codice da utilizzare in punti diversi dell'applicazione stessa; per questo motivo sono presenti in PHP due funzioni con le quali si possono caricare in un uno script porzioni di codice o di sorgente HTML.

Le due funzioni sono include() e require(); entrambe caricano, al momento dell'esecuzione,il file indicato come argomento e segnalano un problema nel caso esso non esista; la require() in tale situazione blocca l'esecuzione, la include() invece prosegue comunque.

Al fine di evitare possibili problemi dovuti alla inclusione successiva di uno stesso file, è talvolta utile usare le funzioni include_once() e require_once() che si comportano come le precedenti alla prima inclusione ma ignorano tentativi di caricamento successivi.

I file da includere possono avere dei nomi arbitrari, ma c'è la consuetudine di assegnare .html a file contenenti HTML e estensione .inc a file con codice PHP; quest'ultima scelta può essere pericolosa perché, in caso di errata configurazione del servente Web, i programmi di navigazione potrebbero visualizzare il contenuto di tali file, mostrando quindi eventuali dati riservati (ad esempio i parametri di connessione ad una base di dati gestita dall'applicazione scritta in PHP).

Si suggerisce quindi di assegnare l'estensione .php ai file da includere contenenti istruzioni del linguaggio.

Come esempio vediamo un tipico utilizzo della include() in cui si effettua l'inclusione di due file HTML contenenti rispettivamente la testata e la coda che si suppongono comuni a tutte le pagine del nostro sito; evidentemente l'inclusione deve essere fatta in modo analogo in tutte le pagine interessate:

<?php
include("testata.html");
?>
.....
(contenuto della pagina)
.....
<?php
include("coda.html");
?>

L'inclusione di codice PHP si usa molte volte per rendere disponibili, all'interno degli script, le definizioni di una serie di funzioni che essi possono utilizzare; ovviamente qui parliamo di funzioni definite dal programmatore, che vanno ad aggiungersi a quelle predefinite nel linguaggio PHP.

Rendere disponibili le funzioni in modo centralizzato in uno o più file da includere permette una gestione più comoda delle modifiche nel caso in cui esse siano necessarie in molti script diversi; questo naturalmente non significa che la definizione di funzioni non possa avvenire direttamente all'interno degli script; nel proseguo, per comodità di esposizione, faremo proprio riferimento al caso in cui le funzioni siano definite nello stesso script in cui sono richiamate.

Dal punto di vista del motore PHP non c'è alcuna differenza rispetto al fatto che la dichiarazione di una funzione sia nello script che la usa piuttosto che in un file esterno da includere; infatti l'inclusione provoca la copia delle istruzioni contenute in quest'ultimo nello script prima che esse siano eseguite.

Una funzione PHP è caratterizzata da un nome, un insieme di parametri opzionali e, in genere, da un valore di ritorno.

Vediamo un esempio di definizione di una funzione:

      1 <?php
      2 function inverti($p)
      3 {
      4    if ((!isset($p)) || ($p!="")) {
      5       $ris="";
      6       for ($i=strlen($p);$i>0;$i--) {
      7          $ris=$ris.substr($p,$i-1,1);
      8       }
      9       return $ris;
     10    }
     11    else {
     12       return "";
     13    }
     14 }    
     15 ?>

Una possibile invocazione della funzione può essere la seguente:

<?php
$b=inverti($a);
?>

La variabile $a è il «parametro attuale» passato alla funzione; il valore restituito da essa è assegnato a $b.

Il passaggio dei parametri avviene per valore a meno che nella definizione della funzione non sia presente un «&» prima del nome del parametro; in tal caso il passaggio avviene per riferimento (in queste dispense si danno per scontate le conoscenze teoriche circa le due modalità di passaggio dei parametri ad una funzione).

Le variabili hanno un campo d'azione o scope coincidente con la funzione in cui sono definite oppure con la pagina se sono definite fuori da qualsiasi funzione.

Per fare in modo che una variabile definita fuori da una funzione sia visibile anche al suo interno, occorre usare, nel corpo della funzione, la parola riservata global seguita dal nome della variabile; in mancanza di ciò la variabile usata nella funzione viene ad essere a tutti gli effetti «nuova» e del tutto diversa da quella, con stesso nome, definita esternamente (anche questi concetti, presenti in tutti i linguaggi di programmazione, vengono dati per noti e non sono ulteriormente approfonditi).

Notiamo infine come una funzione possa anche non prevedere un valore di ritorno: potrebbe essere ad esempio una funzione che esegue solo una serie di print(), oppure una funzione che contiene solo del sorgente HTML (cosa possibile anche se non di uso frequente); in tal caso non è necessario return al suo interno e la sua invocazione avviene senza la presenza di una assegnazione.

7.12   La funzione header() di PHP

La funzione header() è utile in tutti quei casi in cui si vuole alterare il comportamento del motore PHP che automaticamente provvede ad inserire, prima di qualsiasi output, l'intestazione HTTP «normale» cioè quella contenente la riga Content-Type: text/html seguita da una riga vuota.

La funzione deve essere usata prima che sia stato emesso qualsiasi output sia con le funzioni a ciò preposte del PHP sia a causa della presenza di sorgente HTML nella pagina che preceda la funzione stessa.

Grazie al suo uso è possibile alterare l'intestazione per gli scopi sotto elencati:

7.13   Gestione di basi di dati MySQL e PostgreSQL con PHP

In questo paragrafo vengono prese in esame alcune delle funzioni che PHP mette a disposizione per lavorare con basi di dati MySQL e PostgreSQL; l'elenco completo si può trovare in molti documenti o siti, ad esempio in <http://www.php.net/manual>.

Essendo le problematiche di uso di una base di dati simili qualunque sia il software di gestione, facciamo gli esempi riferendoci solo a PostgreSQL evidenziando le differenze con MySQL riguardo alle funzioni PHP da richiamare.

Compito Funzione per MySQL Funzione per PostgreSQL
Apertura connessione ad una base di dati mysql_connect() pg_connect()
Selezione di una base di dati mysql_select_db() -
Esecuzione di una istruzione SQL mysql_query() pg_query()
Numero record restituiti da una istruzione SQL mysql_num_rows() pg_num_rows()
Valori estratti con una interrogazione SQL mysql_fetch_array() pg_fetch_array()
Chiusura di una connessione ad una base di dati mysql_close() pg_close()

Non vengono forniti dettagli sui parametri richiesti dalle funzioni e sui loro valori di ritorno; queste informazioni possono essere desunte dall'uso delle funzioni fatto nei prossimi esempi e poi approfondite consultando i manuali di PHP.

Nell'esempio seguente viene realizzato un modulo per inserire i dati nella tabella rubrica definita in precedenza nella base di dati fulvio in PostgreSQL e in MySQL.

Con lo stesso modulo è anche possibile effettuare un'interrogazione all'archivio specificando alcuni parametri; i dati da inserire o da usare per la ricerca vengono passati ad un file PHP che effettua l'operazione richiesta e crea una opportuna pagina di risposta.

La gestione del modulo non è rifinita: mancano molti controlli sui campi di input e le funzionalità sono molto lontane da quelle che dovrebbero essere previste in una procedura di gestione di dati via Web «reale»; d'altra parte questo esempio è inserito solo a scopo didattico ed è quindi volutamente limitato.

Il sorgente del modulo, chiamato modulo.html è definito come segue (il file può avere estensione .html in quanto non contiene codice PHP):

<html>
<head>
<title>Gestione tabella Rubrica</title>
</head>
<body>
<h3 style="text-align:center;">Gestione Rubrica</h3>
<form method="post" action="./phpsql.php">
<p><b>Cognome: </b>
<input type="text" name="cognome" maxlength="25" size="20"/>**
</p>
<p><b>Nome &#160;&#160;&#160;&#160;&#160;&#160;:</b>
<input type="text" name="nome" maxlength="25" size="20"/>**
<p><b>Data di nascita (ggmmaaaa): </b>
<input type="text" size="8" maxlength="8" name="data_nas"/>
</p>
<p><b>Indirizzo &#160;:</b>
<input type="text" name="indirizzo" maxlength="40" size="40"/>
</p>
<p><b>Telefono &#160;:</b>
<input type="text" name="telefono" maxlength="15" size="15"/>**
</p>
<p><b>E-mail &#160;&#160;&#160;&#160;&#160;:</b>
<input type="text" name="email" maxlength="30" size="30"/>
</p><br/>
** = campi validi per l'interrogazione<p/>
(lasciarli <b>tutti vuoti </b>per ottenere la
<b>lista completa </b>del contenuto dell'archivio)<p/>
<input type="reset" value="Azzera"/>
<input type="submit" value="Inserisci" name="sub"/>
<input type="submit" value="Interroga" name="sub"/>
</form>
</body>
</html>

Il modulo viene visualizzato come mostrato nella figura 7.51.

Figura 7.51.

figure/pagine-web-php-ese0006

Il file "phpsql.php" che svolge le operazioni sulla base di dati è il seguente:

      1 <html>
      2 <head>
      3 <title>Interrogazione / inserimento Rubrica</title> 
      4 </head> 
      5 <body> 
      6 <?php 
      7 // include il file con le impostazioni per la connessione
      8 require("connetti_psql.php");
      9 // per mysql: require ("connetti_mysql.php");
     10 // la variabile della connessione è $conn
     11 $cognome=$_POST['cognome'];
     12 $nome=$_POST['nome'];
     13 $data_nas=$_POST['data_nas'];
     14 $indirizzo=$_POST['indirizzo'];
     15 $telefono=$_POST['telefono'];
     16 $email=$_POST['email'];
     17 $sub=$_POST['sub'];
     18 // verifica se richiesto un inserimento o una interrogazione 
     19 if ($sub == "Interroga") { 
     20    echo "<p/><h3>Risultato dell'interrogazione
     21    </h3>"; 
     22 // interrogazione: prepara la variabile alt in base ai parametri richiesti 
     23    $alt=0; 
     24    if ($cognome != "") $alt=100; 
     25    if ($nome != "") $alt += 10; 
     26    if ($telefono != "") $alt += 1; 
     27 /* L'istruzione switch permette di eseguire l'interrogazione appropriata   
     28    in base all'impostazione dei parametri */ 
     29    switch ($alt) {
     30    case 111: 
     31       $query="SELECT * FROM rubrica WHERE cognome='$cognome' AND 
     32       nome='$nome' AND telefono='$telefono';"; 
     33       break; 
     34    case 110: 
     35       $query="SELECT * FROM rubrica WHERE cognome='$cognome' AND 
     36       nome='$nome';"; 
     37       break; 
     38    case 101: 
     39       $query="SELECT * FROM rubrica WHERE cognome='$cognome' AND 
     40       telefono='$telefono';"; 
     41       break; 
     42    case 100: 
     43       $query="SELECT * FROM rubrica WHERE cognome='$cognome';"; 
     44       break; 
     45    case 11: 
     46       $query="SELECT * FROM rubrica WHERE
     47       nome='$nome' AND telefono='$telefono';"; 
     48       break;  
     49    case 10: 
     50       $query="SELECT * FROM rubrica WHERE nome='$nome';"; 
     51       break; 
     52    case 1: 
     53       $query="SELECT * FROM rubrica WHERE telefono='$telefono';"; 
     54       break; 
     55    default: 
     56       $query="SELECT * FROM rubrica;"; 
     57    } // fine switch 
     58    $richiesta=pg_query($conn,$query);
     59 // per mysql: $richiesta=mysql_query($query);     
     60    $righe=pg_num_rows($richiesta); 
     61 // per mysql: $righe=mysql_num_rows($richiesta);   
     62 
     63 /* se l'interrogazione ha restituito un risultato, da esso vengono
     64    estratti i dati e vengono formattati nel documento di risposta */ 
     65    if ($righe > 0) { 
     66       $cont=0; 
     67       while ($cont < $righe) {
     68          $riga = pg_fetch_array($richiesta);
     69 // Per mysql:
     70 //       $riga = mysql_fetch_array($richiesta);
     71          $v1 = $riga["cognome"]; 
     72          $v2 = $riga["nome"]; 
     73          $v3 = $riga["data_nas"]; 
     74          $v4 = $riga["indirizzo"];
     75          $v5 = $riga["telefono"]; 
     76          $v6 = $riga["email"];
     77          print ("<p/>Cognome    :  "); 
     78          print ($v1); 
     79          print ("<br/>Nome      :  "); 
     80          print ($v2); 
     81          print ("<br/>Data nas. :  "); 
     82          print ($v3); 
     83          print ("<br/>Indirizzo :  "); 
     84          print ($v4); 
     85          print ("<br/>Telefono  :  "); 
     86          print ($v5); 
     87          print ("<br/>Email     :  "); 
     88          print ($v6); 
     89          print ("<p/>"); 
     90          $cont++; 
     91       } // fine while 
     92    } 
     93    else { 
     94       print ("<p/>Nella base dati non presente alcuna
     95       informazione<br/> con le caratteristiche specificate!"); 
     96    } // fine if ($righe)  
     97 }
     98 else {
     99 // Inserimento: il cognome deve essere indicato  
    100    if (! $cognome)  
    101       echo "<p/><B> Il cognome &egrave; obbligatorio"; 
    102    else { 
    103       $g=substr($data_nas,0,2);
    104       $m=substr($data_nas,2,2);
    105       $a=substr($data_nas,4,4);
    106       if(!checkdate($m,$g,$a)) {
    107          echo "<p/>Errore nella data\n";
    108       }
    109       else {
    110          $inser = "insert into rubrica
    111           (cognome, nome, indirizzo, telefono, data_nas, email)
    112          values ('$cognome', '$nome', '$indirizzo', '$telefono',
    113           '$a-$m-$g', '$email')";
    114          $ris = pg_query($conn, $inser );
    115 //      per mysql: $ris = mysql_query($inser);  
    116          if ($ris != 1)  
    117             echo  "Errore SQL in inserimento";
    118          else 
    119             echo "Inserimento di $cognome $nome effettuato";
    120       }       
    121    }// fine if (! $cognome) 
    122 } // fine if ($sub.....) 
    123 pg_close($conn);
    124 // per mysql: mysql_close($conn);
    125 print ("<p/>"); 
    126 print ("<hr width=\"30%\"><p/> 
    127 <a href=\"./modulo.html\">Nuova operazione</a>"); 
    128 ?> 
    129 </body> 
    130 </html> 

Nel caso venga richiesta una interrogazione fornendo come unico parametro il cognome Paperino si ottiene la risposta riportata nella figura 7.53.

Figura 7.53.

figure/pagine-web-php-ese0007

Di seguito viene mostrato il contenuto dei due file connetti_psql.php e connetti_mysql.php utilizzati per la connessione alle rispettive basi di dati.

<?php
// imposta le variabili per la connessione al database e lo apre 
$pg_database="fulvio"; 
$pg_host="localhost"; 
$pg_port=5432; 
$pg_user="fulvio"; 
$pg_psw="laurai"; 
$conn=pg_connect("dbname=$pg_database host=$pg_host port=$pg_port\
  \user=$pg_user password=$pg_psw"); if (! $conn) { echo "<p/>Connessione al database non riuscita"; exit(); } ?>
<?php
// imposta le variabili per la connessione al database e lo apre 
$my_database="fulvio"; 
$my_host="localhost"; 
$my_user="fulvio"; 
$my_psw="laurai"; 
$conn=mysql_connect($my_host, $my_user, $my_psw);
if (! $conn) { 
    echo "<p/>Connessione al database non riuscita"; 
    exit();  
}
else {
    mysql_select_db($my_database, $conn);
}     
?>

Le funzioni per la connessione alle basi di dati usate in questi due file sono di facile comprensione e il ruolo dei parametri richiesti dovrebbe apparire chiaro dal nome degli stessi.

Qualche parola in più può essere spesa per spiegare almeno alcune parti del listato di phpsql.php completando i commenti già in esso presenti: