Dal vecchio mysql_ a mysqli e PDO: come si fa davvero il porting nel 2026
Le storiche funzioni mysql_ di PHP sono diventate deprecate con la versione 5.5 e sono state rimosse completamente da PHP 7.0, uscita alla fine del 2015. È un fatto vecchio di anni, eppure continuo a trovarlo vivo: applicazioni gestionali "fatte a mano", portali costruiti un decennio fa e mai più toccati, in cui mysql_connect, mysql_query e mysql_fetch_assoc sono ancora il cuore dell'accesso al database. La differenza è che nel 2015 il problema era prepararsi al futuro; oggi quel futuro è passato da un pezzo, e il significato è cambiato del tutto.
TL;DR
- Fatto: le funzioni
mysql_sono rimosse da PHP 7.0 (fine 2015): chi le ha ancora gira su una versione di PHP fuori supporto da circa dieci anni.- Porting automatico: un convertitore sostituisce
mysql_*conmysqli_*, ma è solo metà del lavoro (inciampa su casi comemysql_result, da gestire a mano).- mysqli o PDO: PDO per astrazione e portabilità fra database; mysqli per un porting rapido che resta su MySQL.
- Il vero rischio non è la deprecazione, è l'SQL injection: passa ai prepared statement, non limitarti a rinominare le funzioni.
- Già che ci sei: attiva le eccezioni (
mysqli_report(...)) e imposta il charsetutf8mb4.
Cosa significa avere ancora mysql_ nel codice nel 2026
Partiamo dall'implicazione che molti non colgono. Se la tua applicazione usa ancora le funzioni mysql_, non sta semplicemente usando un'API vecchia: sta girando su una versione di PHP precedente alla 7.0, perché dalla 7.0 in poi quelle funzioni non esistono più e il codice non parte nemmeno. Vuol dire PHP 5.6 o precedenti, una linea fuori da ogni supporto di sicurezza da circa dieci anni.
Questo trasforma il problema da estetico a critico. Una versione di PHP non più supportata non riceve patch per le vulnerabilità che vengono scoperte, e di vulnerabilità su PHP 5.x ne sono emerse molte nel tempo. Un'applicazione in queste condizioni è esposta a falle note e documentate, contro cui non esiste aggiornamento. Il porting delle funzioni mysql_, quindi, non è mai un intervento isolato: è la punta visibile di una migrazione di versione che va affrontata per intero. Ho descritto il percorso completo, con i breaking changes reali e la strategia incrementale per non fermare la produzione, in migrare un'applicazione da PHP 5.6 a PHP 8, e il quadro dei rischi concreti del restare indietro lo metto nero su bianco in i rischi del codice PHP obsoleto.
Il porting automatico funziona, ma è solo metà del lavoro
La buona notizia è che la sostituzione meccanica delle chiamate mysql_ con le equivalenti mysqli_ è in gran parte automatizzabile. Esistono da anni strumenti che attraversano una codebase e riscrivono le chiamate: il più noto è un convertitore disponibile su GitHub che dispone sia di interfaccia grafica sia a riga di comando, e che su una directory di file PHP fa il grosso del lavoro sostituendo le funzioni una a una.
Eseguito sulla webroot, uno strumento del genere produce in pochi secondi il rimpiazzo di centinaia di chiamate. Il punto è cosa succede dopo, ed è qui che l'esperienza conta più dello strumento. Il convertitore lavora bene sulle funzioni che hanno un equivalente diretto, ma inciampa sui casi particolari. L'esempio che incontro più spesso è mysql_result, una funzione che non ha un gemello mysqli_ con la stessa firma: il tool la lascia indietro, e l'applicazione, dopo la conversione, lancia un errore fatale appena raggiunge quella riga.
La procedura corretta dopo un porting automatico è sempre la stessa: testare l'applicazione percorrendo i flussi reali, leggere il log degli errori di PHP, individuare le righe che sollevano eccezioni, e gestire a mano i casi che lo strumento ha saltato. Per le funzioni senza equivalente diretto si scrive una piccola funzione di compatibilità che riproduce il comportamento atteso sopra l'API nuova. Nel caso di mysql_result, una funzione che naviga il result set e restituisce il valore della cella richiesta risolve il problema senza dover riscrivere ogni singola chiamata:
function mysqli_result($res, $row = 0, $col = 0) {
$numrows = mysqli_num_rows($res);
if ($numrows && $row <= ($numrows - 1) && $row >= 0) {
mysqli_data_seek($res, $row);
$resrow = is_numeric($col) ? mysqli_fetch_row($res) : mysqli_fetch_assoc($res);
if (isset($resrow[$col])) {
return $resrow[$col];
}
}
return false;
}Con questa in libreria, basta sostituire le chiamate residue mantenendo gli stessi parametri. È un cerotto onesto, utile a far ripartire l'applicazione, ma resta un cerotto: il porting vero, quello che vale la pena fare, va oltre.
Prima di convertire: il censimento delle occorrenze
Un errore tipico è lanciare il convertitore alla cieca senza sapere quanto è grande il problema. Il primo passo che faccio sempre è un censimento: una ricerca ricorsiva su tutta la codebase per contare e localizzare ogni chiamata residua. Un comando come grep -rn "mysql_" --include="*.php" . restituisce l'elenco completo delle righe coinvolte, e quel numero racconta subito la storia: poche decine di occorrenze significano un intervento di ore, qualche migliaio significano un progetto da pianificare. Conoscere il volume prima di iniziare evita di scoprire a metà strada che il lavoro è il triplo di quanto stimato. La lista delle funzioni interessate è lunga e va trattata per intero, dalla connessione (mysql_connect, mysql_select_db) all'esecuzione (mysql_query) al recupero dei risultati (mysql_fetch_assoc, mysql_fetch_row, mysql_num_rows), e il riferimento canonico resta il manuale ufficiale dell'estensione mysqli su php.net, dove ogni vecchia funzione ha la sua corrispondente documentata.
mysqli procedurale o a oggetti, e perché gli errori vanno gestiti con le eccezioni
Il convertitore automatico produce mysqli in stile procedurale, perché è la traduzione più letterale delle vecchie chiamate. Funziona, ma se hai intenzione di mantenere il codice negli anni conviene sapere che mysqli offre anche un'interfaccia a oggetti più leggibile, in cui la connessione è un oggetto e i metodi si invocano su di esso. Più importante dello stile, però, è la gestione degli errori. Il vecchio codice mysql_ controllava il valore di ritorno di ogni chiamata e gestiva i fallimenti a mano, un approccio prolisso e facile da dimenticare. Su PHP moderno conviene attivare la modalità a eccezioni:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);Con questa riga, ogni errore del database solleva un'eccezione invece di restituire silenziosamente false, e questo cambia la qualità del codice: gli errori non passano più inosservati, si gestiscono in modo centralizzato con un blocco try/catch, e non rischi che una query fallita prosegua come se nulla fosse, producendo dati corrotti o pagine a metà. È una di quelle modifiche che il convertitore non fa e che trasforma un porting meccanico in un upgrade reale della robustezza.
Il dettaglio che salta in mente a nessuno: il charset della connessione
C'è un particolare che il porting automatico ignora e che genera bug subdoli: il charset della connessione. Il vecchio codice spesso non lo impostava esplicitamente, affidandosi a default che oggi sono diversi. Senza un charset corretto sulla connessione, i caratteri accentati e gli emoji si corrompono, e in certi scenari un charset mal gestito apre persino la porta a tecniche di injection che aggirano l'escaping. La regola che applico sempre è impostare utf8mb4 subito dopo la connessione:
$conn->set_charset("utf8mb4");È una riga sola, ma è la differenza tra un'applicazione che gestisce correttamente qualsiasi testo internazionale e una che mostra punti interrogativi al posto delle lettere accentate. In un porting fatto bene non si salta.
Su questo tipo di interventi, dalla valutazione di una codebase legacy alla pianificazione di una migrazione che non rompa la produzione, lavoro da molti anni con chi ha applicazioni datate ma ancora centrali per il business: nel mio profilo trovi il metodo con cui affronto la modernizzazione del codice esistente, partendo da quello che porta valore e non da una riscrittura totale che quasi mai conviene.
mysqli o PDO: quale scegliere nel porting?
Il convertitore automatico spinge verso mysqli, perché è la traduzione più diretta delle vecchie funzioni. Ma se stai già mettendo le mani nell'accesso al database, vale la pena fare la domanda giusta: mysqli o PDO?
La differenza pratica è la portabilità. mysqli è legato a MySQL e MariaDB; PDO è un'astrazione che parla con molti database diversi attraverso la stessa interfaccia, il che significa che un domani potresti cambiare motore con un impatto minore sul codice applicativo. Entrambi supportano i prepared statement, che sono la ragione vera per cui questo passaggio conta. Per un'applicazione nuova o per un refactoring profondo consiglio quasi sempre PDO, per la sua interfaccia più pulita e per la flessibilità che lascia aperta; per un porting rapido e mirato che deve solo rimettere in piedi un'applicazione esistente, mysqli riduce la distanza dal codice originale ed è una scelta legittima. Non c'è una risposta unica: c'è la risposta giusta per il tuo vincolo di tempo e per quanto in profondità vuoi spingerti.
In PDO lo stesso accesso al database assume una forma che molti trovano più naturale, e il binding dei parametri è altrettanto immediato:
$pdo = new PDO("mysql:host=localhost;dbname=app;charset=utf8mb4", $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare("SELECT nome, email FROM utenti WHERE id = ?");
$stmt->execute([$id]);
$riga = $stmt->fetch(PDO::FETCH_ASSOC);Da notare due cose che dovrebbero diventare automatiche: il charset=utf8mb4 già nella stringa di connessione, e l'attributo che attiva le eccezioni fin da subito. L'intera interfaccia, con tutti i metodi e le modalità di fetch, è descritta nel manuale ufficiale di PDO su php.net, che è il riferimento da tenere aperto durante il lavoro invece di affidarsi a frammenti copiati da forum.
Come verifico che il porting sia davvero completo?
Un porting non è finito quando l'applicazione riparte: è finito quando hai la ragionevole certezza di non aver lasciato regressioni. Il metodo che seguo combina tre controlli. Il primo è la verifica che non resti nemmeno una chiamata mysql_, ripetendo la stessa ricerca ricorsiva del censimento iniziale: il risultato deve essere zero. Il secondo è il percorrere a mano i flussi critici dell'applicazione, quelli che toccano il database in scrittura, perché un errore su una SELECT si nota subito mentre uno su una INSERT o UPDATE può restare nascosto finché non corrompe dati reali. Il terzo è tenere il log degli errori sotto osservazione per qualche giorno dopo il rilascio, perché certi percorsi di codice si attivano solo in condizioni particolari e non emergono in un test rapido. È la differenza tra un porting che sembra fatto e uno che lo è davvero.
Perché il vero rischio non è la deprecazione ma l'SQL injection
Arriviamo al punto che fa la differenza tra un porting fatto e un porting fatto bene. Il codice scritto nell'era delle funzioni mysql_ costruiva quasi sempre le query concatenando stringhe, infilando direttamente nella query i valori che arrivavano dall'utente. Era la norma di allora, ed è esattamente il pattern che apre la porta all'SQL injection, una delle vulnerabilità più vecchie e più sfruttate del web.
Ecco la trappola: un convertitore automatico traduce mysql_query("SELECT ... WHERE id = $id") in mysqli_query($conn, "SELECT ... WHERE id = $id"). La sintassi è aggiornata, l'applicazione riparte, ma la vulnerabilità è rimasta identica. Hai cambiato l'API e lasciato la falla esattamente dov'era. Per questo dico che il porting meccanico è solo metà del lavoro: la metà che conta davvero è trasformare quelle query in prepared statement, dove i valori dell'utente viaggiano separati dal testo della query e non possono più alterarne la logica.
$stmt = $conn->prepare("SELECT nome, email FROM utenti WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();Questo non è più solo un porting: è la chiusura di una vulnerabilità che il codice si portava dietro da anni. Il momento in cui tocchi ogni chiamata al database è l'unica occasione in cui rifarlo costa poco, perché sei già lì. La prevenzione dell'SQL injection nel 2026, tra prepared statement e ORM, l'ho trattata in dettaglio in SQL injection: prevenzione con prepared statement e ORM per le PMI, ed è la lettura che consiglio prima di considerare chiuso un porting.
Il porting come occasione, non come tappabuchi
Riassumo il modo in cui affronto questi lavori, perché l'errore più costoso è trattare il porting come una sostituzione testuale e dichiararlo finito quando l'applicazione riparte. La sequenza che seguo è chiara: prima la conversione automatica delle chiamate, per abbattere il volume del lavoro meccanico; poi il test reale e la gestione manuale dei casi che lo strumento ha saltato, leggendo i log; poi la decisione consapevole tra mysqli e PDO; e infine, la parte che dà valore, la trasformazione delle query in prepared statement per chiudere l'SQL injection ereditata.
Il tutto inserito nel quadro più ampio della migrazione di versione di PHP, perché avere ancora mysql_ nel codice significa per forza essere su una linea fuori supporto, e il porting del database è solo un pezzo di un aggiornamento che va completato. Fatto così, un intervento che molti vivono come una scocciatura tecnica diventa il momento in cui un'applicazione vecchia ma viva ritrova sicurezza, prestazioni e un futuro di manutenibilità.
Se hai un'applicazione che ancora poggia sulle vecchie funzioni mysql_, o più in generale un gestionale costruito anni fa che fatica a stare al passo, scrivimi dal modulo di contatto: partiamo da una valutazione onesta dello stato del codice e da dove conviene intervenire per primo, perché modernizzare bene significa scegliere cosa toccare e in quale ordine, non riscrivere tutto sperando che basti.