=== Imparando.net ===

PDO per accesso ai database

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.

Importazione dei dati

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

Connessione al database

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.
Una volta creata la stringa DSN bisogna istanziare un oggetto di classe PDO usando l'opportuno costruttore: questo oggetto rappresenta la connessione vera e propria e quindi va istanziato una sola volta all'interno dello script e può essere utilizzato per eseguire quante query si desiderano.

                    $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,
                    ];
                        

Come gestire gli errori

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.

  • Nel caso l'applicativo gestisca le eccezioni a livello globale, potranno essere intercettate e gestite in maniera uniforme, come qualsiasi altro tipo di eccezione
  • Nel caso non vengano gestite, causeranno la generazione di un Fatal Error che farà terminare l'esecuzione dello script e produrrà una serie di informazioni utili per il debug.
In quest'ultimo caso può essere opportuno impostare l'ambiente PHP in modo che esse vengano o non vengano visualizzate a seconda che l'applicazione sia in fase di sviluppo piuttosto che in produzione. Solitamente questi settaggi sono impostati nel file php.ini, che, a seconda del server su cui si sviluppa, potrebbe essere o meno accessibile al programmatore. Un altro modo per impostarli è quello di usare la funzione ini_set, che lavorando al livello dello script, sovrascrive le impostazioni del file php.ini. Per quanto riguarda la visualizzazione degli errori una configurazione ragionevole in fase di sviluppo è la seguente:

                    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
                    

Effettuare una query costante

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.

Effettuare query variabili con i prepared statements

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.
La successiva chiamata al metodo execute invia al server i valori con cui si vuole eseguire la query, tramite un array con coppie nome->valore e solo a questo punto $stmt conterrà al suo interno i risultati dell'esecuzione della query.
Qual è il vantaggio dell'utilizzo dei prepared statements? I valori che verranno inseriti sono automaticamente gestiti correttamente applicando l'escaping per caratteri speciali e isolandoli all'interno delle virgolette, in modo tale che non sarà possibile creare query malformate o, peggio, essere soggetti ad attacchi di tipo SQL injection.
Una volta eseguita la query si può procedere ad analizzare i dati ritornati, in questo caso utilizzando il costrutto foreach (si poteva ovviamente fare anche con il while come nell'esempio precedente), poichè $stmt è iterabile.
In questo esempio è stato usato un solo "segnaposto" all'interno della query, ma ovviamente il numero può essere arbitrario, come si vede nel seguente esempio con due parametri:
  $sql = "SELECT * FROM studenti WHERE ID > :id1 AND ID < :id2";
                      $stmt = $pdo->prepare($sql);
                      $stmt->execute(['id1' => 2, 'id2' => 5]);
                    

Istruzioni DML (Data Manipulation Language)

Non ci sono molte differenze tra l'utilizzo di istruzioni di questo tipo rispetto all'utilizzo di SELECT come visto negli esempi precedenti: in generale la differenza più evidente è che mentre nel caso delle SELECT si è interessati a cosa viene ritornato (una tabella), nel caso di istruzioni DML non viene ritornata una tabella, ma si può eventualmente essere interessati a sapere quante righe sono state coinvolte nell'operazione.

Istruzione INSERT

Se si suppone che l'istruzione INSERT vada a buon fine le istruzioni PDO possono essere concatenate per ottenere il codice risultante
$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.

L'istruzione UPDATE

Dovendo aggiornare uno o più record il codice potrebbe essere questo:
$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.

L'istruzione DELETE

Infine la cancellazione di uno o più record viene fatta in questo modo:
$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.
In tutti e tre gli esempi si è fatto uso della funzione allStudents, che non fa altro che stampare la lista degli studenti con un header utile ai fini di questi esempi.

Altro ...

download_icon Listati.zip