Kort gezegd: PHP meldt problemen op twee manieren: als klassieke fouten zoals waarschuwingen en opmerkingen, die via error_reporting worden gestuurd, en als exceptions, die met try en catch kunnen worden afgevangen. Sinds PHP 7 werpen ook veel interne fouten een object van de klasse Error; samen met Exception implementeert dat de interface Throwable. In productie geldt: fouten vastleggen, nooit in de browser tonen. Op PHP 8.5 hoort vanwege PHP2Shell bovendien fatal_error_backtraces=0 bij de overgangsmaatregelen.
Twee werelden: klassieke fouten en exceptions
PHP stamt uit een tijd waarin fouten vooral als melding werden weergegeven: opmerkingen (E_NOTICE), waarschuwingen (E_WARNING), verouderde functies (E_DEPRECATED) en fatale fouten (E_ERROR). Veel van deze meldingen laten het script doorlopen, vaak met onjuiste of lege gegevens.
Exceptions werken anders. Ze onderbreken het normale verloop en springen naar het eerstvolgende passende catch-blok. Wordt er geen gevonden, dan eindigt het script. Voor beslissers is het verschil belangrijk: een stille waarschuwing kan ertoe leiden dat een bestelling onvolledig wordt opgeslagen zonder dat iemand het merkt. Een exception maakt het probleem zichtbaar en afhandelbaar.
Een onderschat voordeel van de klassieke meldingen zijn aanwijzingen van het type E_DEPRECATED. Ze kondigen aan dat een taalmiddel in een toekomstige PHP-versie vervalt. Wie ze in het log verzamelt en regelmatig beoordeelt, weet vóór een upgrade welke plaatsen moeten worden aangepast, en hoeft bij een dringend beveiligingslek niet eerst lang te testen.
De Throwable-hiërarchie
Alles wat met throw kan worden geworpen en met catch kan worden gevangen, implementeert Throwable. Daaronder zijn er twee takken:
| Klasse | Doel | Voorbeelden van subklassen |
|---|---|---|
Exception |
verwachte fouten van de toepassing en van extensies | ErrorException, PDOException, eigen klassen |
Error |
interne fouten van PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
Eigen foutklassen erven van Exception, niet van Error. Sinds PHP 8.0 werpen de meeste interne functies bij ongeldige parameters een TypeError of ValueError, in plaats van alleen een waarschuwing te geven. strlen([]) is zo'n geval.
try, catch en finally in de praktijk
<?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) {
// sinds PHP 8.0 is de variabele in de catch optioneel
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Meerdere klassen kunnen met | in één catch worden samengevoegd. Het finally-blok wordt altijd uitgevoerd, of er nu een fout is opgetreden of niet. Sinds PHP 8.0 is throw bovendien een expressie en kan het bijvoorbeeld achter ?? staan: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Globale handlers: set_error_handler en set_exception_handler
Twee functies vangen af wat anders zou doorglippen. set_exception_handler() registreert een functie voor niet-afgevangen Throwables. Volgens de handleiding eindigt het script nadat de functie is aangeroepen; ze is dus geschikt voor het vastleggen in het log en een neutrale foutpagina, niet om door te werken.
Een handler die de volledige fout met tijdstip en een willekeurige code in het log schrijft en de bezoeker alleen een neutrale pagina met die code toont, heeft zich bewezen. De support kan het geval dan in het log terugvinden zonder dat interne details naar buiten lekken.
set_error_handler() vangt klassieke fouten af. De handleiding laat zien hoe u ze omzet in een ErrorException, zodat waarschuwingen als exceptions worden behandeld:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // uitgesloten door error_reporting
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // aanwijzingen over verouderde code niet laten escaleren
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Fatale fouten zoals een geheugenoverschrijding kunnen op deze manier niet worden afgevangen. Wat er bij een overschrijding van binnen gebeurt, legt de pagina memory_limit en out-of-memory uit.
Foutweergave en logging configureren
De belangrijkste instellingen met hun standaardwaarden volgens de handleiding:
| Instelling | Standaard | Aanbeveling productie |
|---|---|---|
error_reporting |
E_ALL sinds PHP 8.0.0 |
E_ALL |
display_errors |
1 |
Off |
display_startup_errors |
1 sinds PHP 8.0.0 |
Off |
log_errors |
0 |
On |
error_log |
leeg (log van de webserver of de SAPI) | eigen bestand buiten de webroot |
fatal_error_backtraces |
1, nieuw in PHP 8.5.0 |
tijdelijk 0 (PHP2Shell) |
Sinds PHP 8.2.0 bepaalt error_log_mode de bestandsrechten van het logbestand, standaard 0644. Bevatten de logs persoonsgegevens of paden, dan is een strengere waarde zinvol, zodat andere gebruikers op de server ze niet kunnen lezen. Met ignore_repeated_errors kunnen identieke meldingen uit hetzelfde bestand en dezelfde regel worden samengevoegd, zodat een foutlus het log niet overspoelt.
De meegeleverde php.ini-production zet display_errors al op Off. Volgens de handleiding werkt ini_set('display_errors', ...) tijdens de uitvoering niet op fatale fouten, en syntaxfouten treden op voordat een ini_set() uberhaupt draait. Deze waarden horen daarom in de serverconfiguratie. Achtergronden staan in het artikel Foutweergave in productie uitschakelen; hoe u logs zonder datalek bijhoudt, in het artikel Veilig loggen.
Foutafhandeling en PHP2Shell
Voor PHP 8.5 noemt php.net als een van de overgangsmaatregelen tegen PHP2Shell het instellen van fatal_error_backtraces=0 en daarna PHP opnieuw starten. De overige maatregelen betreffen memory_limit, post_max_size en session.upload_progress.enabled. Geen daarvan vervangt de update naar PHP 8.5.12, 8.4.27, 8.3.36 of 8.2.35.
; alleen PHP 8.5, in php.ini of FPM-pool, daarna alle workers opnieuw starten
fatal_error_backtraces = 0
Foutlogs zijn na PHP2Shell bovendien een belangrijk bewijsmiddel. Nemen vanaf 20 oktober 2026 geheugenfouten of afgebroken workers toe, dan kan dat wijzen op aanvalspogingen, maar het kan ook onschuldige oorzaken hebben. Bewaar de logs en beoordeel ze samen met de toegangslogs, zoals beschreven in de gids Weblogs na een aanval analyseren.
Hoe helpt PHP SOS concreet?
- Wij controleren de werkzame instellingen voor foutweergave en logging per website en stellen ze productieklaar in.
- Wij voeren de PHP2Shell-overgangsmaatregelen door die passen bij de PHP-versie en starten alle workers opnieuw.
- Wij beveiligen fout- en toegangslogs vanaf 20 oktober 2026 en beoordelen ze op bijzonderheden.
- In het kader van de security-monitoring melden wij ongebruikelijke foutopeenhopingen, in plaats van ze in het log te laten wegzakken.
Veelgestelde vragen
Wat is het verschil tussen Error en Exception?
Beide implementeren de interface Throwable. Exception is bedoeld voor fouten die een toepassing verwacht en afhandelt, zoals een mislukte databaseverbinding. Error staat voor interne fouten van PHP, zoals een TypeError of een deling door nul.
Moet display_errors in productie aanstaan?
Nee. Foutmeldingen in de browser verraden paden, queries en soms toegangsgegevens. In productie hoort display_errors op Off en log_errors op On, zodat fouten in het log terechtkomen in plaats van bij de bezoeker.
Kan een geheugenoverschrijding met try/catch worden afgevangen?
Nee. Overschrijdt een script memory_limit, dan beëindigt PHP het met een fatale fout die niet als exception kan worden afgevangen. Ze verschijnt alleen in het foutenlog, mits log_errors actief is.
Wat is fatal_error_backtraces?
Een instelling die er sinds PHP 8.5.0 is en die fatale fouten van een backtrace voorziet. Ze is standaard actief. Als overgangsmaatregel tegen PHP2Shell noemt php.net voor PHP 8.5 het op 0 zetten ervan en daarna PHP opnieuw starten.