PHP - Come risolvere l'errore "Warning: preg_replace(): The /e modifier is no longer supported" in PHP 7

PHP - How to disable error log, display errors and error reporting programmatically

In questo articolo parleremo di uno dei classici errori che si verificano quando si tenta l'upgrade da PHP5.x a PHP7:

Warning: preg_replace(): The /e modifier is no longer supported, use preg_replace_callback instead

Nonostante il problema in questione sia ben documentato nella documentazione ufficiale di PHP (l'utilizzo del modificatore /e  è deprecato fin dalla release 5.5 e non più supportato dalla 7.0.0 in poi), il warning di cui sopra resta uno dei più complessi da affrontare, principalmente perché non esiste un workaround valido per tutte le situazioni: il modo migliore di procedere è, ovviamente, quello suggerito dalla documentazione: procedere a una re-implementazione del codice, utilizzando la funzione preg_replace_callback in luogo del modificatore /e: sfortunatamente nella maggior parte dei casi non si tratta di un lavoro semplice, perché la funzione preg_replace è presente in numerosissimi script PHP di terze parti che è particolarmente scomodo modificare.

In questo articolo proveremo a documentare tre diversi metodi che sono stati messi a punto dalla community internazionale per risolvere il problema in modo soddisfacente: sentitevi liberi di scegliere quello che meglio si presta al vostro scenario specifico.

Soluzione #1: Seguire il metodo consigliato

La prima cosa da fare è vedere se la nostra situazione ci consente di fare quello che dice il manuale, ovvero utilizare la nuova funzione preg_replace_callback in luogo della precedente, modificando il nostro codice di conseguenza. Inutile a dirsi, si tratta - come già detto - del modo migliore e più robusto per risolvere il problema, anche perché il codice che andremo a scrivere sarà pienamente conforme alle modalità con cui PHP 7 gestisce le sostituzioni di stringa basate su regular expression (regex): nello specifico, si tratta di disfarsi del precedente eval (rappresentato dal parametro /e) e utilizzare un vero e proprio delegate method al suo posto.

Ecco un esempio di "vecchia" implementazione basata su preg_replace, che può essere utilizzata per trasformare tutte le minuscole presenti nella stringa di origine in maiuscole:

Ovviamente, questo codice non funzionerà se utilizzato con PHP7, restituendo l'errore di cui sopra.

Ecco invece l'implementazione corretta per PHP 7, utilizzando la funzione preg_replace_callback, che - per la cronaca - funziona anche con PHP5 ed è quindi pienamente retro-compatibile:

Come possiamo facilmente vedere, il codice di cui sopra presenta sostanzialmente tre differenze:

  • Utilizzo di preg_replace_callback in luogo del precedente e non più supportato preg_replace.
  • Eliminazione del parametro /e alla fine della stringa regex di lookup, mantenendo tutti gli altri switch: questo significa che se il nostro vecchio codice termina con /e , dovremmo sostituirlo con  / ; se termina, ad esempio, con  /uise  , dovremo sostituirlo con /uis ; se termina con   #ise  dovremo utilizzare  #is , e così via (la sintassi utilizzata può variare leggermente a seconda dei delimitatori utilizzati nello script PHP originario).
  • Modifica del secondo parametro, che non è più una semplice stringa che rappresenta del codice da eseguire tramite eval() bensì una funzione di callback vera e propria che restituisce la stringa risultante dall'avvenuta sostituzione. L'unica differenza tra la vecchia stringa e quella restituita dalla funzione di callback è data dalla modalità con cui viene effettuata la sostituzione dei risultati trovati dalla regex: come è possibile vedere nel codice di cui sopra, con il nuovo metodo non è più necessario utilizzare quegli orribili placeholder in-line come   \1 , \2  e   \3 , peraltro inevitabilmente soggetti al doppio-escape: i risultati della regex sono infatti resi disponibili all'interno del nuovo array accettato dalla nuova funzione come parametro di input (  $matches , nel nostro esempio) e possono quindi essere iterati con un semplice ciclo for / foreach.

Fix #2: Ingannare il Sistema

Sfortunatamente ci sono molte situazioni in cui re-implementare la stringa eval() in una funzione di callback vera e propria può rivelarsi estremamente difficile, per non dire impossibile: un caso emblematico è quello fornito dal noto framework di gestione forum phpBB, che - fino alla versione 3.1.x - utilizzava un approccio template-based per gestire la conversione da BBCODE a HTML: il codice, che si trova nel file /includes/bbcode.php , può essere consultato facilmente recandosi nella pagina ufficiale del progetto e scaricando una delle tante vecchie versioni. Fortunatamente il problema è stato risolto a partire dalla versione 3.2.x, tuttavia il web è ancora pieno di versioni meno aggiornate della nota piattaforma che, quando vengono aggiornate a PHP 7, presentano questo problema.

Vediamo in dettaglio come mai non è possibile utilizzare la soluzione #1 in questa situazione. Ecco la porzione di codice sorgente interessata (file /includes/bbcode.php, linee 113 e 494 per phpBB 3.1.x - numeri soggetti a variazione in caso di versioni differenti):

La seconda occorrenza della funzione incriminata potrebbe essere facilmente gestita con l'approccio #1 aggiungendo una istruzione di tipo   use($user)  subito dopo la definizione della funzione di callback, nel seguente modo:

Purtroppo, non è possibile utilizzare lo stesso metodo - né alcun altro metodo - per la prima: il parametro che contiene le stringhe di sostituzione è infatti un array che viene riempito attraverso template che l'applicazione recupera da un gran numero di posti diversi, come il FileSystem e il database del forum. Modificarle tutte, o convertirle in funzioni, è quindi pressoché impossibile, a patto di non stravolgere completamente l'interfaccia di sistema e verso l'utente/amministratore.

In tutti i casi dove procedere con una sostituzione puntuale della stringa in una funzione si rivela molto complesso o infattibile, la cosa "migliore" che possiamo fare (il virgolettato è d'obbligo, trattandosi di un workaroudn) è sostituire il parametro /e con un eval() vero e proprio, nel seguente modo:

Lo sappiamo, risolvere il problema in questo modo è un pò come rubare... nonostante questo, questa tecnica - se implementata correttamente - riesce a risolvere il problema, sempre che il nostro server/service provider non abbia disabilitato la funzione eval() , cosa che spesso viene fatta per ovvie ragioni di sicurezza - le stesse che hanno portato il team di sviluppo PHP 7 a non consentire più l'utilizzo del parametro /e !

Soluzione #3: Nascondere lo sporco sotto il tappeto

Se la soluzione #2 vi è sembrata un workaround di bassa lega, questa vi farà inorridire. L'unico motivo per cui ci sentiamo in dovere di menzionarla è dovuto al fatto che ci è stato recentemente chiesto di trovare un workaround, non importa quanto "sporca", per nascondere la visualizzazione di questo problema su un sistema dove non potevano essere effettuati cambiamenti sostanziali sul codice sorgente dell'applicazione: in altre parole, la richiesta era quella di "nascondere il warning" così da consentire al codice sorgente di funzionare, pur se privo delle funzionalità assolte in precedenza dalla funzione preg_replace con il parametro /e.

Per farla breve, chiunque abbia questa esigenza può procedere in questo modo:

Questo workaround può essere d'aiuto anche a chi dovesse trovarsi ad effettuare un upgrade da una versione di PHP uguale o inferiore alla 5.4 a PHP 5.5 o superiore: quest'ultimo, infatti, pur supportando ancora il parametro /e , emette un avviso per avvertire che la funzione preg_replace utilizzata in questo modo è deprecated e che a breve non sarà più supportata. Se vi trovate in questa particolare situazione, e non volete disabilitare i messaggi di tipo E_DEPRECATED ed E_STRICT tramite PHP.ini, questa "disabilitazione temporanea" può fare al caso vostro. Inutile dire che, per utilizzarla in questo modo, dovrete sostituire   E_WARNING con  E_DEPRECATED | E_STRICT  nella riga 2 del codice di cui sopra.

Nel caso in cui vogliate utilizzare questa strategia per nascondere altre tipologie di errore - ad esempio per impedire a PHP di loggare alcuni particolari errori nel file PHP_errors.log, suggeriamo di leggere  questo altro articolo sull'argomento: al tempo stesso, è molto importante comprendere che nascondere gli errori dei vostri script è quasi sempre il peggior regalo che potete farvi per garantire a voi stessi e ai vostri utenti la stabilità del sistema. Per questo motivo, prima di ricorrere a questa strategia, è assolutamente necessario comprendere tutte le possibili implicazioni e potenziali conseguenze che una simile scelta comporta.

Riferimenti utili

About Ryan

IT Project Manager, Web Interface Architect e Lead Developer di numerosi siti e servizi web ad alto traffico in Italia e in Europa. Dal 2010 si occupa anche della progettazione di App e giochi per dispositivi Android, iOS e Mobile Phone per conto di numerose società italiane. Microsoft MVP for Development Technologies dal 2018.

View all posts by Ryan

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *


The reCAPTCHA verification period has expired. Please reload the page.

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.