PDO, acronimo di PHP Data Object, è un’estensione di PHP introdotta a partire dalla versione 5.1 del linguaggio con lo scopo di unificare le API di accesso ai database. Alla base della loro concezione vi era quindi l’idea di fornire un’unica interfaccia di programmazione con cui interagire con tutte le basi di dati. Con la versione 7 di PHP, e le successive implementazioni di questa milestone, PDO si avvia a diventare uno standard quanto meno de facto, in particolare perché con questa release è stato rimosso il supporto ormai deprecato alle originarie funzioni specifiche per MySQL (lasciando però disponibile mysqli). Con l’abbandono delle “storiche” mysql functions il passaggio a PDO diventa quindi essenziale per la creazione di applicazioni aggiornate agli standard di sviluppo e sicurezza più recenti. PDO e la programmazione ad oggetti Come ben sintetizza il suo acronimo, PDO si presenta come una soluzione per sua natura orientata agli oggetti, l’estensione fornisce infatti un’interfaccia comune per la comunicazione con i diversi database. Tale caratteristica risulta estremamente pratica nel caso di una migrazione da un DBMS ad un altro, e permette agli sviluppatori di utilizzare le medesime strutture di programmazione a prescindere dal database di riferimento. Quello che non offre PDO è però una funzionalità di traduzione delle query o di emulazione di eventuali funzioni mancanti, sarà quindi compito dello sviluppatore farsi carico delle differenti versioni dei dialetti SQL. PDO non mette infatti a disposizione una vera e propria “database abstraction“, ma più semplicemente un data-access abstraction layer, cioè un livello di astrazione per l’accesso ai dati. I vantaggi nell’uso di PDO Uno dei principali punti di forza di PDO risiede nel fatto che esso mette a disposizione delle sviluppatore i prepared statement, costrutti che se utilizzati sistematicamente sono in grado di mettere al riparo gli script delle minacce basate sull’SQL injection, una tecnica di attacco incentrata sull’iniezione di SQL malevolo tramite input al fine di violare le applicazioni per la gestione dati. In conclusione è possibile sottolineare che i vantaggi derivanti dall’uso di PDO fanno riferimento alle seguenti caratteristiche: Portabilità: grazie al supporto di più engine per la gestione di dati. Programmazione orientata agli oggetti: grazie ad un’interfaccia nativamente object-oriented. Sicurezza: grazie ai prepared statement che proteggono dai tentativi di code injection. L’uso di PDO rappresenta quindi il primo passo per rendere la nostra applicazione object-oriented e riusabile. Il driver di PHP per PDO è stato abilitato in modalità predefinita con PHP 5.1.0, motivo per il quale il file di configurazione del linguaggio, PHP.ini, dovrebbe presentare già la seguente riga decommentata (cioè non preceduta dal punto e virgola. ";"): extension=pdo.so Se siete su un sistema Windows la riga in questione sarà: extension=php_pdo.dll Ora, sempre tramite PHP.ini, sarà possibile attivare il supporto di PDO per MYSQL decommentando (cioè rimuovendo il ";") dalla riga: extension=pdo_mysql.so oppure, se siete su Windows: extension=php_pdo_mysql.dll getMessage(); } ?> In sostanza lo script appena proposto rappresenta un template standard per la connessione a MySQL tramite PDO dove: vengono definite le variabili che contengono i parametri di connessione; i parametri vengono utilizzati per stabilire la connessione tramite istanza dell'oggetto PDO; il blocco try/catch per la gestione delle eccezioni notifica l'avvenuta connessione o gestisce eventuali errori che dovessero verificarsi nel tentativo di stabilirla. Una volta effettuata la connessione, se l'applicazione non dovesse prevedere altre operazioni sarà possibile chiuderla impostandone il valore su null. Chiaramente nulla avrebbe impedito di eseguire la medesima operazione senza il ricorso alle variabili, infatti l'istanza avrebbe accettato anche il passaggio dei parametri di connessione nella loro forma originale: //stringa di connessione al DBMS senza passaggio di variabili $connessione = new PDO("mysql:host=localhost;dbname=mysql", "mrwebmaster", "..."); ------------ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) { echo 'Attenzione errore: '.$e->getMessage(); } ?> richiamare il metodo setAttribute() al quale passare gli attributi PDO::ATTR_ERRMODE, per la reportistica degli errori, e PDO::ERRMODE_EXCEPTION, per il lancio delle eventuali eccezioni; --------------- 0)) $id=$form->sanitize_int($_REQUEST['id']); if ( empty($id) || !is_numeric($id) ) { die('ID non valido, solo numeri (0-9)!'); } $sql = 'SELECT * FROM clicks WHERE 1 AND id_click = :id AND clicks.active = 1 LIMIT 1'; $result = $db->prepare($sql); $result->bindParam(':id', $id, PDO::PARAM_INT); $result->execute(); $row=$result->fetch(PDO::FETCH_ASSOC) or die("qui"); // while($row=$result->fetch(PDO::FETCH_ASSOC)){ } if($result->rowCount() == 1){ $url= $row['click_url']; // aggiungi clicks. $sql_pre='UPDATE clicks SET clicks = clicks + 1 WHERE id_click = :id LIMIT 1'; $result = $db->prepare($sql_pre); $result->bindParam(':id', $id, PDO::PARAM_INT); $result->execute(); /* Redirect to the link URL */ echo $url; Header('Location: '.$url); exit(); ?> --------------- Anche per le operazioni relative all'inserimento dei dati nella tabella "contatti" precedentemente creata verrà seguita una procedura per molti versi simile a quella proposta in precedenza; motivo per il quale si stabilirà una connessione al DBMS, si selezionerà il database da utilizzare, verrà utilizzato il metodo exec() per l'esecuzione delle istruzioni SQL, sarà chiusa la connessione in caso di esito positivo o segnalate eventuali eccezioni in fase di esecuzione. Nel caso specifico però l'applicazione prevede di effettuare due diversi inserimenti per due record distinti, motivo per il quale il codice proposto di seguito presenterà alcune piccole (ma non per questo irrilevanti) novità che saranno analizzate nel dettaglio: setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // popolo la tabella con due record $inserisci_dati = $connessione->exec("INSERT INTO contatti (nome, cognome) VALUES ('Giuseppe', 'Garibaldi')"); $inserisci_dati_2 = $connessione->exec("INSERT INTO contatti (nome, cognome) VALUES ('Silvio', 'Pellico')"); // visualizza l'identificatore univoco dell'ultimo inserimento effettuato // in questo caso stampa la cifra "2" echo $connessione->lastInsertId(); // chiusura della connessione $connessione = null; } catch(PDOException $e) { // notifica in caso di errore nel tentativo di connessione echo $e->getMessage(); } ?> è possibile richiamare il metodo lastInsertId() che, senza la necessità del passaggio di alcun parametro, consentirà di visualizzare l'identificatore dell'ultimo inserimento effettuato tramite istruzione. --------------- Prepared statements o "istruzioni parametrizzate" In linea di massima i Prepared statements si possono definire come dei template (cioè degli schemi) destinati ad inviare dei dati ad una tabella, questi ultimi potranno essere utilizzati per l'esecuzione di istruzioni di vario tipo, dall'inserimento di valori alla loro estrazione, dall'aggiornamento dei record alla loro cancellazione. Il fatto che essi rappresentino dei template presuppone tre diversi vantaggi nell'utilizzo dei Prepared statements: sono più sicuri rispetto all'invio di istruzioni SQL nel loro formato originale; sono riutilizzabili e quindi consentono di ottimizzare il codice di un'applicazione; migliorano il livello generale delle prestazioni nelle operazioni che prevedono l'interazione con i database. Per quanto riguarda la sicurezza, i Prepared statements si rivelano particolarmente utili nella prevenzione degli attacchi basati sulla SQL Injection, cioè l'iniezione di codice malevolo tramite istruzioni SQL, questo per il semplice motivo che i Prepared statements separano l''SQL dai dati, quindi, anche nel caso in cui in essi sia presente del codice malevolo, questo non verrà eseguito perché non farà parte della query SQL. Dato che la parte relativa ad SQL diventa un template, tale schema potrà essere riutilizzato quando si desidera effettuare lo stesso tipo di istruzione, nel momento in cui viene effettuata la formazione del template essa potrebbe pesare sulle performances dell'applicazione di riferimento, nel complesso però la sua riutilizzabilità dovrebbe poi influire positivamente sulle prestazioni. Alla base della dinamica che porta alla generazione dei template nei Prepared statements vi è un meccanismo denominato binding dei parametri, esso prevede in pratica che al posto dei valori vengano passati in query dei placeholder (dei nomi che fungono da "segnaposto"), questi ultimi saranno successivamente vincolati (da qui l'utilizzo del termine "binding") a dei parametri da utilizzare per completare le operazioni di inserimento, selezione, aggiornamento o cancellazione dei dati. Per fare un esempio, si immagini che la nostra tabella "contatti" del database "nominativi" contenga anche il campo numerico intero "anni_vissuti" e di voler estrarre da essa il record che corrisponda al "cognome = 'Pellico'" e ad "anni_vissuti = 64"; il primo passaggio da effettuare sarà quello di definire due variabili da associare ai due valori precedentemente introdotti. // definizione delle variabili per la query $contatto_cognome = "Pellico"; $contatto_anni_vissuti = 64; Ora che tali variabili sono state memorizzate sarà possibile lasciarle da parte per un attimo e passare alla definizione del template. Esso viene passato come parametro al metodo prepare() e assume una forma simile a quella di un query (anche se in realtà non lo è) dove i valori vengono sostituiti dai segnaposto che, nel caso specifico sono ":contatto_cognome" e ":contatto_anni_vissuti". // preparazione della query SQL $sql = $connessione->prepare("SELECT nome FROM contatti WHERE cognome = :contatto_cognome AND anni_vissuti = :contatto_anni_vissuti"); Ora si potrà passare al vincolo tra variabili e segnaposto (binding), ciò potrà avvenire per ciascun valore passando all'apposito metodo bindParam() i parametri relativi al segnaposto utilizzato nel template, alla variabile che contiene il valore e al tipo di dato da associare a tale valore (nel nostro caso abbiamo un intero e una stringa, per quest'ultima è stata indicata anche la lunghezza, pari a 7 caratteri, tramite un ulteriore parametro). Vediamo il codice: // bind dei parametri $sql->bindParam(':contatto_cognome', $contatto_cognome, PDO::PARAM_STR, 7); $sql->bindParam(':contatto_anni_vissuti', $contatto_anni_vissuti, PDO::PARAM_INT); PDO::PARAM_STR e PDO::PARAM_INT sono delle costanti di classe utilizzate da PDO per PHP a partire dalla versione 5.1 di quest'ultimo per la rappresentazione di un data type SQL. A tal proposito potremo adottare le seguenti costanti: PDO::PARAM_BOOL: rappresenta un tipo di dato booleano; PDO::PARAM_NULL: rappresenta un tipo di dato SQL NULL (assenza di valore); PDO::PARAM_INT: rappresenta un tipo di dato numerico intero (SQL INTEGER); PDO::PARAM_STR: rappresenta un tipo di dato stringa (SQL CHAR, VARCHAR, TEXT..); PDO::PARAM_LOB: rappresenta un tipo di dato large object di SQL. Il binding dei parametri consnetirà quindi di eseguire il Prepared statement tramite il metodo execute() $sql->execute();