L'accesso ai database con PHP è stato uno dei punti di forza che hanno decretato il successo del linguaggio quando ha iniziato a diffondersi come uno degli strumenti con cui poter realizzare facilmente pagine Web dinamiche. Inizialmente il binomio PHP + MySQL, per diverse ragioni, veniva usato utilizzando la libreria originale per accedere a MySQL: il vantaggio sostanziale di questa libreria era la semplicità d'uso, lo svantaggio ancora la semplicità d'uso, nel senso che questo ha portato alla proliferazione di codice di scarsa qualità creato tramite copia/incolla, replicato su svariati siti, che si è rivelato in seguito essere poco sicuro, poco scalabile e in generale di scarsa qualità.
Esistono anche altre estensioni, in particolare MySQLi e PDO, che oltre a fornire un approccio orientato agli oggetti, sfruttano caratteristiche avanzate di MySQL. In particolare in questa pagina si vedrà come utilizzare PDO, facendo riferimento a quanto riportato in questo tutorial su PDO. I listati si possono trovare a fondo pagina.
Per poter far funzionare l'esempio è necessario creare la tabella di database che si trova nel file accesso.sql. Se si utilizza l'interfaccia phpMyAdmin è sufficiente selezionare la funzione di importazione sul DB desiderato, se invece si utilizza la riga di comando dal proprio server su Cloud9 l'istruzione sarà:
mysql -u root -Dc9 < accesso.sql
In questo caso ovviamente il file accesso.sql dovrà prima essere portato sul server di Cloud9.
A questo punto nel database sarà creata una tabella studenti con 5 righe al suo interno
+----+------------+-----------+-------+------------+------------------------------------------+
| id | nome | cognome | sesso | nascita | password |
+----+------------+-----------+-------+------------+------------------------------------------+
| 1 | Alessandro | Bugatti | m | 1971-06-19 | d012f68144ed0f121d3cc330a17eec528c2e7d59 |
| 2 | Cristina | Trevisani | f | 1975-06-22 | d012f68144ed0f121d3cc330a17eec528c2e7d59 |
| 3 | Manfredo | Guildini | m | 1978-12-12 | d012f68144ed0f121d3cc330a17eec528c2e7d59 |
| 4 | Alonso | Finn | m | 1900-12-02 | d012f68144ed0f121d3cc330a17eec528c2e7d59 |
| 5 | Francesco | Giannino | m | 1983-10-21 | d012f68144ed0f121d3cc330a17eec528c2e7d59 |
+----+------------+-----------+-------+------------+------------------------------------------+
Per connettersi al database è necessario fornire alcuni dati, che sono gli stessi che devono essere forniti al client a linea di comando e che sono l'host, il db, lo username, la password e l'encoding dei caratteri.
Il costruttore della classe PDO vuole come primo parametro una stringa che contiene, opportunamente formattati, i dati relativi al driver, all'host, al database e all'encoding usato. Questa stringa è chiamata DSN (Data Source Name)
In questo esempio la stringa è così creata:
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
Come si può notare la prima parte della stringa, mysql, indica quale driver usare, in questo caso quello che permette di accedere al server MySQL: questo perchè PDO è uno strato di astrazione che isola il programmatore dal database effettivamente utilizzato. In linea teorica, volendo cambiare il server di database utilizzato, passando ad esempio da MySQL a PostgreSQL, dovrebbe essere sufficiente modificare solo il DSN indicando di utilizzare il driver per PostgreSQL anzichè quello per MySQL. Questo in linea teorica, perchè poi differenze nei vari dialetti SQL utilizzati dai diversi software o specifiche estensioni proprietarie possono rendere il passaggio un po' più complesso.
$pdo = new PDO($dsn, $user, $pass, $opt);
L'ultimo parametro, che è un array opzionale (in realtà l'unico parametro non opzionale è il primo), serve ad impostare alcuni settings della connessione già all'atto della creazione, anzichè doverli eventualmente impostare successivamente. A titolo di esempio un possibile array potrebbe essere il seguente:
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
PDO permette di usare diversi modi con cui gestire condizioni di errore, quella usata qui è di utilizzare le eccezioni (come si può vedere anche nel vettore $opt). Il vantaggio di usare le eccezioni è che il comportamento generato in caso di errore sarà sensato sia che vengano gestite sia che non vengano gestite.
ini_set('display_errors', 1);
ini_set('log_errors', 0);
In questo esempio la prima istruzione fa in modo che gli errori vengano visualizzati nella pagina, mentre la seconda fa in modo che non vengano scritti sul file di log. In fase di produzione invece conviene invertire questi valori, in modo che i messaggi non siano mostrati nella pagina, ma vengano scritti nel file di log. Il file di log degli errori di PHP può trovarsi in posti diversi, che vengono impostati nel file php.ini del server , in Cloud9 ad esempio si trovano nella cartella superiore a quella dove viene eseguito lo script in un file chiamato php_errors.log.
Un esempio di messaggio d'errore è il seguente:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000] [1045] Access denied for user 'rot'@'localhost' (using password: NO)' in /home/ubuntu/workspace/pdo/select.php on line 18 PDOException: SQLSTATE[HY000] [1045] Access denied for user 'rot'@'localhost' (using password: NO) in /home/ubuntu/workspace/pdo/select.php on line 18 Call Stack: 0.0016 236464 1. {main}() /home/ubuntu/workspace/pdo/select.php:0 0.0017 238712 2. PDO->__construct() /home/ubuntu/workspace/pdo/select.php:18
Se si vuole effettuare una query costante, cioè una query nella quale non ci sono parte variabili, la soluzione corretta è utilizzare il metodo query in questo modo:
$stmt = $pdo->query('SELECT nome FROM studenti');
La variabile $stmt ritornata dal metodo in questo caso rappresenta la tabella studenti con tutte le righe da cui è composta. A questo punto di solito si ha l'esigenza di scorrere tutte le righe, ad esempio per mostrarle, e per far questo si può procedere nel seguente modo:
while ($row = $stmt->fetch())
{
echo $row['nome'] . "
";
}
Come si vede questo costrutto idiomatico sfrutta il ciclo while per estrarre le righe una per volta: il metodo fetch ritorna la riga corrente, che inizialmente è la prima riga e ad ogni chiamata il "puntatore" interno a $stmt si sposta alla riga successiva. Quando le righe finiscono la chiamata a fetch ritorna FALSE e il while termina. Ogni riga, per una delle impostazioni settate nella variabile $opt è un array associativo, quindi per ottenere i dati di ogni singolo campo è sufficiente indicare il nome del campo: nell'esempio mostrato verrà quindi stampata una lista di nomi di studenti.
Generalmente nelle applicazioni web le query non sono statiche, ma contengono al loro interno delle variabili che vengono valorizzate in maniera dinamica al momento dell'esecuzione dello script. Il modo corretto di inserire questi valori all'interno di una query è quello di utilizzare lo strumento dei prepared statements, che permettono di creare una sorta di template, che successivamente può essere eseguito inserendo i dati variabili, come si può vedere nell'esempio seguente:
$id = 3;
$sql = "SELECT * FROM studenti WHERE ID > :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
foreach($stmt as $row)
{
echo $row['nome'] . "
";
}
La variabile $sql contiene una query con una parte variabile, indicata con un nome preceduto da :, in questo esempio id. Il metodo prepare invia questa istruzione al server, che la elabora e la pianifica e la "tiene in pancia", pronta per essere eseguita quando verranno passati i valori, in questo caso l'id. $sql = "SELECT * FROM studenti WHERE ID > :id1 AND ID < :id2";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id1' => 2, 'id2' => 5]);
$sql = "INSERT INTO studenti (nome, cognome) VALUES (:nome, :cognome)";
$pdo->prepare($sql)->execute(['nome'=>'Perfengo', 'cognome' => 'Istrusi']);
allStudents($pdo, "Inserimento di uno studente");
Come si può vedere il metodo prepare e il metodo execute sono concatenati: in questo modo il codice è più compatto, ma non è possibile recuperare informazioni sul fatto che l'inserimento sia avvenuto o meno (ad esempio perchè i valori di un campo UNIQUE si ripetono). Nel prossimo paragrafo si vedrà come rispondere a questa esigenza.
$sql = "UPDATE studenti SET nome = :nome WHERE cognome = :cognome";
$stmt = $pdo->prepare($sql);
$stmt->execute(['nome'=>'Perfello', 'cognome' => 'Istrusi']);
allStudents($pdo, "Modifica del nome di Istrusi");
Stavolta viene salvato nella variabile $stmt lo statement preparato, in modo che dopo aver eseguito l'execute questa variabile contenga informazioni sugli effetti della query. In particolare se interessa sapere quante righe sono state interessate dalla modifica, allora il metodo rowCount può fornire la risposta, come si vedrà nel prossimo esempio.
$sql = "DELETE FROM studenti WHERE cognome = :cognome";
$stmt = $pdo->prepare($sql);
$stmt->execute(['cognome' => 'Istrusi']);
allStudents($pdo, "Eliminazione di Istrusi");
echo "Righe cancellate: " . $stmt->rowCount();
dove l'unica differenza rispetto al codice precedente risiede nell'istruzione SQL, mentre il codice PHP è esattamente uguale.