In breve: PHP segnala i problemi in due modi: come errori classici, ad esempio avvisi e notifiche, controllati tramite error_reporting, e come eccezioni, che si possono intercettare con try e catch. Da PHP 7 anche molti errori interni generano un oggetto della classe Error; insieme a Exception implementa l'interfaccia Throwable. In produzione vale la regola: registrare gli errori nel log, non mostrarli mai nel browser. Su PHP 8.5, a causa di PHP2Shell, tra le misure transitorie c'è inoltre fatal_error_backtraces=0.
Due mondi: errori classici ed eccezioni
PHP nasce in un'epoca in cui gli errori venivano emessi soprattutto come messaggi: notifiche (E_NOTICE), avvisi (E_WARNING), funzioni obsolete (E_DEPRECATED) ed errori fatali (E_ERROR). Molti di questi messaggi lasciano proseguire lo script, spesso con dati errati o vuoti.
Le eccezioni funzionano diversamente. Interrompono il flusso normale e saltano al successivo blocco catch adatto. Se non ne viene trovato nessuno, lo script termina. Per chi decide la differenza è importante: un avviso silenzioso può far sì che un ordine venga salvato in modo incompleto senza che nessuno se ne accorga. Un'eccezione rende il problema visibile e gestibile.
Un vantaggio sottovalutato dei messaggi classici sono le notifiche di tipo E_DEPRECATED. Annunciano che uno strumento del linguaggio verrà eliminato in una futura versione di PHP. Chi le raccoglie nel log e le analizza con regolarità sa, prima di un upgrade, quali punti vanno adeguati, e in caso di vulnerabilità urgente non deve prima testare a lungo.
La gerarchia di Throwable
Tutto ciò che si può lanciare con throw e intercettare con catch implementa Throwable. Al di sotto ci sono due rami:
| Classe | Scopo | Esempi di sottoclassi |
|---|---|---|
Exception |
errori prevedibili dell'applicazione e delle estensioni | ErrorException, PDOException, classi proprie |
Error |
errori interni di PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
Le classi di errore proprie derivano da Exception, non da Error. Da PHP 8.0 la maggior parte delle funzioni interne, con parametri non validi, lancia un TypeError o un ValueError invece di emettere soltanto un avviso. strlen([]) ne è un esempio.
try, catch e finally nella pratica
<?php
declare(strict_types=1);
final class ZahlungFehlgeschlagen extends RuntimeException {}
function bezahlen(Bestellung $b, Zahlungsdienst $dienst, LoggerInterface $log): bool
{
try {
$dienst->belasten($b->betrag(), $b->referenz());
return true;
} catch (ZahlungFehlgeschlagen $e) {
$log->warning('Zahlung abgelehnt', ['bestellung' => $b->id(), 'exception' => $e]);
return false;
} catch (TimeoutException | VerbindungsFehler) {
// da PHP 8.0 la variabile nel catch è facoltativa
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Più classi si possono riunire in un unico catch con |. Il blocco finally viene eseguito sempre, che si sia verificato un errore o no. Da PHP 8.0 throw è inoltre un'espressione e può comparire, ad esempio, dopo ??: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Handler globali: set_error_handler e set_exception_handler
Due funzioni intercettano ciò che altrimenti sfuggirebbe. set_exception_handler() registra una funzione per i Throwable non intercettati. Secondo il manuale, dopo la sua chiamata lo script termina; è quindi adatta per la registrazione nel log e per una pagina di errore neutra, non per proseguire il lavoro.
Si è rivelato valido un handler che scrive nel log l'errore completo con data e ora e un identificativo casuale, mostrando al visitatore solo una pagina neutra con tale identificativo. Il supporto può così ritrovare il caso nel log, senza che dettagli interni trapelino all'esterno.
set_error_handler() intercetta gli errori classici. Il manuale mostra come trasformarli in una ErrorException, in modo che gli avvisi vengano trattati come eccezioni:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // esclusa da error_reporting
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // non trasformare in eccezioni le segnalazioni di funzioni obsolete
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Gli errori fatali, come un superamento della memoria, non si possono intercettare in questo modo. Che cosa accade internamente in caso di superamento lo spiega la pagina memory_limit e Out-of-Memory.
Configurare la visualizzazione e la registrazione degli errori
Le impostazioni più importanti con i loro valori predefiniti secondo il manuale:
| Impostazione | Predefinito | Raccomandazione per la produzione |
|---|---|---|
error_reporting |
E_ALL da PHP 8.0.0 |
E_ALL |
display_errors |
1 |
Off |
display_startup_errors |
1 da PHP 8.0.0 |
Off |
log_errors |
0 |
On |
error_log |
vuoto (log del server web o della SAPI) | file proprio fuori dal webroot |
fatal_error_backtraces |
1, nuova in PHP 8.5.0 |
temporaneamente 0 (PHP2Shell) |
Da PHP 8.2.0 error_log_mode stabilisce i permessi del file di log, per impostazione predefinita 0644. Se i log contengono dati personali o percorsi, è sensato un valore più restrittivo, perché gli altri utenti sul server non possano leggerli. Con ignore_repeated_errors si possono riunire i messaggi identici provenienti dallo stesso file e dalla stessa riga, perché un ciclo di errori non sommerga il log.
Il php.ini-production fornito con PHP imposta già display_errors su Off. Secondo il manuale ini_set('display_errors', ...) in fase di esecuzione non ha effetto sugli errori fatali, e gli errori di sintassi si verificano prima ancora che un ini_set() venga eseguito. Questi valori appartengono quindi alla configurazione del server. Gli approfondimenti si trovano nell'articolo Disattivare la visualizzazione degli errori in produzione; come tenere i log senza fughe di dati, nell'articolo Registrare i log in modo sicuro.
Gestione degli errori e PHP2Shell
Per PHP 8.5 php.net indica, tra le misure transitorie contro PHP2Shell, di impostare fatal_error_backtraces=0 e di riavviare poi PHP. Le altre misure riguardano memory_limit, post_max_size e session.upload_progress.enabled. Nessuna di esse sostituisce l'aggiornamento a PHP 8.5.12, 8.4.27, 8.3.36 o 8.2.35.
; solo PHP 8.5, in php.ini o nel pool FPM, poi riavviare tutti i worker
fatal_error_backtraces = 0
Dopo PHP2Shell i log degli errori sono inoltre un'importante prova. Se dal 20/10/2026 si accumulano errori di memoria o worker interrotti, ciò può indicare tentativi di attacco, ma può avere anche cause innocue. Conservi i log e li analizzi insieme ai log degli accessi, come descritto nella guida Analizzare i log del server web dopo un attacco.
Come aiuta concretamente PHP SOS?
- Verifichiamo le impostazioni effettive per la visualizzazione degli errori e la registrazione per ogni sito web e le configuriamo in modo adatto alla produzione.
- Applichiamo le misure transitorie PHP2Shell in base alla versione di PHP e riavviamo tutti i worker.
- Mettiamo al sicuro i log degli errori e degli accessi dal 20/10/2026 e li analizziamo alla ricerca di anomalie.
- Nell'ambito del monitoraggio della sicurezza segnaliamo concentrazioni insolite di errori, invece di lasciarle disperdere nel log.
Domande frequenti
Qual è la differenza tra Error ed Exception?
Entrambe implementano l'interfaccia Throwable. Exception è pensata per gli errori che un'applicazione si aspetta e gestisce, ad esempio una connessione al database non riuscita. Error indica errori interni di PHP come un TypeError o una divisione per zero.
display_errors dovrebbe essere attivo in produzione?
No. I messaggi di errore nel browser rivelano percorsi, query e talvolta credenziali. In produzione display_errors va impostato su Off e log_errors su On, così gli errori finiscono nel log e non davanti al visitatore.
Si può intercettare un superamento della memoria con try/catch?
No. Se uno script supera memory_limit, PHP lo termina con un errore fatale che non può essere intercettato come eccezione. Compare solo nel log degli errori, se log_errors è attivo.
Che cos'è fatal_error_backtraces?
Un'impostazione disponibile da PHP 8.5.0 che aggiunge un backtrace agli errori fatali. È attiva per impostazione predefinita. Come misura transitoria contro PHP2Shell, per PHP 8.5 php.net indica di impostarla su 0 e di riavviare poi PHP.