Introduzione a PHP PDO

Cosa significa PDO?

PDO è l’acronimo di PHP Data Objects, definisce un layer di astrazione dal database (Database Access Abstraction Layer) ed è stato rilasciato con PHP 5.1. Generalmente, per accedere alle funzioni di un database, si utilizza una sintassi propria del database con il quale dobbiamo interfacciarci.

Con MySQL ad esempio, per ottenere tutti i record della tabella users, si utilizza:

“SELECT * FROM users”

Questa sintassi però ci lascia esposti alle SQL Injections.

PDO offre una interfaccia unica per tanti tipi di database relazionali, molto soprattutto nei casi in cui si debba sviluppare applicazioni in grado di appoggiarsi a differenti database.

Al contrario di quanto si può trovare in rete, è impossibile cambiare tipo di database modificando solamente una riga nella configurazione del PDO, ma bisognerà far riferimento a driver specifici per ogni tipo di database.

L’astrazione non riguarda solamente le API dei database, ma anche le operazioni che altrimenti andrebbero ripetute centinaia di volte all’interno dell’applicazione.

Connessione con DSN

Per poter essere utilizzato, PDO sfrutta un metodo chiamato DSN, cioè chiede in input una determinata configurazione che utilizzerà nelle chiamate al DB:

'database driver:host,db (schema) name,port,charset'

Nel caso di MySQL:

'mysql:host=x;dbname=y;port=3306;charset=utf8mb4'

Scritto proprio così, senza spazi, virgolette o decorazioni particolari come indicato nel manuale.

Questo è il DSN di cui PDO ha bisogno. Se sono presenti, si possono utilizzare anche username e password per l’accesso al DB direttamente nell’istanza:

$pdo = new PDO('mysql:host=x;dbname=y;port=3306;charset=utf8mb4', 'username', 'password');

Volendo, si può aggiungere anche un array che contiene opzioni aggiuntive:

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

Per convenzione, l’istanza al PDO si assegna alla proprietà (privata) $pdo.

  • Contrariamente alle vecchie funzioni mysqli_*, che possono essere usate ovunque nel codice, l’istanza PDO viene salvata all’interno di una variabile private che la rende inaccessibile a meno che non la si passi come parametro all’interno delle funzioni.
  • La connessione al DB deve essere effettuata una sola volta, e non una volta per ogni funzione o in ogni costruttore di classe. In caso contrario, con le connessioni multiple può succedere che il server DB possa cedere.
  • Molto importante è fissare il set di catatteri charset attraverso DSN, ed è questo il modo giusto perché così facendo, specifichiamo a PDO quale set di caratteri usare (e deve SEMPRE essere UTF-8). In alternativa, ma SOLO se la versione di PHP è vecchia (sconsigliato!), si può usare l’atrtibuzione del charset attraverso l’array delle opzioni:
// questo non è consigliato!
$options = [
    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF-8\''
];

Ecco un esempio specifico per MySQL:

$host = '127.0.0.1';
$port = '3306';
$db   = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;port=$port;dbname=$db;charset=$charset";

$pdo = new PDO($dsn, $user, $pass);

Meglio indicare direttamente l’indirizzo IP e porta al posto di localhost, per evitare latenze dovute al DNS lookup!

Le query. PDO::query()

Esistono 2 modi di effettuare le query tramite PDO. Nel caso in cui nessuna variabile dovesse essere passata, nella query, si può usare il metodo diretto PDO::query(), che restituisce un oggetto della PDOStatement class che può essere paragonato ad una risorsa simile a quella restituita da mysqli_query().

$stmt = $pdo->query('SELECT name FROM users'); // statement
while ($row = $stmt->fetch())
{
    echo $row['name'] . "\n";
}

Protezione da SQL Injections con Prepared Statements

Il motivo principale per abbandonare MySQLi è la facilità con cui si incorre nella SQL Injection. Per evitare questo tipo di problemi e quindi rendere sicure le istruzioni da passare ai DB, con PDO si introducono i prepared statements. In questo modo, se c’è necessità di passare variabili nella query, si blinda la query stessa sostituendo la variabile con un suo segnaposto, preparando la query e in un secondo momento eseguendo la stessa, passando le variabili separatamente.

Per fare questo abbiamo bisogno, nella maggior parte dei casi, solo di due funzioni: prepare() e execute().

Questo è ciò che faremmo con MySQLi:

$sql = "SELECT * FROM users WHERE username = '$username' AND group = '$group'";

Bisogna quindi modificare la query a cui si è abituati, utilizzando i segnaposto già descritti.

$sql = "SELECT * FROM users WHERE username = ? AND group = ?";

I segnaposto “?” vengono definiti “positional placeholders“, e vengono accettanti anche da MySQLi.

Un altro modo di eseguire la stessa query è quello di utilizzare i “named placeholders” ossia parametri nominali che si indicano con il simbolo due punti “:” seguito da un nome (stringa). Es. “:username“.

$sql = "SELECT * FROM users WHERE email = :username AND group = :group";

I “named placeholders” accettano solamente lettere, numeri e il carattere undescore, e non sono supportati da MySQLi.

Bisogna inoltre notare che non si possono mescolare segnaposto posizionali e nominali nella stessa query (si usa uno stile oppure l’altro, ma in maniera pura) e in nessun caso non viene fatto uso del carattere apice per definire i segnaposto.

Ora, con query scritte in questo modo, la prima cosa da fare è prepararle all’utilizzo con il metodo PDO::prepare(). Si preparano una sola volta ma si possono eseguire all’occorrenza un numero indefinito di volte, con gli stessi parametri o con parametri diversi.

Una volta che la query è pronta, il database la analizzerà, la compilerà e ottimizzerà le sue funzioni in attesa di eseguire quella determinata query. Utilizzare i prepared statements serve anche ad evitare che l’applicazione ripeta il ciclo “analisi/compilazione/ottimizzazione” ogni volta.

Eseguendo entrambe le query, si può notare che restituiscono lo stesso oggetto PDOStatement ma senza alcun parametro associato. I parametri verranno passati successivamente, e in maniera separata dallo statement.

Non rimane altro che eseguire la query con PDOStatement::execute(), passando le variabili sotto forma di array.

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ? AND group = ?');
$stmt->execute([$username, $group]);
$user = $stmt->fetch();

// oppure

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND group = :group');
$stmt->execute(['username' => $username, 'group' => $group]);
$user = $stmt->fetch();

Nel caso di positional placeholders, passiamo un normale array di valori, mentre nel caso dei named placeholders si passa un associative array formato da coppie chiave -> valore in cui le chiavi devono corrispondere ai nomi usati nello statement.

La differenza è evidente: utilizzando i positional placeholders scriviamo meno codice ma nell’array c’è da rispettare la posizione dei parametri, mentre con i named placeholders scriviamo più codice ma non è importante l’ordine in cui si scrivono le coppie chiave -> valore.

Attenzione però: nella creazione dell’array associativo, è importantissimo NON UTILIZZARE i due punti che invece abbiamo usato per la preparazione dello statement.

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND group = :group');
$stmt->execute(['username' => $username, 'group' => $group]);
$user = $stmt->fetch();
// ['username' e non ':username']

In seguito all’execute() possiamo iniziare a recuperare i dati ottenuti dalla query con PDOStatement::fetch().

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.