L’implementazione della versione 5.5 di PHP prevede che le mysql functions native del linguaggio vengano considerate deprecate, ciò significa che lo sviluppatore dovrà utilizzare in loro sostituzione le estensioni PDO o MySQLi; entrambe queste ultime supportano funzionalità più avanzate rispetto alla loro alternativa in dismissione, come per esempio la possibilità di definire prepared statements (o, meno comunemente, parameterized statements), dei costrutti, supportati da MySQL a partire dalla versione 4.1, che consentiranno alle applicazioni di interagire in modo sicuro ed efficiente con il Database manager attraverso le query.
Nel caso specifico di questa trattazione, verranno descritte le procedure necessarie per parametrare dichiarazioni tramite MySQLi.
Funzionamento dei prepared statements
Quando si affronta il discorso relativo ai prepared statements, l’accento viene posto in particolare sugli innegabili vantaggi che questo tipo di istruzioni offrono dal punto di vista delle sicurezza. Si tratta naturalmente di un aspetto non secondario, ma parametrare le dichiarazioni significa in primo luogo sfruttare un meccanismo che permetterà di eseguire ripetutamente le medesime query (o query tra loro simili) ad un livello di prestazioni più elevato, il criterio relativo all’efficienza non dovrebbe quindi essere ignorato.
I prepared statements funzionano sulla base di uno schema che prevede innanzitutto la definizione di un template da parte di un’applicazione, quest’ultimo verrà utilizzato per le dichiarazioni in quanto presenterà dei parametri che svolgeranno la funzione di placeholder e verranno via via sostituiti con i valori reali in corso d’esecuzione; tali parametri sono detti “variabili bind” e permetteranno di non specificare a priori i valori in modo da permettere la costruzione di un modello riutilizzabile.
Un semplice esempio di template per la parametrizzazione delle dichiarazioni potrebbe essere il seguente, dove i placeholder sono simboleggiati da dei punti interrogativi:
INSERT INTO tbl (cmp1, cmp2) VALUES (?, ?)
La fase relativa al confezionamento del template e al suo invio al DBMS prende il nome di “preparazione”, essa porterà il Database engine ad effettuare il parsing della dichiarazione e ad archiviarne il risultato senza per questo eseguire alcuna istruzione; nel corso della passaggio successivo, detto “esecuzione”, i parametri utilizzati come segnaposto verranno sostituiti con dei valori associati a variabili grazie ad un processo vincolante denominato “binding” e il Database manager potrà eseguire l’istruzione prevista.
Vantaggi e svantaggi dei prepared statements
Dal punto di vista dello sviluppatore, uno dei vantaggi relativi all’utilizzo dei prepared statements è identificabile nel fatto che, attraverso di essi, la logica delle istruzioni SQL verrà separata dal dataset, rendendo i sorgenti più leggibili. Ma è ancora più importante il discorso relativo alle prestazioni, infatti tali costrutti consentiranno di limitare il verificarsi di overhead (cioè richiesta di risorse accessorie) in quanto le procedure di compilazione e ottimizzazione della dichiarazione verranno eseguite una volta sola attraverso il parsing del template passato al DBMS e archiviato in memoria.
Per contro, andrebbe sottolineato che le fasi addizionali richieste dalla parametrizzazione potrebbero rendere meno performanti le esecuzioni relative ad istruzioni non ripetute, preparare una dichiarazione per una singola interazione potrebbe quindi non risultare ideale per quanto riguarda le prestazioni, ma ciò risulterebbe vero soltanto se non si prendesse in considerazione il fondamentale aspetto legato alla sicurezza.
I prepared statements costituiscono infatti una protezione contro gli attacchi basati su SQL injections, ciò è una conseguenza del fatto che in tali dichiarazioni l’SQL e i dati rimangono separati; in pratica, anche in presenza di tentativi di SQL injections questi non potranno avere successo, in quanto le istruzioni malevole non entreranno a far parte del template e i dati verranno manipolati al di fuori delle istruzioni basate su SQL.
Sintassi dei prepared statements in MySQLi
Per formulare una semplice query finalizzata all’estrazione di dati da una tabella tramite la sintassi di MySQLi, potrebbe essere sufficiente un’istruzione come la seguente:
$query = $mysqli->query("SELECT nome, cognome FROM agenda WHERE record_id = 5");
Nell’esempio precedente non sono stati utilizzati i prepared statements, come si può notare infatti la logica dell’SQL non è stata separata dai dati, il valore relativo alla condizione della clausola WHERE non è rappresentato da un placeholder e al DBMS non è stato passato il template di una dichiarazione ma un’istruzione SQL vera e propria. Volendo parametrare la dichiarazione proposta, la si potrebbe riformulare in questo modo:
// preparazione del template
$pst = $mysqli->prepare("SELECT nome, cognome FROM agenda WHERE record_id = ?");
// definizione della variabile per la sostituzione del placeholder
$pst->bind_param('i', $record_id);
// valorizzazione della variabile per l’esecuzione
$record_id = 5;
// esecuzione dell’istruzione
$pst->execute();
In pratica, l’esempio appena riportato mostra la fase relativa alla preparazione con la relativa definizione del template, ad essa faranno seguito l’associazione del parametro per la sostituzione del placeholder (“?”) e, infine, l’esecuzione dell’istruzione sulla base del modello memorizzato da MySQL.
Mentre il funzionamento de metodi prepare()
ed execute
dovrebbe risultare sufficientemente intuitivo, bind_param()
merita un ulteriore approfondimento; quest’ultimo infatti si occuperà di effettuare il binding delle variabili accettando due argomenti: il primo (nel caso specifico “i”) destinato a definire il tipo di dato associato ad un valore atteso, il secondo indicante la variabile a cui tale valore dovrà essere associato.
Ecco perché, nell’esempio, il placeholder “?” verrà sostituito dal valore “5” utilizzato per inizializzare la variabile intera $record_id
. Il numero dei parametri da passare al metodo citato dipenderà direttamente dalla quantità di campi coinvolti dalla query.
I caratteri per specificare i tipi di dato
L’utilizzo di “i” per l’indicazione di un tipo di dato non è il frutto di una scelta arbitraria o di una semplice convenzione, ma di uno schema di corrispondenze previsto a livello sintattico che vede anche:
- “d” corrispondere a variabili associate al tipo di dato double;
- “s” a variabili associate al tipo di dato string;
- “b” corrispondere a blob, cioè a dati in formato binario.
I caratteri per la specifica del tipo di dato dovranno essere ripetuti tante volte quante sono le variabili da valorizzare e ordinati sulla base di queste ultime, come nel caso concreto proposto di seguito:
// preparazione del template
$pst = $mysqli->prepare("INSERT INTO agenda (nome, cognome, paese) VALUES (?, ?, ?)");
// variabili per la sostituzione dei placeholder
$pst->bind_param('sss', $nome, $cognome, $paese);
// variabili per l’esecuzione
$nome = 'Homer';
$cognome = 'Simpson';
$paese = 'USA';
// esecuzione dell’istruzione
$pst->execute();
// estrazione del resultset
$result = $pst->get_result();
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
var_dump($row);
}
// chiusura dello statement
$pst->close();
Nell’esempio proposto il primo parametro passato come argomento al metodo bind_param()
dovrà essere questa volta sss
, sarà infatti necessario indicare che le tre variabili per il binding ($nome
, $cognome
e $paese
) dovranno essere tutte associate ad un tipo di dato stringa. Si dovrà agire in modo analogo nel caso in cui si preveda di passare a bind_param()
caratteri differenti per la specifica del tipo di dato, come accade nell'esempio successivo:
// preparazione del template
$pst = $mysqli->prepare("INSERT INTO agenda VALUES (?, ?, ?, ?)");
// variabili per la sostituzione dei placeholder
$pst->bind_param('sssi', $nome, $cognome, $paese, $anni);
// variabili per l’esecuzione
$nome = 'Apu';
$cognome = 'Nahasapeemapetilon';
$paese = 'India';
$anni = 28;
// esecuzione dell’istruzione
$pst->execute();
// chiusura dello statement
$pst->close();
In questo caso il codice proposto presenta un utilizzo combinato dei caratteri per la specifica del tipo di dato; il primo parametro del metodo bind_param() dovrà infatti essere “sssi” (tre stringhe e un intero) questo perché le variabili per il binding ($nome
, $cognome
, $paese
e $anni
) non risultano essere tutte associate allo stesso tipo di dato.
È da notare che, a differenza di quanto accadrebbe per i valori in assenza di prepared statements, i placeholder non vengono delimitati da apici anche se destinati ad essere associati a tipi di dato che li richiederebbero, come per esempio le stringhe, ciò accade in quanto le diverse tipologie di dato vengono stabilite successivamente e al di fuori della definizione del template.
Conclusioni
In questa trattazione è stato affrontato il discorso relativo all'adozione dei prepared statements tramite l'estensione MySQLi di PHP, essi sono dei costrutti che permetteranno alle applicazioni di interagire con il DBMS MySQL in modo più performante è sicuro, soprattutto nel caso in cui si debbano gestire istruzioni ripetitive.