Practical examples of implementing error handling in PHP

If you write PHP for anything beyond a throwaway script, you need real, practical examples of implementing error handling in PHP. Silent failures, blank white screens, and cryptic logs are how bugs sneak into production and stay there. The good news: modern PHP gives you solid tools to detect, log, and respond to errors in a way that fits both small scripts and large applications. In this guide, we’ll walk through real examples of how to implement error handling in PHP using native error handlers, exceptions, custom exception classes, and integration with logging libraries. We’ll look at examples of handling database failures, API timeouts, user input validation, and fatal errors that would otherwise crash your app. Along the way, you’ll see how to keep detailed diagnostics in development while returning safe, user-friendly messages in production. If you’ve ever wondered how experienced PHP developers structure error handling in 2024 and beyond, these examples will give you patterns you can copy directly into your own projects.
Written by
Jamie
Published

Real-world examples of implementing error handling in PHP

Let’s start with concrete scenarios. These examples of implementing error handling in PHP are the patterns you’ll actually copy into your codebase. We’ll move from basic to more advanced, but all of them are production-grade ideas, not toy snippets.

Example of centralized error and exception handling

A common pattern is to route all errors and exceptions through a single handler. That gives you one place to log, format, and respond.

<?php
// public/index.php

declare(strict_types=1);

// Show detailed errors only in development
\(isDev = (\)_ENV['APP_ENV'] ?? 'prod') === 'dev';

ini_set('display_errors', $isDev ? '1' : '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/../storage/logs/php-error.log');

error_reporting(E_ALL);

function handleError(int \(severity, string \)message, string \(file, int \)line): bool
{
    // Convert all errors to ErrorException so we can catch them
    throw new ErrorException(\(message, 0, \)severity, \(file, \)line);
}

function handleException(Throwable $e): void
{
    global $isDev;

    error_log(sprintf(
        "[UNCAUGHT] %s: %s in %s on line %d\nStack trace:\n%s\n",
        get_class($e),
        $e->getMessage(),
        $e->getFile(),
        $e->getLine(),
        $e->getTraceAsString()
    ));

    http_response_code(500);

    if ($isDev) {
        echo '<h1>Application error</h1>';
        echo '<pre>' . htmlspecialchars((string)$e) . '</pre>';
    } else {
        echo 'Something went wrong. Please try again later.';
    }
}

set_error_handler('handleError');
set_exception_handler('handleException');

// From here on, any warning/notice becomes an exception you can catch

This is one of the best examples of taking PHP’s loose error model and turning it into something predictable. By converting all errors into ErrorException, you can use try/catch everywhere instead of juggling error_get_last() and return codes.

Examples of handling database errors with PDO exceptions

Database failures are a classic source of white screens. Here’s an example of using PDO with exception-based error handling.

<?php
$dsn = 'mysql:host=localhost;dbname=app;charset=utf8mb4';
$user = 'app_user';
$pass = 'secret';

try {
    \(pdo = new PDO(\)dsn, \(user, \)pass, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);

    \(stmt = \)pdo->prepare('SELECT * FROM users WHERE email = :email');
    \(stmt->execute(['email' => \)inputEmail]);
    \(user = \)stmt->fetch();

} catch (PDOException $e) {
    // Log detailed DB error
    error_log('DB error: ' . $e->getMessage());

    // Return safe message to caller
    http_response_code(500);
    echo 'We are having trouble accessing your account data. Please try again later.';
}

In production, this pattern protects you from leaking SQL details while still capturing enough data in logs to debug. In 2024, almost every serious PHP stack that talks to a database uses this style of error handling.

Best examples of validating user input and throwing domain-specific exceptions

Raw if checks with die() calls are still everywhere in legacy PHP. A better approach: wrap validation failures in domain-specific exceptions. Here’s an example of implementing error handling in PHP for an email registration flow.

<?php
class ValidationException extends RuntimeException
{
    private array $errors;

    public function __construct(array $errors)
    {
        parent::__construct('Validation failed');
        \(this->errors = \)errors;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }
}

function validateRegistration(array $data): void
{
    $errors = [];

    if (empty(\(data['email']) || !filter_var(\)data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'Please provide a valid email address.';
    }

    if (empty(\(data['password']) || strlen(\)data['password']) < 12) {
        $errors['password'] = 'Password must be at least 12 characters.';
    }

    if ($errors) {
        throw new ValidationException($errors);
    }
}

try {
    validateRegistration($_POST);
    // Continue with registration
} catch (ValidationException $e) {
    http_response_code(422);
    header('Content-Type: application/json');
    echo json_encode([
        'message' => $e->getMessage(),
        'errors'  => $e->getErrors(),
    ]);
}

This is one of those real examples that scales nicely: you can reuse ValidationException across forms, APIs, and background jobs.

Examples include handling third‑party API timeouts and network failures

External APIs fail. DNS breaks. Timeouts happen. Ignoring those errors in PHP is how you end up with stuck orders or half-finished workflows.

Here’s an example of implementing error handling in PHP for an HTTP call using curl with timeouts and structured responses.

<?php
class ApiException extends RuntimeException {}

function callPaymentApi(array $payload): array
{
    $ch = curl_init('https://payments.example.com/charge');

    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode($payload),
        CURLOPT_TIMEOUT        => 10,  // seconds
        CURLOPT_CONNECTTIMEOUT => 5,
    ]);

    \(responseBody = curl_exec(\)ch);

    if ($responseBody === false) {
        \(error = curl_error(\)ch);
        curl_close($ch);
        throw new ApiException('Payment API request failed: ' . $error);
    }

    \(statusCode = curl_getinfo(\)ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($statusCode >= 500) {
        throw new ApiException('Payment API unavailable, status ' . $statusCode);
    }

    \(data = json_decode(\)responseBody, true, 512, JSON_THROW_ON_ERROR);

    if (($data['status'] ?? null) !== 'success') {
        throw new ApiException('Payment declined: ' . ($data['message'] ?? 'Unknown error'));
    }

    return $data;
}

try {
    $result = callPaymentApi(['amount' => 1000, 'currency' => 'USD']);
    // Mark order as paid
} catch (ApiException | JsonException $e) {
    error_log('Payment error: ' . $e->getMessage());
    http_response_code(502);
    echo 'We could not process your payment. Please try again or use another method.';
}

This is one of the best examples of layering concerns: callPaymentApi knows about HTTP and JSON; the caller decides how to respond to the user.

Example of logging errors with Monolog in a modern stack

By 2024, many PHP apps run under frameworks like Laravel or Symfony, which use Monolog for logging. You can still use the same pattern in a plain PHP app.

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

require __DIR__ . '/vendor/autoload.php';

$log = new Logger('app');
$log->pushHandler(new StreamHandler(__DIR__ . '/storage/logs/app.log', Logger::WARNING));

set_exception_handler(function (Throwable \(e) use (\)log) {
    $log->error('Uncaught exception', [
        'type'    => get_class($e),
        'message' => $e->getMessage(),
        'file'    => $e->getFile(),
        'line'    => $e->getLine(),
        'trace'   => $e->getTraceAsString(),
    ]);

    http_response_code(500);
    echo 'Internal server error.';
});

// Somewhere later
try {
    // Risky code
} catch (Throwable $e) {
    \(log->warning('Handled exception', ['exception' => \)e]);
}

This example of implementing error handling in PHP with Monolog gives you structured logs that work well with modern observability stacks like ELK or OpenSearch.

Examples of handling file upload errors in PHP

File uploads are another area where people forget to handle errors. PHP already gives you error codes in $_FILES — you just have to use them.

<?php
function handleProfileUpload(array $file): void
{
    if (!isset(\(file['error']) || is_array(\)file['error'])) {
        throw new RuntimeException('Invalid upload parameters.');
    }

    switch ($file['error']) {
        case UPLOAD_ERR_OK:
            break;
        case UPLOAD_ERR_NO_FILE:
            throw new RuntimeException('No file sent.');
        case UPLOAD_ERR_INI_SIZE:
        case UPLOAD_ERR_FORM_SIZE:
            throw new RuntimeException('Uploaded file is too large.');
        default:
            throw new RuntimeException('Unknown upload error.');
    }

    if ($file['size'] > 5 * 1024 * 1024) { // 5 MB
        throw new RuntimeException('Uploaded file exceeds 5MB limit.');
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    \(mime  = \)finfo->file($file['tmp_name']);

    $allowed = [
        'jpg' => 'image/jpeg',
        'png' => 'image/png',
    ];

    \(ext = array_search(\)mime, $allowed, true);
    if ($ext === false) {
        throw new RuntimeException('Invalid file format.');
    }

    \(target = sprintf('uploads/%s.%s', bin2hex(random_bytes(8)), \)ext);

    if (!move_uploaded_file(\(file['tmp_name'], \)target)) {
        throw new RuntimeException('Failed to move uploaded file.');
    }
}

try {
    handleProfileUpload($_FILES['avatar']);
    echo 'Upload successful.';
} catch (RuntimeException $e) {
    error_log('Upload error: ' . $e->getMessage());
    echo $e->getMessage();
}

This is one of those real examples where careful error handling improves both security and user experience.

Example of catching fatal errors and logging shutdown issues

Some errors in PHP are fatal and would normally kill your script instantly. But you can still catch the last error on shutdown and log it.

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

    if (\(error !== null && in_array(\)error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
        error_log(sprintf(
            '[FATAL] %s in %s on line %d',
            $error['message'],
            $error['file'],
            $error['line']
        ));

        // Optionally show a generic message to the user
        if (!headers_sent()) {
            http_response_code(500);
        }
        echo 'Unexpected server error. Please try again later.';
    }
});

When you review logs, these fatal error entries often point directly to the bug that needs fixing.

Example of separating development and production error behavior

One of the best examples of modern PHP practice is to treat development and production very differently. In development you want noisy, visible errors. In production you want quiet logs and friendly messages.

<?php
\(isDev = (\)_ENV['APP_ENV'] ?? 'prod') === 'dev';

if ($isDev) {
    ini_set('display_errors', '1');
    error_reporting(E_ALL);
} else {
    ini_set('display_errors', '0');
    error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT);
}

You can combine this with the earlier centralized handler examples of implementing error handling in PHP to get consistent behavior across environments.

A few patterns have become standard in the PHP ecosystem:

  • Exceptions over error codes: Modern libraries throw exceptions by default (PDO, many HTTP clients, modern ORMs). Your code should expect and catch them.
  • Typed exceptions: Instead of throwing Exception everywhere, developers use domain-specific types like ValidationException, ApiException, and DomainException to make catch blocks more precise.
  • Structured logging: Tools like Monolog and centralized log aggregation are common even in mid-size apps.
  • Health monitoring: Many teams pair PHP error logs with uptime monitors and health checks. For general guidance on error reporting and monitoring culture, resources from organizations like NIST and universities such as MIT provide good background on reliability practices.

If your codebase still relies heavily on @ to suppress warnings or die() inside business logic, these examples of implementing error handling in PHP give you a modern alternative.

FAQ: common questions and short examples

What are some simple examples of PHP error handling for beginners?

A very small example of error handling is wrapping risky code in try/catch:

try {
    echo 10 / 0; // Will trigger a warning converted to ErrorException if you use set_error_handler
} catch (Throwable $e) {
    error_log($e->getMessage());
    echo 'Math error.';
}

Pair this with set_error_handler from the earlier section and you have a simple, consistent pattern.

Can you show an example of handling JSON errors in PHP?

Yes. With JSON_THROW_ON_ERROR, you can treat bad JSON as an exception instead of checking json_last_error().

<?php
\(json = \)_POST['payload'] ?? '';

try {
    \(data = json_decode(\)json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    http_response_code(400);
    echo 'Invalid JSON payload.';
    exit;
}

This pattern fits nicely with the other examples of implementing error handling in PHP shown above.

How do I avoid exposing sensitive error details to users?

Use environment-aware behavior: show stack traces only in development, log details in production, and return generic messages to users. The centralized handler example earlier demonstrates this. For general secure coding guidance, the OWASP Foundation maintains widely respected recommendations that align well with these PHP practices.

Are there examples of integrating PHP error handling with monitoring tools?

Most monitoring platforms (Sentry, New Relic, etc.) provide PHP SDKs that hook into set_exception_handler and register_shutdown_function. You keep your local logging, and the SDK forwards errors to the external service. The patterns in the examples above still apply; you just call the SDK’s capture function inside your handlers.


If you treat these snippets as building blocks rather than isolated tricks, you’ll end up with a consistent, predictable error handling strategy. That’s the difference between “it works on my machine” and “it behaves the same way every time it fails.”

Explore More PHP Code Snippets

Discover more examples and insights in this category.

View All PHP Code Snippets