Interazione PHP-MySQL: l’estensione MySQLi

da html.it

MySQLi: scegliere l’interfaccia

Abbiamo analizzato le principali differenze tra le diverse librerie messe a disposizione da PHP per l’interazione con il DBMS MySQL; partiamo quindi esaminando il funzionamento di MySQLi (MySQL improveded). Come anticipato, una caratteristica peculiare di questa estensione è quella di disporre di una doppia interfaccia: procedurale e ad oggetti.

Questo significa che, sfruttando l’approccio procedurale, abbiamo a disposizione una sintassi estremamente simile a quella della prima estensione fornita nativamente dal linguaggio per MySQL, le cosiddette mysql functions, aspetto che può risultare rilevante per chi era già avvezzo a queste ultime e intende modificare il meno possibile il proprio codice. Ricordiamo però che le mysql functions non vanno più considerate un’alternativa valida, non sono incluse in PHP 7 e vengono sconsigliate dalla stessa comunità degli sviluppatori di PHP anche perché prive di una propria interfaccia ad oggetti.

Non ci sono particolari differenze fra i due stili di programmazione a livello di prestazioni, ed è quindi possibile scegliere liberamente l’uno o l’altro; dobbiamo tuttavia tenere presenti i vantaggi della programmazione object oriented: il codice è più facilmente riutilizzabile nonché più facilmente comprensibile nella misura in cui programmi, anche molto complessi, possono essere scissi in parti indipendenti che dialogano fra loro; per lo stesso motivo anche il debugging e le modifiche, così come un’eventuale estensione delle funzionalità, sono più semplici da eseguire. Il paradigma OOP si rivela poi particolarmente utile in sede di sviluppo collaborativo.

Nel corso della guida per gli esempi di codice ci rifaremo sempre alla programmazione orientata agli oggetti, ma se siamo interessati alla possibilità di usare l’approccio procedurale, questa pagina della documentazione ufficiale risulterà un utile strumento poiché mette in parallelo i metodi della classe con le rispettive funzioni presenti nella libreria.

Istanziare la classe per connettersi al database

Per iniziare a lavorare con la libreria è necessario innanzitutto istanziare la relativa classe: il costruttore di MySQLi utilizzerà i parametri da noi forniti, o quelli di default se non ne vengono specificati altri, per aprire la connessione con il DBMS, essa sarà il nostro tramite per comunicare di volta in volta le operazioni da svolgere sul database. I metodi connect_error()e connect_errno() potrebbero esserci utili per controllare l’avvenuta connessione:

$mysqli = new mysqli('localhost', 'username', 'password', 'nome_database');
    if ($mysqli->connect_error) {
        die('Errore di connessione (' . $mysqli->connect_errno . ') '
        . $mysqli->connect_error);
    } else {
        echo 'Connesso. ' . $mysqli->host_info . "\n";
    }

Questo semplice codice apre la connessione creando un nuovo oggetto, e poi la verifica tramite il metodo connect_error(): nel caso si verifichi un errore lo script viene interrotto stampando a schermo il codice di errore (connect_errno) e la sua descrizione, mentre se la connessione viene stabilita viene invocato il metodo host_info che restituisce una stringa con l’host del DBMS e il metodo di connessione. L’output più comune nei sistemi Unix-like risulterà essere:

Connesso. Localhost via UNIX socket

I parametri da passare al costruttore sono soltanto 4 più un argomento opzionale:

  1. l’host, cioè l’indirizzo della macchina in cui sta girando MySQL, indicato tramite il nome o l’indirizzo IP (in locale nella stragrande maggioranza dei casi si tratterà di localhost);
  2. il nome utente dell’utilizzatore abilitato ad inviare istruzioni al DBMS sulla base dei permessi che gli sono stati accordati;
  3. la password associata al nome utente utilizzato;
  4. il nome del database;
  5. eventualmente la porta (di base impostata su 3306) e il socket (di default nei sistemi Unix-like è impostata come /tmp/mysql.sock).

Concentrandoci sull’aspetto pratico non è necessario dettagliare il significato di questi ultimi due parametri: teniamo presente che gli ambienti di lavoro più comuni, ad esempio lo sviluppo in locale con strumenti come WAMP, LAMP o XAMPP, o un hosting remoto con servizio database, forniscono nella loro documentazione la configurazione completa per la connessione al DBMS.

Naturalmente il codice proposto è consigliabile soltanto per i siti Web in fase di sviluppo, e non per quelli in produzione. Terminare lo script con un die() e fornire le informazioni di sistema sull’errore che si è verificato è decisamente sconsigliato per qualsiasi applicazione pubblicamente accessibile, motivo per il quale questo controllo potrà essere disattivato prima del deployment una volta completata l’applicazione.

Per mostrare il funzionamento dei principali metodi disponibili per la classe MySQLi inizieremo a costruire una semplice applicazione Web. L’esempio che proponiamo è quello della gestione di una biblioteca. I dati sui quali opereremo saranno quindi relativi ai libri a disposizione nel catalogo, agli utenti abilitati, e ai prestiti passati e in corso, oltre alle credenziali degli amministratori.

Per iniziare progettiamo la struttura di quattro tabelle (login di accesso, libri, utenti e prestiti) con i relativi campi che ci consentiranno di creare delle relazioni fra loro.

Creiamo quindi la connessione a DBMS, ma stavolta senza specificare il perché vogliamo crearne uno apposito per l’applicazione, poi utilizziamo uno dei metodi più importanti della classe, query:

$mysqli = new mysqli('localhost', 'root', 'password');
if ($mysqli->connect_error) {
    die('Errore di connessione (' . $mysqli->connect_errno . ') . $mysqli->connect_error);
}
// Creo il database
$mysqli->query("CREATE DATABASE biblioteca");
// Seleziono il database
$mysqli->query("USE biblioteca");

Il metodo query accetta come parametro la query SQL da passare a MySQL, e restituisce il booleano TRUE a meno che la query generi un errore. Nel codice proposto l’abbiamo utilizzato prima per creare il database dell’applicazione e poi per selezionare quest’ultimo come nostro ambiente di lavoro.

Il metodo error può essere usato di seguito per ottenere la stringa di errore eventualmente generata da MySQL. Supponiamo ad esempio di scrivere:

if (!$mysqli->query("CRREATE DATABASE biblioteca")) {
    die($mysqli->error);
}

In questo caso si tratta di un semplice errore di battitura del comando CREATE. Otterremo quindi come risultato l’arresto dello script e la stampa a schermo dell’errore restituito da MySQL:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CRREATE DATABASE biblioteca' at line 1

Un esito che naturalmente eviteremmo per il sito in produzione e che prendiamo in considerazione solo per la fase di debug.

La creazione delle tabelle

Procediamo sempre col metodo query per la creazione delle tabelle necessarie, e richiamando la keyword SQL CREATE:

// creazione della tabella per il login
$mysqli->query("CREATE TABLE 'login'
                           ( 'id' INT ( 5 ) NOT NULL AUTO_INCREMENT,
                             'user' VARCHAR(40) NOT NULL,
                             'password' VARCHAR(64) NOT NULL,
                             PRIMARY KEY ('id'))");
// creazione della tabella per i libri
$mysqli->query("CREATE TABLE 'libri'
                           ( 'id' INT(5) NOT NULL AUTO_INCREMENT,
                             'autore' VARCHAR(40) NOT NULL,
                             'titolo' TEXT NOT NULL,
                             'editore' VARCHAR(40) NOT NULL,
                             'anno' SMALLINT(2) NOT NULL,
                             PRIMARY KEY ('id'))");
// creazione della tabella per gli utenti
$mysqli->query("CREATE TABLE 'utenti'
                           ( 'id' INT(5) NOT NULL AUTO_INCREMENT ,
                             'nome' VARCHAR(30) NOT NULL ,
                             'cognome' VARCHAR(30) NOT NULL ,
                             'indirizzo' TEXT NOT NULL ,
                             'nascita' DATE NOT NULL ,
                             PRIMARY KEY ('id'))");
// creazione della tabella per i prestiti
$mysqli->query("CREATE TABLE 'prestiti'
                           ( 'id' INT NOT NULL AUTO_INCREMENT ,
                             'id_utente' INT NOT NULL ,
                             'id_libro' INT NOT NULL ,
                             'data' DATE NOT NULL ,
                             'restituito' ENUM('0','1') NOT NULL ,
                             PRIMARY KEY ('id'))");

Ricordiamo velocemente che l’id di ogni tabella, un valore univoco automaticamente incrementato dal DBMS, è lo strumento con il quale costruiremo le relazioni che ci permetteranno di collegare gli utenti ai prestiti in corso, e questi, a loro volta, ai libri.

È naturale per un’applicazione Web con un’interfaccia di backend verificare l’accesso degli operatori autorizzati tramite le credenziali memorizzate in una apposita tabella di login. Per quest’ultima, lo ricordiamo, abbiamo predisposto i campi id, user e password.

Nel campo password non inseriremo comunque del testo in chiaro, ma l’hash di 64 caratteri della password generato tramite la funzione SHA256 disponibile nativamente in PHP.

Inserimento dei dati nelle tabelle

L’inserimento dei dati avviene tramite query SQL con la parola chiave INSERT processata da MySQLi tramite il metodo query():

// Connessione al database
$mysqli = new mysqli('localhost', 'root', 'root', 'biblioteca');
if ($mysqli->connect_error) {
    die('Errore di connessione (' . $mysqli->connect_errno . ') '
    . $mysqli->connect_error);
}
$password = hash('sha256', 'paperino'); //Creazione dell'hash
$query = "INSERT INTO login (user, password) VALUES ('pippo', '$password')";
// Esecuzione della query e controllo degli eventuali errori
if (!$mysqli->query($query)) {
    die($mysqli->error);
}

Abbiamo detto in precedenza che il metodo query() restituisce il valore false nel caso in cui MySQL risponda alla richiesta con un messaggio di errore per il debug. Non è tuttavia l’unico dato che possiamo ricavare dopo l’esecuzione della query, altre due informazioni che potrebbero risultare molto utili sono il numero delle righe generate e, trattandosi di un inserimento all’interno di una tabella con una campo autoincrement, l’ultimo ID inserito.

Per testare il semplice funzionamento dei metodi affected_rows() e insert_id(), applicabili all’istanza della classe mysqli_result ottenuta come risultato dal metodo query, al termine del codice possiamo aggiungere queste righe:

echo "Righe generate: " $mysqli->affected_rows . "<br />";
echo "Ultimo ID inserito: " . $mysqli->insert_id  . "<br />";

Che stamperanno a schermo (naturalmente in assenza di errori):

Righe generate: 1
Ultimo ID inserito: 1

Verifica delle credenziali

Per verificare la correttezza delle credenziali dobbiamo passare alla lettura dei dati, utilizzando come parametri quelli inseriti dall’utente verosimilmente da un form. Un esempio di codice potrebbe essere il seguente:

// Connessione al database
$mysqli = new mysqli('localhost', 'root', 'root', 'biblioteca');
if ($mysqli->connect_error) {
    die('Errore di connessione (' . $mysqli->connect_errno . ') '
    . $mysqli->connect_error);
}
$user = "pippo"; // Normalmente questi valori sarebbero ottenuti tramite POST
$password = hash('sha256', "paperino");
$query = $mysqli->query("SELECT * FROM login WHERE user = '$user' AND password = '$password'");
if($query->num_rows) {
    echo "Accesso consentito";
} else {
    echo "Accesso rifiutato";
}

Il metodo num_rows(), sempre applicabile alla classe mysqli_result, restituisce il numero di righe ottenute dalla select e avremo quindi un risultato superiore a 0 solo nel caso in cui le credenziali siano valide.

La classe mysqli_result dispone di metodi per lavorare sui dati ottenuti, ad esempio per ottenere un array con i record ottenuti dalla query possiamo utilizzare il metodo fetch_all:

if($query->num_rows) {
    echo "<pre>"; // Il tag pre rende facilmente leggibile l'array
    print_r($query->fetch_all(MYSQLI_BOTH));
    echo "</pre>";
} else {
    echo "Accesso rifiutato";
}

fetch_all() è un metodo molto flessibile, che produce un array per ogni record, numerico (MYSQLI_NUM), associativo (MYSQLI_ASSOC) o di entrambe le tipologie (MYSQLI_BOTH) a seconda della costante che passeremo come argomento. Il codice proposto produrrà quindi come risultato:

Array
(
    [0] => Array
        (
            [0] => 1
            [id] => 1
            [1] => pippo
            [user] => pippo
            [2] => f106e246ecdab88cb2262780f60079651aeaa6a3c8f5b1e75fc2ab0582cd3f67
            [password] => f106e246ecdab88cb2262780f60079651aeaa6a3c8f5b1e75fc2ab0582cd3f67
        )
)

Esamineremo altri metodi per la lettura dei dati, pensati ad esempio per leggere singoli record o per generare degli oggetti al posto degli array, man mano che procederemo nella nostra applicazione.

La necessità dei prepared statements

Non è tuttavia questo il codice che consigliamo di usare per query dei dati che provengono direttamente dall’utente. Infatti ogni volta che i dati che entrano nell’applicazione provengono dall’input dell’utente è necessario cautelarsi da minacce come la SQL injection.

L’estensione MySQLi può aiutarci tramite i prepared statements, una particolare tecnica che complica leggermente il semplice procedimento appena descritto, ma che offre diversi vantaggi sia a livello di pulizia del codice che di sicurezza.

Nella prossima lezione mostreremo quindi come funzionano i prepared statements e come leggere i dati da essi ottenuti.

Leave a Reply