PDO (PHP Data Objects) è una delle estensioni più interessanti introdotte nell’implementazione della versione 5 di PHP; essa nasce infatti dall’esigenza di recuperare una grave mancanza che per lungo tempo aveva caratterizzato questo linguaggio di sviluppo, cioè l’assenza di un’unica interfaccia per l’accesso alle basi di dati.
Cos’è PDO e a cosa serve?
Si immagini di lavorare su un progetto che prevede la creazione di un sito Web basato sull’interazione con un DBMS per l’accesso ai dati, il Database manager utilizzato potrebbe essere per esempio MySQL; a questo scopo lo sviluppatore potrà sfruttare le potenzialità messe a disposizione dalle funzioni native mysql_ based o mysqli_ based, ma cosa succederebbe se, per una qualsiasi esigenza, ci si ritrovasse di fronte alla necessità di trasferire il sito Web su un altro server che ha come DBMS di riferimento PostgreSQL?
Semplicemente si presenterebbe la necessità di dover riscrivere la propria applicazione da zero, o quasi, almeno per quanto riguarda la parte relativa all’accesso ai dati; PDO è stata realizzata proprio per evitare il verificarsi di problemi simili a quello appena descritto, grazie ad essa lo sviluppatore non avrà più delle semplici funzioni dedicate all’interazione con i database ma una vera e propria classe in grado di fornire metodi utilizzabili indipendentemente dal DBMS di riferiemento.
Questa estensione è stata introdotta nella versione 5.1 di PHP ma era già disponibile come componente PECL (PHP Extension Community Library) nella distribuzione 5.0; grazie ad essa è possibile accedere a dati messi a disposizione da differenti tipologie di Database manager tra cui anche MySQL, SQLite, PostgreSQL, SQLserver e Oracle.
Essenzialmente è possibile dire che PDO fornisce un data-access abstraction layer, cioè un livello di astrazione per l’accesso ai dati; si tratta infatti di una classe, definita forse impropriamente anche come "libreria", che mette a disposizione un insieme di sotto-classi derivate che agiscono in modo trasparente rispetto all’utente; il suo utilizzo permetterà di creare applicazioni dotate della massima portabilità possibile, l’utilizzatore potrà scegliere il DBMS di riferimento sulla base delle proprie esigenze senza particolari constrizioni relative alla tipologia di accesso ai dati dell’applicazione.
Naturalmente anche PDO presenta qualche limite seppur di importanza marginale, non è per esempio retrocompatibile con versioni di PHP precedenti alla 5, si tratta infatti di un’estensione che necessita della presenza nel core del linguaggio delle funzionalità relative alla programmazione per oggetti, indisponibili per i rilasci più datati che comunque, ormai, non godono di alcun supporto. Inoltre, è bene tenere conto del fatto che PDO non mette a disposizione una database abstraction, quindi non è in grado di riscrivere istruzione in linguaggio SQL o di emulare funzioni che non vengano fornite dal DBMS stesso.
Per coloro che sviluppano abitualmente applicazioni utilizzando il paradigma OOP, l’utilizzo di PDO non propone particolari novità a livello sintattico, diverso è il discorso per quanto riguarda coloro che sfruttano ancora l’approccio procedurale, a questi ultimi si consiglia in via propedeutica la lettura dell’apposita guida su "PHP e la Programmazione Orientata agli Oggetti" pubblicata in questo sito.
Installazione e connessione al database
Uno dei vantaggi derivanti dall’utilizzo di PDO sta nel fatto che grazie ad esso non sarà necessario installare set di librerie dedicate a DBMS specifici; PDO è disponibile di default per le più recenti distribuzioni di PHP nell’apposita directory dedicata alle estensioni; per abiltarla, se questo non è stato già fatto, è possibile aprire il file di configurazione del linguaggio, Php.ini, ed editare la seguente riga decommentandola:
extension=php_pdo.dll
Il passo successivo sarà quello di decommentare le righe relative alle DLL di supporto per i DBMS che si desidera utilizzare, nell’esempio che segue è stata decommentata soltanto la riga per il caricamento della libreria specifica per MySQL, la libreria per SQLite è invece abilitata di default a partire dalla versione 5.1 di PHP:
extension=php_pdo_sqlite.dll
;extension=php_pdo_firebird.dll
;extension=php_pdo_mssql.dll
extension=php_pdo_mysql.dll
;extension=php_pdo_oci.dll
;extension=php_pdo_ibm.dll
:extension=php_pdo_informix.dll
;extension=php_pdo_oci8.dll
;extension=php_pdo_odbc.dll
;extension=php_pdo_pgsql.dll
Fatto questo basterà salvare le modifiche effettuate e riavviare il Web server, ad esempio Apache, per usufruire delle funzionalità di PDO; a questo punto si potrà passare alle fasi di collegamento e di connessione al database, la prima richiede la definizione di una stringa all’interno della quale verranno passati come argomenti il nome di host della macchina su cui gira MySQL (nel nostro esempio "localhost") e il nome del database da selezionare ("agenda"):
// collegamento al database con PDO
$col = 'mysql:host=localhost;dbname=agenda';
La seconda fase, quella di connessione, detta anche di costruzione di un oggetto PDO, non è altro che un’istanza e prevede il passaggio di tre parametri obbligatori:
- la stringa di collegamento al database precedentemente definita;
- il nome dell’utente attraverso i quale si desidera effettuare l’autenticazione;
- la password relativa all’utente scelto;
- opzionalmente è possibile aggiungere un argomento relativo alle opzioni del driver utilizzato per la connessione.
Nel nostro caso, il codice necessario per la costruzione di un oggetto PDO potrebbe essere il seguente:
// connessione al database con PDO
$db = new PDO($col , 'username', 'password');
Se tutto dovesse andare per il meglio e non si dovessero ricevere notifiche di errore, la nostra applicazione sarà pronta per l’accesso ai dati; in ogni caso, dato che in fase connessione possono verificarsi non di rado degli imprevisti, è bene affrontarla all’interno di un blocco per la gestione delle eccezioni, ad esempio:
# gestione delle eccezioni in fase di connessione con PDO
// collegamento al database
$col = 'mysql:host=localhost;dbname=agenda';
// blocco try per il lancio dell'istruzione
try {
// connessione tramite creazione di un oggetto PDO
$db = new PDO($col , 'username', 'password');
}
// blocco catch per la gestione delle eccezioni
catch(PDOException $e) {
// notifica in caso di errorre
echo 'Attenzione: '.$e->getMessage();
}
Nell’esempio abbiamo:
- definito la stringa di collegamento, in questa fase non ci sono eccezioni da gestire, eventuali notifiche verranno infatti prodotte in fase di connessione;
- aperto un blocco "try" all’interno del quale lanciare l’istruzione per la connessione attraverso la costruzione di un oggetto PDO;
- aperto un blocco "catch" all’interno del quale gestire eventuali eccezioni verificatesi durante la connessione.
Il blocco "catch" entrerà in azione soltanto nel caso in cui dovesse presentarsi un’eccezione da gestire, diversamente l’esecuzione si esaurirà con il lancio della connessione tramite "try" e si potrà procedere con la fase di accesso ai dati.
Generare transazioni con PDO
Una volta superata la fase di costruzione di un oggetto PDO, questo metterà a disposizione alcuni metodi grazie ai quali manipolarne le proprietà; uno dei vantaggi nell’impiego di PDO sta nella possibilità di effettuare delle transazioni, esse consentono di rendere inefficaci determinate istruzioni SQL nel caso in cui esse non abbiano avuto esito positivo. Un DBMS non è in grado di dar vita ad una transazione se questa non le viene richiesta, le interrogazioni sono infatti regolate da un meccanismo denominato auto-commit che applica delle modifiche a carico dei dati o della loro struttura una volta che sono state lanciate; una transazione permetterà invece di ritornare allo stato originario con evidenti implicazioni per la sicurezza e l’integrità dei dati.
PDO mette a disposizione un’apposito metodo per inizializzare una transazione (beginTransaction()), grazie ad esso sarà possibile introdurre un numero indefinito di interrogazioni avendo la sicurezza di poter riportare le proprie tabelle alla condizione originaria:
// lancio di una transazione con PDO
$db->beginTransaction();
Per introdurre delle query, PDO mette a disposizione il metodo exec(), da richiamare per ogni diversa interrogazione, la loro esecuzione non apporterà modifiche ai dati o alla struttura in cui sono inseriti e questi potranno essere riportati allo stato precedente tramite il metodo rollback(); si analizzi il seguente esempio:
// disabilitazione dell'auto-commit
$db->beginTransaction();
// esecuzione delle query
$sql = $db->exec("DROP TABLE recapiti");
$sql = $db->exec("UPDATE anagrafica SET nome = 'Rossi' WHERE id = 1");
// ritorno alla situazione precedente
$db->rollBack();
Nel codice proposto:
- viene inizializzata una transazione;
- vengono lanciate due query differenti che però potrebbero dar luogo ad anomalie nella nostra struttura dei dati;
- viene introdotto il metodo rollback() per rendere inefficaci le modifiche che sarebbero state apportate dalle query.
L’introduzione rollback(), a transazione iniziata, impedisce che vengano applicate delle modifiche, ma è bene tenere presente che il suo utilizzo riabilita l’auto-commit, quindi, se si desidera disabilitarlo per le query successive sarà necessario dare il via ad una nuova transazione.
Naturalmente, nello sviluppo di un’applicazione è necessario, prevedere anche funzionalità in grado di apportare modifiche ai dati gestiti, in modo da rendere permanenti i risultati dell query quando lo si desidera; con PDO ciò è possibile grazie al metodo commit() che ha il compito di riabilitare l’auto-commit disabilitato tramite beginTransaction(); commit() ha una funzione opposta a quella di rollback, grazie ad esso infatti verranno apportati a carico dei dati i cambiamenti previsti dalle query; si analizzi il seguente esempio:
// disabilitazione dell'auto-commit
$db->beginTransaction();
// esecuzione delle query
$sql = $db->exec("UPDATE anagrafica SET nome = 'Luca' WHERE id = 1");
// applicazione delle modifiche
$db->commit();
commit() non necessita del passaggio di argomenti, è infatti valido all’interno del contesto della transazione che va a terminare; naturalmente, come nel caso di rollback(), ma per motivi ancora più evidenti, questo metodo riporta le query in stato di auto-commit che potrà essere disabilitato iniziando una nuova transazione; in ogni caso è bene ricordare che è un errore annidare delle transazioni, ognuna di esse dovrà concludersi infatti con il ritorno all’auto-commit.
Metodi per l’esecuzione delle query
Il metodo più semplice messo a disposizione da un oggetto PDO per l’esecuzione delle interrogazioni è query(), a differenza di altri metodi analizzati nel corso di questa trattazione, esso ha il compito di eseguire una query direttamente così com’è senza alcuna preparazione. query() deve essere utilizzato nei casi in cui si debba accedere a fonti di dati interne e sicure e quando si ha la sicurezza che la query potrà produrre qualche risultato valido, per il resto si tratta di un metodo estremamente semplice da utilizzare perché consente l’accesso diretto ai dati. A questo proposito, si analizzi il seguente esempio:
# utilizzo del metodo query()
// definizione della query
$sql = 'SELECT nome, cognome, cap FROM anagrafica ORDER BY id';
// visualizzazione dei risultati
foreach($db->query($sql) as $row){
echo $row['nome']. '<br>';
echo $row['cognome']. '<br>';
echo $row['cap']. '<br>';
}
Il codice proposto mostra un esempio di esecuzione di una query attraverso il metodo query(), l’interrogazione viene passata come argomento al metodo all’interno di un ciclo foreach in quanto il suo risultato è un PDOStatement object (oggetto restituito da PDO), contenente i risultati ottenuti tramite l’interrogazione, che può essere manipolato come un array.
Il metodo prepare() di PDO mette a disposizione uno strumento più avanzato e più sicuro per l’esecuzione delle query, attraverso di esso infatti sarà possibile accedere in "modalità protetta" anche a dati provenienti da fonti esterne, inoltre, il metodo consente di mettere il database interrogato al riparo di minacce come per esempio le SQL Injections; si analizzi il seguente esempio:
# utilizzo del metodo prepare()
// preparazione della query
$sql = $db->prepare('SELECT nome, cognome FROM anagrafica');
// esecuzione della query
$sql->execute();
// creazione di un array dei risultati
$res = $sql->fetchAll();
// visualizzazione dei risultati
print_r($res);
Nel codice proposto prepare() mette a disposizione una query che dovrà essere eseguita tramite l’oggetto execute() (restituito dallo stesso prepare()), il PDOStatement object fetchAll() avrà quindi il compito di restituire un array() multidimensionale contenente l’output dell’interrogazione i cui valori potranno essere stampati tramite la funzione print_r() associati alle relative chiavi.
PDO mette a disposizione un metodo appositamente concepito per il quoting delle stringhe anche all’interno delle istruzioni SQL; come è noto, il quoting è una procedura che si utilizza all’interno di un sorgente per la corretta chiusura di stringhe tra apici, in modo che queste siano definite tramite la giusta sintassi e non siano confuse dall’applicazione con altri frammenti di codice; a questo scopo in PDO si utilizza quote() che svolge una funzione molto simile alle ben conosiute mysql_escape_string() e mysql_real_escape_string(), si tratta di un metodo in grado di delimitare una stringa di input con apici e di effettuare l’escaping degli apici singoli per la stringa stessa. Si analizzi il seguente esempio:
# quoting delle stringhe con quote()
// definizione della stringa
$stringa = 'Ciao!';
// stampa a video senza quoting
echo $stringa;
// stampa a video con quoting
echo $db->quote($stringa);
La prima istruzione del codice proposto stamperà:
Ciao!
La seconda stamperà invece:
'Ciao!'
In quanto il metodo quote() provvederà a delimitarla tramite apici eseguendone il quoting.
PDO e data binding
Nei capitoli precedenti è stata sottolineata la capacità di prepare() nel permettere di formulare query sicure sia su dati provenienti da fonti interne che esterne; prepare() mette però a disposizione anche altre funzionalità tra cui, per esempio, quella importantissima di poter eseguire il bind dei dati all’interno di query. Grazie al bind sarà possibile infatti utilizzare dei valori da passare come argomenti delle query senza specificarli direttamente, cioè sostituendoli con dei caratteri che ne forniscano la rappresentazione.
A proposito di quanto appena detto, si analizzi il seguente esempio:
# bind dei dati con PDO
// preparazione della query
$sql = $db->prepare('DELETE FROM anagrafica WHERE nome = ? AND anni = ?');
// esecuzione della query con sostituzione dei valori
$sql->execute(array('Luca', 43));
Teoricamente, una query come quella contenuta nel codice proposto non avrebbe senso, infatti il linguaggio SQL non prevede l’utilizzo di punti interrogativi al posto dei valori, il DBMS non sarebbe quindi in grado di intepretarli e darebbe luogo ad un insuccesso dell’interrogazione. Grazie ad execute() è però possibile proporre un array che presenti quali valori gli argomenti da passare come parametri alla query; nel caso specifico dell’esempio proposto la query verrà reintepretata quindi nel modo seguente:
DELETE FROM anagrafica WHERE nome = 'Luca' AND anni = 42
Il bind dei dati può essere molto utile nel caso in cui si desideri lavorare su query a cui passare arbitrariamente dei valori, naturalmente questo sistema ha i suoi limiti e per utilizzarlo sarà necessario passare i valori da sostituire ai punti interrogativi nell’ordine corretto in cui dovranno essere utilizzati (in pratica, da sinistra verso destra). Inoltre, all’interno dell’array introdotto da prepare(), i valori dovranno rispettare il tipo di dato relativo ai campi utilizzati in query, quindi una stringa dovrà essere delimitata da apici mentre questi dovranno essere assenti nel caso dei valori numerici.
Per contro, è interessante notare come nella query in cui è stato effettuato il bind dei dati non siano stati utilizzati gli apici per delimitare il punto interrogativo che rappresenta il valore stringa:
DELETE FROM anagrafica WHERE nome = ? ..
Ciò non è stato necessario perché PDO è in grado di capire il tipo di dato da utilizzare grazie a quello già associato al corrispondente valore contenuto nell’array; nel caso in cui l’array dovesse contenere valori associati a tipi di dato incoerenti con quelli previsti per la query, il risultato sarà semplicemente un insuccesso dell’interrogazione, ma il database non sarà esposto a pericoli derivanti da SQL injections.
I punti interrogativi possono essere sostituiti utilizzando un altro sistema per il bind dei dati basato su stringhe; si analizzi il seguente esempio:
# bind dei dati con PDO
// preparazione della query
$sql = $db->prepare('DELETE FROM anagrafica WHERE nome = :nome AND anni = :anni');
// esecuzione di query con sostituzione dei valori
$sql->execute(array(':nome'=>'Luca', ':anni'=>42));
$sql->execute(array(':nome'=>'Max', ':anni'=>56));
Il sistema si basa su un meccanismo molto simile a quello proposto in precedenza ma si rivela più leggibile in quanto permette di fare dei riferimenti diretti ai nomi dei campi coinvolti durante l’interrogazione.
Sempre a proposito del bind dei dati, PDO mette inoltre a disposizione un oggetto, bindParam(), che consente di specificare una variabile o un valore come parametro per una query prima della sua esecuzione e la posizione di questo argomento nell’istruzione SQL:
# bind dei dati con PDO
// definizione di una variabile
$nome = 'Luca';
// preparazione della query
$sql = $db->prepare('DELETE FROM anagrafica WHERE nome = :nome AND anni = 42');
// sostituzione di valori
$sql->bindParam(':nome', $nome);
// esecuzione della query
$sql->execute();
Nell’esempio, BindParam() presenta due argomenti: il primo indica quale rappresentazione sotto forma di stringa (":nome") dovrà essere sostituita nella query con il relativo valore, il secondo indica la variabile ("$nome") contenente il valore da utilizzare per la sostituzione. In generale, è possibile dire che bindParam() accetta due argomenti:
- un numero intero nel caso in cui si utilizzi il punto interrogativo, una stringa se si utilizza il metodo basato sulle stringhe; se si utilizza un intero, questo sarà il numero d’ordine del parametro nella query.
- una variabile passata per riferimento il cui valore ha il compito di sostituire quello rappresentato dal primo argomento.
Si riformuli la query proposta in precedenza utilizzando il sistema di bin basato sul punto interrogativo:
$sql = $db->prepare('DELETE FROM anagrafica WHERE nome = ? AND anni = 42');
In questo secondo caso, i parametri introdotti da bindParam() saranno i seguenti:
$sql->bindParam(1,$nome);
Il valore numerico intero "1" indicherà infatti che la varibile passata come secondo parametro dovrà essere utilizzata per la sostituzione del primo valore utilizzato nel confronto introdotto tramite la clausola WHERE.