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.