Estensione PDO

PDO, guida completa all'estensione PHP

Per accedere ad una base dati PHP offre due estensioni: MySQLi che abbiamo visto nella precedente guida e PDO che analizzeremo nel dettaglio in questa nuova guida.

Cos'è PDO

PHP Data Objects (PDO) è un estensione PHP che fornisce un livello di astrazione di accesso ai dati, il che vuol dire che, indipendentemente dal database in uso, vengono utilizzate sempre le stesse funzioni per eseguire le query. PDO supporta più di 12 diversi DBMS attraverso l'utilizzo di uno driver specifico per quel database.

L'estensione è disponibile da PHP 5.1 e necessita di alcune caratteristiche tipiche della programmazione ad oggetti (OO) disponibili a partire dalla versione 5.0, quindi non funziona con versioni precedenti del linguaggio.

Database supportati

La seguente tabella elenca i DBMS supportati da PDO con relativo driver

Nome del driver Database supportati
PDO_CUBRID Cubrid
PDO_DBLIB FreeTDS / Microsoft SQL Server / Sybase
PDO_FIREBIRD Firebird
PDO_IBM IBM DB2
PDO_INFORMIX IBM Informix Dynamic Server
PDO_MYSQL MySQL 3.x/4.x/5.x
PDO_OCI Oracle Call Interface
PDO_ODBC ODBC v3 (IBM DB2, unixODBC e win32 ODBC)
PDO_PGSQL PostgreSQL
PDO_SQLITE SQLite 2 e SQLite 3
PDO_SQLSRV Microsoft SQL Server / SQL Azure
PDO_4D 4D

Anche se PDO offre la possibilità di cambiare DBMS senza dover riscrivere il codice, nella realtà non vi è quasi mai necessità di cambiare DBMS in corso d'opera. Tuttavia PDO presenta alcuni indiscutibili vantaggi che elenchiamo nel paragrafo successivo.

Vantaggi

PDO offre numerosi vantaggi che riguardano soprattuto la sicurezza delle applicazioni web e la fase di sviluppo e debug:

  • Sicurezza: tramite i prepared statement è possibile prevenire la SQL Injection.
  • Riusabilità: offre un'unica intefaccia per accedere a differenti database.
  • Flessibilità: consente una gestione flessibile degli errori per la fase di debug.
  • Facilità di utilizzo: rispetto a MySQLi sono necessarie solo due istruzioni per eseguire query in maniera sicura.
  • Usabilitàoffre numerosi helpers per compiere varie operazioni di routine.
  • Supporto per i named parameters (:param) e i positional parameters (?) anche su database che non li supportano nativamente.

Nei paragrafi successivi vediamo nella pratica alcuni di questi vantaggi, iniziando dalla connessione al database e le classiche operazioni CRUD su di esso.

Installazione

Per poter utilizzare l'estensione PDO è necessario installare la versione specifica del database che si intende utilizzare. Per esempio nel caso di MySQL, dovrà essere installata l'estensione pdo_mysql e abilitarla dal file di configurazione php.ini.

Sarà sufficiente aprire con un editor il file php.ini, cercare la seguente riga

;extension=pdo_mysql

quindi rimuovere il punto e virgola iniziale, salvare il file e riavviare Apache per rendere effettive le modifiche.

Connessione al database

Per aprire la connessione al database è necessario creare un'instanza della classe PDO, nell'esempio che segue e in quelli successivi utilizzeremo il driver di MySQL per connetterci al database

<?php
try{
    $pdo = new PDO("mysql:host=localhost;dbname=shop", "test", "pass1234");
    // Set the PDO error mode to exception
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
    die("ERRORE: Impossibile stabilire una connessione al database");
}

Come possiamo notare abbiamo introdotto due nuovi costrutti, uno per creare un nuovo oggetto tramite la keyword new e il costrutto try/catch per gestire le eccezioni, argomenti che vedremo nella guida avanzata.

In questa guida ci limitiamo a dire che il costrutto try/catch serve per interrompere l'esecuzione di un programma qualora l'istruzione contenuta dentro il blocco try dovesse generare un errore a runtime. In questo caso anziché mostrare all'utente un errore fatale oppure un warning, viene visualizzato un messaggio personalizzato e più leggibile che migliora l'usabilità dell'applicazione web.

Ritornando al nostro codice, alla riga 3 creiamo un'istanza della classe PDO rappresentante una connessione al database shop da parte dell'utente test con password pass1234, mentre host è uguale all'indirizzo del server MySQL. Nel nostro caso lavoriamo in locale, quindi il valore da inserire è localhost.

Come per le funzioni, anche per gli oggetti è possibile passare degli argomenti, in questo caso chiamati parametri.

Nel caso di PDO abbiamo:

PDO ($dsn, $username, $passwd, $options)

  • $dsn:  Data Source Name, rappresenta le informazioni che indicano ad un programma come connettersi ad una determinata fonte dati tramite un driver. In generale, un DSN è costituito dal nome del driver PDO, seguito da due punti e dalla sintassi di connessione specifica del driver PDO.
  • $username: lo username dell'utente che deve connettersi alla base dati
  • $passwd: la password dell'utente che deve connettersi alla base dati
  • $options: Un'array associativo contentente alcune opzioni di connessione specifiche del driver.

Le opzioni possono essere impostate pure con il metodo setAttribute() della riga 6.

Nota: l'impostazione dell'attributo PDO::ATTR_ERRMODE su PDO::ERRMODE_EXCEPTION dice a PDO di generare eccezioni ogni volta che si verifica un errore.

Un'altra opzione largamente usata è

PDO::MYSQL_ATTR_INIT_COMMAND

Ad esempio, per specificare l'utilizzo di caratteri UTF-8 è possibile scrivere il seguente codice

<?php
try{
    $pdo = new PDO(
        "mysql:host=localhost;dbname=shop", 
        "test", 
        "pass1234", 
        [PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"] // La connessione utilizzerà la codifica UTF-8
    );
    // Set the PDO error mode to exception
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e){
    die("Impossibile stabilire una connessione al database: " . $e.getMessage());
}

Una volta che abbiamo stabilito la connessione al database è possibile eseguire operazioni come inserimento, selezione, modifica e cancellazione.

Eseguire query

Eseguire query con PDO è molto semplice, è possibile utilizzare il metodo exec() per effettuare interrogazioni SQL di tipo INSERT, CREATE, UPDATE e DELETE, mentre il metodo query() consente di eseguire interrogazioni SELECT, che ci consentono di estrarre i dati dalla nostra base dati.

Diamo un'occhiata ad alcuni esempi di utilizzo. Per prima cosa creiamo la tabella prodotti nel nostro database shop

<?php
$sql = "CREATE TABLE `shop`.`prodotti` (
    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    `nome` VARCHAR(300) NOT NULL,
    `prezzo` DECIMAL(6,2) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;";
$result = $pdo->exec($sql);

if ($result) {
    echo "Tabella creata con successo";
}

Il codice precedente non fa altro che eseguire l'istruzione CREATE TABLE che consente la creazione di una nuova tabella MySQL.

In questo caso abbiamo usato il metodo exec() che prende come parametro un'istruzione SQL e restituisce il numero di righe interessate

<?php
$sql = 'INSERT INTO prodotti (nome, prezzo) VALUES ("sapone liquido 100ml", 8.50)';
$affectedRows = $connection->exec();

echo $affectedRows;

Il metodo query() viene usato per eseguire istruzioni SQL SELECT, vediamo un esempio

<?php
$sql = 'SELECT nome, prezzo FROM prodotti';
$products = $conn->query($sql);

foreach ($products as $product) {
    echo $product['nome'] . " " . $product['prezzo'] . "<br>";
}

Anche se può sembrare comodo, l'utilizzo di exec() e query() è sconsigliato per motivi di sicurezza, in quanto potenzialmente vulnerabili ad attacchi di tipo SQL Injection.

Introduciamo altri due metodi della classe PDO che consentono di eseguire query SQL in maniera sicura.

I metodi prepare() e execute() e fetchAll()

Per eseguire interrogazioni SQL in maniera sicura è consigliabile utilizzare in combinazione i metodi prepare() e execute() della classe PDO.

Il metodo prepare() prende in input un'istruzione SQL e restituisce un oggetto di tipo PDOStatement. Quest'oggetto può essere utilizzato per eseguire una query SQL.

Vediamo subito un esempio in combinazione con il metodo execute() per eseguire una SELECT dalla tabella prodotti

<?php
$sql = 'SELECT nome, prezzo FROM prodotti';
$sth = $conn->prepare($sql);
$result = $sth->execute();

if (! $result) {
    die('Errore esecuzione query: ' . implode(',', $conn->errorInfo()));
}

$products = $sth->fetchAll(PDO::FETCH_OBJ);

foreach ($products as $product) {
    echo $product->nome . " " . $product->prezzo . "<br>";
}

Una volta che è stata eseguita con successo la query attraverso l'istruzione execute(), che restituisce true in caso di successo, possiamo recuperare il risultato tramite il metodo fetchAll().

L'istruzione fetchAll() consente di recuperare tutti risultati della query SELECT, restituendo i valori sotto forma di oggetti della classe stdClass del linguaggio, la costante PDO::FETCH_OBJ passata come parametro alla funzione fetchAll() consente di fare questo.

Se invece vogliamo farci restituire i risultati in un'array associativo dove ciascun indice rappresenta il nome della colonna della tabella MySQL dobbiamo usare la costante PDO::FETCH_ASSOC al posto di PDO::FETCH_OBJ e per accedere al dato dobbiamo sostituire

$product['nome'] al posto di $product->nome

Il codice riportato in precedenza può essere usato per eseguire tutte le tipologie di interrogazioni SQL, dalla SELECT, alle INSERT, UPDATE e DELETE.

Vediamo ora alcuni esempi di codice in cui utilizziamo prepare() e execute() sfruttando i cosiddetti prepared statement.

I prepared statement

I prepared statement, letteralmente istruzioni preparate, consentono di "preparare" una query SQL utilizzando dei marcatori al posto dei dati. Tali marcatori sono rappresentati da un nome di variabile preceduto da due punti (named parameters), oppure da un punto interrogativo (positional parameters).

Vediamo meglio un esempio di utilizzo

<?php
$sql = 'SELECT nome, prezzo FROM prodotti WHERE id = :id';
$params = ['id' => 2];
$sth = $conn->prepare($sql);
$result = $sth->execute($params);

if (! $result) {
    die('Errore esecuzione query: ' . implode(',', $conn->errorInfo()));
}

$product = $sth->fetch();

if ($product) {
    echo $product['nome'] . " " . $product['prezzo'];
}

Nella query alla riga 2, abbiamo utilizzato il marcatore :id al posto del valore 2 che invece abbiamo inserito in un'array di parametri alla riga 3.

PDO non fa altro che sostituire il marcatore presente nella query con il relativo valore presente nell'array $params.

La sicurezza nell'utilizzo delle prepared statement sta nel fatto che in caso di valori provenienti da un input utente, tramite ad esempio la variabile $_GET, viene scongiurato l'inserimento di stringhe direttamente nella query da parte di malintenzionati che potrebbero hackerare la nostra applicazione.

Conclusioni

Per chiudere questa guida possiamo dire che per l'accesso al database è preferibile utilizzare la classe PDO piuttosto che MySQLi, poiché garantisce una grande portabilità del codice grazie all'astrazione offerta dall'estensione stessa. Inoltre prepara chi è alle prime armi a iniziare a prendere confidenza con la programmazione ad oggetti, argomento di cui parleremo nella guida avanzata.

Con PDO abbiamo terminato la serie di guide base. Nelle prossime affronteremo argomenti più complessi tra cui le espressioni regolari, le eccezioni, la programmazione ad oggetti e tanto altro.