Lezione Precedente | Elenco Lezioni | Lezione Successiva |
Il PHP 5 mette a disposizione del programmatore un'insieme di funzioni predefinite (API) per l'interfacciamento a MySQL. In realtà esistono varie API differenti che ci consentono di utilizzare MySQL: noi utilizzeremo la API denominata PDO, abbreviazione di PHP Data Objects. PDO è una libreria generica che consente di accedere a diversi DBMS: gli stessi comandi si usano sia per accedere ad un database MySQL che per un database Postgres o Oracle. In questo si differenzia da altre API specifiche per MySQL, come la libreria mysqli
che veniva utilizzata nel corso dell'anno scorso.
.htaccess
, e salvarlo nella propria directory public_html
. Il file in questione modifica altri parametri di configurazione di PHP, che però studieremo solo in seguito.
mancolista
, in cui l'utente gamato
ha accesso in lettura e scrittura. Modificate opportunamente i valori in base alla vostra configurazione.
La libreria PDO mette a disposizione una classe, anch'essa denominata PDO
, che serve a collegarsi a un server MySQL e ad eseguire operazioni sugli schemi e le tabelle ivi presenti. Per poter fare qualunque cosa su un DBMS, occorre prima di tutto creare un oggetto di classe PDO. La gestione degli oggetti di PHP e simile al Java, e per creare gli oggetti si utilizza l'operatore new
.
Il costruttore della classe PDO
ha la seguente sintassi:
PDO::__construct(string $dsn, string $username, string $password)
: costruttore per un oggetto di classe PDO. I parametri $username
e $password
sono i nomi utente e password da usare per la connessione. Il parametro $dsn
prende il nome di Data Source Name ed è una stringa che specifica il tipo di server a cui collegarsi (Mysql, Postgres, Oracle, etc...), il nome o indirizzo IP del server, lo schema da utilizzare. Nel caso di MySQL, la Data Source Name avrà la forma
mysql:host=<hostname>;dbname=<schemaname>
Se non si vuole specificare una password, si può omettere l'ultimo parametro.
PDO
. Il metodo che interessa adesso a noi è quello che consente di inviare query a MySQL:
PDOStatement PDO::query( string $statement )
: invia la query SQL $statement
alla connessione MySQL. Restituisce il risultato della query sotto forma di oggetto della classe PDOStatement.
PDOStatement
, si può accedere al risultato della query invocando ripetutamente il metodo fetch
di quest'ultimo:
mixed PDOStatement::fetch( )
: restituisce la prossima riga del risultato. La riga è codificata sotto forma di array. La posizione 0 dell'array contiene il valore presente nella prima colonna della riga, alla posizione 1 c'è il valore presente nella seconda colonna e così via. È possibile accede alle varie colonne anche in maniera associativa, usando come indice il nome della colonna piuttosto che la sua posizione. Ogni volta che mysqli_fetch_array
viene chiamato, viene restituita una riga diversa del risultato. Quando le righe sono terminate, restituisce false
.
elenco.php
, che visualizza nome e descrizione del primo oggetto presente nel database mancolista
:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Elenco oggetti Mancolista di Paolo</title> </head> <body> <?php $conn=new PDO("mysql:host=localhost;dbname=mancolista","amato","?????"); $result=$conn->query("SELECT * FROM oggetti"); $riga=$result->fetch(); echo $riga["nome"]," : ",$riga["descrizione"],"<br>"; ?> </body> </html>Lo script inizia con la connessione al server mysql su localhost come utente
gamato
usando la password ?????
. A questi dati fittizi dovrete sostituire quelli del vostro account. Nello stesso costruttore si indica che siamo interesssati ad accedere al database mancolista
. Successivamente viene inviata la query SELECT * FROM voli
, e il risultato viene messo nella variabile $result
. Da lì viene estratta una riga e inserita nella variabile $riga
, per poi essere stampata.
Nel caso dell'aula informatica, dovrete rimpiazzare localhost
con goemon
, mancolista
e gamato
con il vostro numero di matricola, e ?????
con la password del vostro account MySQL. Ovviamente sarà necessario che carichiate le tabelle per l'applicazione Mancolista nel vostro schema personale.
Se potessimo accedere soltanto alla prima riga di una tabella avremmo dei seri problemi a scrivere un programma decente. Tuttavia, come abbiamo già detto, ogni volta che viene chiamato il metodo fetch
della classe PDOStatement
, viene estratta un riga divera. Possiamo allora modificare il programma in modo che visualizzi tutti gli elementi della tabella oggetti (in rosso la parte modificata):
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Elenco oggetti Mancolista di Paolo</title>
</head>
<body>
<?php
$conn=new PDO("mysql:host=localhost;dbname=mancolista","gamato","?????");
$result=$conn->query("SELECT * FROM oggetti");
while ($riga=$result->fetch())
echo $riga["nome"]," : ",$riga["descrizione"],"<br>";
?>
</body>
</html>
Finché ci sono righe da esaminare, il risultato di $result->fetch()
è un array che viene assegnato alla variabile $riga
. Dopo l'assegnamento, visto che ci troviamo nella condizione del comando while
, il valore viene automaticamente convertito in booleano. Qualunqe array non vuoto viene convertito nel booleano true
, quindi la condizione è verificata e il corpo del ciclo while viene eseguito. Quando non ci sono più righe da esaminare, il risultato di $result->fetch()
è false
. Questo viene assegnato alla variabile $riga
e causa la terminazione del ciclo while.
$riga
(ovvero nome
e descrizione
) sono gli stessi che si ottengono come intestazione delle colonne quando la query select * from voli è inviata dal monitor di MySQL. Se si modifica la linea $result=$conn->query("SELECT * FROM oggetti");con
$result=$conn->query("SELECT nome as n, descrizione FROM oggetti");occorre anche modificare l'echo finale in
echo $riga["n"]," : ",$riga["descrizione"],"<br>";altrimenti viene generato un errore, che ci avverte che stiamo tentando di accedere alla chiave
nome
che non esiste.Quello che abbiamo visto è un schema generale che si incontra ripetutamente nelle applicazioni web che si interfacciano a un database: connessione, interrogazione, elaborazione dei risultati, chiusura connessione. Ecco un breve riassunto:
$conn=new PDO("mysql:host=.....;dbname=.....",".....","....."); $result=$conn->query("......query....."); while ($riga=$result->fetch()) .... corpo del while .....
Notare che il PHP consente una scorciatoia. Si può usare foreach
per accedere alle righe di risultato invece del comando while
con il metodo fetch
. In pratica, si può rimpiazzare:
while ($riga=$result->fetch()) .... corpo del while .....con
foreach ($result as $riga) .... corpo del while ....
Qualora non interessi accedere a tutte le righe del risultato, ma solo alla prima, il while
e il foreach
, sono inutili, e si può rimpiazzare
while ($riga=$result->fetch()) .... corpo del while .....
con
$riga=$result->fetch() ..... elaborazione su $riga .......
Talvolta nella stessa pagina web occorre effettuare più di una interrogazione. Ci sono allora due possibilità:
La seconda soluzione è sicuramente più efficiente, ma richiede un po' di attenzione. Quando viene inviata una interrogazione al server MySQL, a seconda delle versioni della libreria PDO, potrebbe non essere più possibile inviare una nuova interrogazione se la prima non viene prima dichiarata conclusa. Per far ciò, la cosa più semplice è porre a null
la variabile che contiene l'oggetto PDOStatement
risultato dalla query. Ad esempio:
$conn=new PDO("mysql:host=.....;dbname=.....",".....",".....");
$result=$conn->query("......query 1.....");
... elaborazione query 1.....
$result=null;
$result=$conn->query("......query 2.....");
... elaborazione query 2.....
Cosa succede se i dati che si forniscono ai metodi e costruttori della libreria PDO sono errati? Vedremo che il comportamento di PHP cambia a seconda che il problema si verifichi nel costruttore della classe PDO (ovvero durante la connessione) o negli altri metodi (quando la connessione è stata ormai stabilita).
Cosa succede se la connessione a MySQL per qualche motivo non ha successo? Ad esempio, modifichiamo l'invocazione del costruttore della classe PDO
sostituendo il nome del database mancolista
con rancolista
(che non esiste). Viene generato un errore simile a questo:
Il messaggio è un po' confuso, ma se lo si formatta all'interno di un tag pre
appare più chiaro:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000] [1049] Unknown database 'mancoldsta'' in /home/amato/public_html/prova-manco1.php:8 Stack trace: #0 /home/amato/public_html/prova-manco1.php(8): PDO->__construct('mysql:host=loca...', 'amato', '') #1 {main} thrown in /home/amato/public_html/prova-manco1.php on line 8
In pratica viene identificato l'errore (Unknown database 'mancoldsta') e la sorgente dell'errore (riga 8 di /home/amato/public_html/prova-manco1.php
). Segue uno stack trace che è un elenco, in ordine inverso, delle funzioni e dei metodi che sono stati invocati e non sono ancora terminati. Questo stack trace ci dice che l'errore si è verificato a riga 8 di prova-manco1.php
, che a sua volta è stata eseguita direttamente dal programma principale ({main}
).
In realtà quello che è successo è che il problema verificatosi durante la connessione ha generato una eccezione di classe PDOException
. Le eccezioni PHP sono simili alle eccezioni Java, con la differenza che non bisogna dichiarare in maniera particolare i metodi o le funzioni che lanciano eccezioni. Le eccezioni si possono catturare con le istruzioni try ... catch
, ma se non vengono catturate causano la generazione di un errore fatale, e il termine immediato del programma. Per ora questo comportamento ci va bene, per cui non cattureremo l'eccezione. Quando però il software va in produzione (ovvero viene utilizzato dall'utente finale), è meglio ridefinire le eccezioni in modo che diano messaggi di errore meno informativi, e quindi meno utili agli hacker. Ad esempio, si potrebbe cambiare la riga
$conn=new PDO("mysql:host=localhost;dbname=mancolista","gamato","?????");nel programma di prima con:
try { $conn=new PDO("mysql:host=localhost;dbname=mancolista","gamato","?????"); } catch (PDOException $e) { die("Errore di connessione"); }
dove la funzione die
è simile ad echo
, ma termina anche l'esecuzione del programma.
Il comportamento di default di PHP per errori che si verificano quando la connessione a MySQL è già stabilita è leggermente diverso. Ad esempio, se nel comando $conn->query()
modifichiamo il nome della tabella oggetti
in soggetti
(che non esiste), otteniamo un errore simile a questo:
L'errore non è stato segnalato sul comando $conn->query
, dove effettivamente si è verificato, ma sul comando $result->fetch()
. Quello che succede è che $conn->query
fallisce in maniera silenziosa, restituendo il valore false
. Quando si tenta di eseguire il comando $result->fetch()
, il PHP si lamenta dicendo che $result
non contiene un oggetto, come è necessario per poter accedere al metodo fetch
.
Il problema è che questo messaggio non da nessuna informazione su quale sia l'errore originale nella query SQL che ha causato il problema. Durante la fase di scrittura del codice è utile che i messaggi di errori siano il più possibile chiari.
Per ovviare a questi problemi ci sono varie strade, ma quella che mi sembra più conveniente è far sì che anche gli errori successivi alla connessione, quando si verificano, generi delle eccezioni. Per far ciò, subito dopo la creazione dell'oggetto di classe PDO
, si può dare il comando:
$conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
Adesso, se riproviamo il programma di primo (con l'errore sulla query) otteniamo:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S02]: Base table or view not found: 1146 Table 'mancolista.oggedtti' doesn't exist' in /home/amato/public_html/prova-manco1.php:10 Stack trace: #0 /home/amato/public_html/prova-manco1.php(10): PDO->query('SELECT * FROM o...') #1 {main} thrown in /home/amato/public_html/prova-manco1.php on line 10
Questa volta l'errore PHP viene generato proprio sulla riga incriminata, e inoltre ci viene anche fornito il messaggio di errore SQL Table 'mancolista.oggedtti' doesn't exist' che ci fa capire qual è il problema.
Volendo, è poi possibile gestire in proprio queste eccezioni, come fatto sopra per quelle verificatesi durante la connessione.
Ogni volta che utilizziamo la classe PDO
ci tocca fornire una serie di parametri più o meno fissi: il nome del server MySQL, la username, la password e lo schema da utilizzare. Vorremmo evitare un inutile e dannosa duplicazione di dati. Allo stato attuale, infatti, se cambia la password di accesso al database, siamo costretti a modificare tutte le invocazioni del costruttore PDO
in tutte le pagine web.
La cosa migliore è definire delle costanti con il comando define
, da utilizzare al posto dei dati vero. Questa è la sintassi del comando define
:
$conn = new PDO(DSN,"gamato",'?????')
La cosa sarebbe poco utile se dovessimo ripetere la definizione della costante in ogni pagina web. Per fortuna il PHP ha due comandi che ci consentono, all'interno di una pagina, di leggere il contenuto di un altro file e di interpretarlo come se si trovasse all'interno della pagina stessa:
Notare che quando il file incluso viene letto il sistema rientra in
"modalità HTML". Per cui, se esso contiene codice PHP, è importante
inserire i tag <?php e ?>. Definiamo allora una costante per ognuno dei parametri della connessione, e mettiamo le definizioni in un file che chiamiamo config.php
. Ecco il suo contenuto:
<?php define('DSN','mysql:hostname=localhost;dbname=mancolista'); define('USERNAME','gamato'); define('PASSWORD','?????'); ?>
Alla fine, il nostro programma diventa:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Elenco oggetti Mancolista di Paolo</title> </head> <body> <?php require 'config.php'; $conn=new PDO(DSN,USERNAME,PASSWORD); $conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); $result=$conn->query("SELECT * FROM oggetti"); while ($riga=$result->fetch()) echo $riga["nome"]," : ",$riga["descrizione"],"<br>"; ?> </body> </html>
Salvare l'ultimo esempio di pagina PHP mostrata e il file config.php
. Accertarsi che tutto funzioni. Una volta che è tutto a posto, provare a modificarla inserendo degli errori, ed esaminare i risultati che si ottengono.
Scrivere una pagina PHP che mostri gli oggetti presenti nel database, indicando per ogni oggetto se è o non è già prenotato.
Scrivere una pagina PHP che mostri gli oggetti divisi per categoria, come richiesto dalla specifica del progetto mancolista.
Lezione Precedente | Elenco Lezioni | Lezione Successiva |