Question

How to Handle PHP Fatal Errors (E_ERROR) in PHP

phpfatal-error

Question

I can use set_error_handler() to catch most PHP errors, but it does not work for fatal errors such as E_ERROR, for example when calling a function that does not exist.

Is there another way to handle these errors in PHP?

I want to send an email with mail() whenever an error occurs, and I am using PHP 5.2.3.

Short Answer

By the end of this page, you will understand why set_error_handler() cannot catch true PHP fatal errors, how fatal errors stop normal script execution, and how to detect them using register_shutdown_function() together with error_get_last(). You will also learn practical patterns for logging, alerting, and safely reacting to serious runtime failures in PHP.

Concept

In PHP, not all errors behave the same way.

set_error_handler() can handle many runtime errors, warnings, and notices, but true fatal errors like E_ERROR are different. A fatal error stops the script immediately. Because execution ends at once, PHP does not continue into your normal error handler.

This is why code like this does not catch a fatal error:

set_error_handler(function ($errno, $errstr, $errfile, $errline) {
    mail('admin@example.com', 'PHP Error', $errstr);
});

undefined_function(); // Fatal error

The custom error handler is skipped because PHP terminates execution.

The common workaround is to register a shutdown function using register_shutdown_function(). A shutdown function runs when the script is ending, including after many fatal errors. Inside it, you can inspect the last error using error_get_last().

This matters in real programming because production systems need a way to:

  • log unexpected crashes
  • alert developers or administrators
  • show friendly messages to users
  • record debugging details without exposing them publicly

In older PHP versions like PHP 5.2.3, this shutdown-based approach was the standard way to react to fatal errors that set_error_handler() could not catch.

Mental Model

Think of PHP error handling like building security in a store:

  • set_error_handler() is like a staff member handling small problems during business hours.
  • A fatal error is like the power suddenly going out.
  • When the power goes out, the staff member cannot continue normal work.
  • register_shutdown_function() is like the emergency backup system that runs when the building is closing down.

So you do not really catch a fatal error in the usual sense. Instead, you run code at shutdown and inspect whether the script ended because of a fatal error.

Take Quiz

Syntax and Examples

The key tools are:

  • set_error_handler() for non-fatal errors
  • register_shutdown_function() for checking fatal errors at the end
  • error_get_last() for reading the last occurred error

Basic fatal error detection

<?php
register_shutdown_function('handleFatalError');

function handleFatalError()
{
    $error = error_get_last();

    if ($error !== null && $error['type'] === E_ERROR) {
        mail(
            'admin@example.com',
            'Fatal PHP Error',
            "Message: {$error['message']}\nFile: {$error['file']}\nLine: {$error['line']}"
        );
    }
}

undefined_function();

What this does

  • register_shutdown_function('handleFatalError') tells PHP to run handleFatalError() when the script ends.
  • error_get_last() returns the last error that happened.
  • If that last error is an E_ERROR, you can log it or send an email.

Handling more than one fatal-like error type

In practice, developers often check multiple serious error types:

<?php
register_shutdown_function('handleShutdown');

function handleShutdown()
{
    $error = error_get_last();

    if ($error !== null && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR))) {
        $message = "A fatal error occurred:\n"
            . "Type: {$error['type']}\n"
            . "Message: {$error['message']}\n"
            . "File: {$error['file']}\n"
            . "Line: {$error['line']}";

        error_log($message);
        mail('admin@example.com', 'Fatal PHP Error', $message);
    }
}

nonexistent_function();

Using both normal and fatal error handling

<?php
set_error_handler('handleError');
register_shutdown_function('handleShutdown');

function handleError($errno, $errstr, $errfile, $errline)
{
    error_log("Non-fatal error [$errno]: $errstr in $errfile on line $errline");
}

function handleShutdown()
{
    $error = error_get_last();

    if ($error !== null && $error['type'] === E_ERROR) {
        error_log("Fatal error: {$error['message']} in {$error['file']} on line {$error['line']}");
    }
}

trigger_error('This is a warning-level issue', E_USER_WARNING);

This pattern lets you respond to both non-fatal and fatal failures.

Step by Step Execution

Consider this example:

<?php
register_shutdown_function('handleShutdown');

echo "Start\n";
undefined_function();
echo "End\n";

function handleShutdown()
{
    $error = error_get_last();

    if ($error !== null && $error['type'] === E_ERROR) {
        echo "Shutdown detected fatal error: {$error['message']}\n";
    }
}

Execution trace

  1. PHP registers the shutdown function handleShutdown.
  2. echo "Start\n"; runs successfully.
  3. PHP reaches undefined_function();.
  4. Because the function does not exist, PHP raises a fatal E_ERROR.
  5. Normal execution stops immediately.
  6. echo "End\n"; never runs.
  7. Before the script fully terminates, PHP runs the shutdown function.
  8. Inside handleShutdown(), error_get_last() returns details about the fatal error.
  9. Your code can then log it, email it, or store it.

Important detail

The fatal error is not resumed or recovered from. The script still ends. You are only getting a final chance to inspect what happened.

Real World Use Cases

Fatal error detection is useful in many real applications:

  • Production monitoring: email or log when a script crashes unexpectedly.
  • Background jobs: detect when a scheduled PHP task fails overnight.
  • Legacy PHP systems: add minimal crash reporting without rewriting the whole app.
  • Form handlers: record unexpected failures before the response ends.
  • API endpoints: log crash details for debugging when requests fail.

Example: a cron job imports CSV files every night. If a missing function or unrecoverable error stops the job, a shutdown function can log the exact file and line so the issue is found quickly.

Example: an old PHP 5.2 application cannot use modern exception-based patterns everywhere. A shutdown handler can still provide a safety net for severe crashes.

Take Quiz

Real Codebase Usage

In real projects, developers usually do not rely on email for every error because it can create too much noise. Instead, they combine several patterns.

Common patterns

  • Central logging: send fatal error details to error_log() or a log file.
  • Alerting for severe failures only: email only when the error type is fatal.
  • Environment checks: show detailed output in development, but log only in production.
  • Guard clauses: validate important values early to reduce fatal conditions.
  • Fallback shutdown reporting: use shutdown handlers as a last-resort safety mechanism.

Typical production pattern

<?php
register_shutdown_function('shutdownMonitor');

function shutdownMonitor()
{
    $error = error_get_last();

    if ($error === null) {
        return;
    }

    $fatalTypes = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR);

    if (!in_array($error['type'], $fatalTypes)) {
        return;
    }

    $message = sprintf(
        "[%s] %s in %s on line %d",
        $error['type'],
        $error['message'],
        $error['file'],
        $error['line']
    );

    error_log($message);
}

This uses early returns to keep the shutdown function simple and readable.

Why this is preferred

  • less duplicate code
  • clearer error filtering
  • easier to maintain
  • safer than assuming every last error should trigger an alert

Common Mistakes

1. Expecting set_error_handler() to catch E_ERROR

This is the most common misunderstanding.

<?php
set_error_handler('myHandler');

function myHandler($errno, $errstr)
{
    echo "Caught: $errstr";
}

undefined_function();

This will not catch the fatal error.

Avoid it by: using register_shutdown_function() for fatal shutdown detection.

2. Thinking shutdown handling can continue execution

A shutdown function does not recover the script.

<?php
register_shutdown_function('handleShutdown');
undefined_function();
echo "Still running";

echo "Still running"; never executes.

Avoid it by: treating shutdown handlers as reporting tools, not recovery tools.

3. Sending email for every warning or notice

If you email on every error, you may flood your inbox.

Avoid it by: limiting email alerts to serious failures and logging lower-level issues.

4. Not checking whether an error exists

Broken example:

<?php
register_shutdown_function(function () {
    $error = error_get_last();
    echo $error['message'];
});

If there was no error, this may itself cause problems.

Correct approach:

<?php
register_shutdown_function(function () {
    $error = error_get_last();

    if ($error !== null) {
        echo $error['message'];
    }
});

5. Assuming all fatal situations are identical

Different severe errors may have different types, such as:

  • E_ERROR
  • E_PARSE
  • E_CORE_ERROR
  • E_COMPILE_ERROR

Avoid it by: checking the exact types you want to handle.

6. Exposing raw fatal errors to users

Showing file paths and stack details to users is a security risk.

Avoid it by: logging technical details privately and showing a generic public message.

Comparisons

ConceptWhat it handlesCan it catch E_ERROR directly?Typical use
set_error_handler()Warnings, notices, user-triggered errors, many non-fatal runtime issuesNoCustom handling for non-fatal errors
register_shutdown_function()Code that runs when the script endsIndirectly, by inspecting the last errorDetect/report fatal crashes
error_get_last()Returns the last error that occurredNot by itselfUsed inside shutdown logic
ExceptionsThrown program errors and catchable exception flowNo, not classic PHP fatal errors in old PHP versionsStructured application-level error handling

set_error_handler() vs shutdown function

  • Use set_error_handler() when the script can continue or when you want custom processing for normal errors.
  • Use register_shutdown_function() when you need a last chance to inspect a fatal crash.

Logging vs emailing

ApproachProsCons
error_log()Fast, standard, good for production logsRequires log access and monitoring
mail()Immediate alertingCan be noisy, slow, or fail silently

In real systems, developers often log everything important and email only critical failures.

Take Quiz

Cheat Sheet

// Non-fatal errors
set_error_handler('handleError');

// Fatal shutdown detection
register_shutdown_function('handleShutdown');

function handleError($errno, $errstr, $errfile, $errline)
{
    error_log("[$errno] $errstr in $errfile on line $errline");
}

function handleShutdown()
{
    $error = error_get_last();

    if ($error !== null && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR))) {
        error_log("Fatal: {$error['message']} in {$error['file']} on line {$error['line']}");
    }
}

Key rules

  • set_error_handler() does not catch true fatal E_ERROR.
  • Use register_shutdown_function() to run code at script shutdown.
  • Use error_get_last() inside the shutdown function.
  • Shutdown handling reports the crash; it does not resume execution.
  • Be selective about emailing errors.

Common fatal-related types to check

  • E_ERROR
  • E_PARSE
  • E_CORE_ERROR
  • E_COMPILE_ERROR

Good practice

  • Log first
  • Alert only for critical failures
  • Do not expose raw error details to end users

FAQ

Can set_error_handler() catch fatal errors in PHP?

No. It can handle many non-fatal errors, but true fatal errors like E_ERROR terminate execution before the normal error handler can continue.

How do I handle fatal errors in PHP 5.2?

Use register_shutdown_function() and inspect error_get_last() when the script ends.

Can I recover from a fatal error and keep the script running?

No. A fatal error stops the script. A shutdown function only lets you inspect or report the failure before the process fully ends.

Should I use mail() for every PHP error?

Usually no. It is better to log most errors and reserve email alerts for serious or rare failures.

What does error_get_last() return?

It returns the last error as an array containing details such as type, message, file, and line, or null if no error occurred.

Can I detect parse errors this way?

In many cases, shutdown detection can help report parse-related fatal conditions, but the script itself cannot continue normally after them.

What is the safest production approach?

Use a shutdown function to log fatal errors, use set_error_handler() for non-fatal ones, and avoid showing raw error details to users.

Take Quiz

Mini Project

Description

Build a small PHP crash reporter for a legacy application. The script should log normal errors separately from fatal shutdown errors, and send an email only when a fatal error occurs. This demonstrates the practical difference between set_error_handler() and register_shutdown_function() in an older PHP-style environment.

Goal

Create a PHP script that logs non-fatal errors, detects fatal errors during shutdown, and emails fatal error details to an administrator.

Requirements

  1. Register a custom non-fatal error handler using set_error_handler().
  2. Register a shutdown function using register_shutdown_function().
  3. In the shutdown function, use error_get_last() to check whether a fatal error occurred.
  4. Log all handled errors with error_log().
  5. Send an email only for fatal error types such as E_ERROR.
Take Quiz

Keep learning

Related questions

View more