Question

PHP Error Reporting and Displaying Errors in the Browser

phpdebuggingwarnings

Question

Why are PHP warnings and errors written to the Apache error log instead of being displayed in the browser?

In this script, $err is created only inside the POST branch but is later used in foreach ($err as $line). This should produce an undefined-variable warning on an initial GET request. Also, changing an SQL statement from INSERT INTO to the invalid DELETE INTO should cause a PDO error.

<?php
error_reporting(E_ALL);
?>

<!-- Later in the page -->
<?php foreach ($err as $line) { ?>
    <div><?php echo $line; ?></div>
<?php } ?>

Even after setting display_errors = On and error_reporting = E_ALL | E_STRICT in php.ini and restarting Apache, no errors or warnings appear on the page. What PHP settings and code flow issues control whether errors are displayed in the browser?

Short Answer

PHP has separate controls for which errors are reported and where they are sent. error_reporting() selects error levels, while display_errors determines whether reported errors are printed in the HTTP response. Errors can still be logged when browser display is disabled. You will also learn why a redirect can make an error message appear to vanish and how to initialize variables so ordinary requests do not create warnings.

Concept

PHP error handling has two related but separate configuration decisions:

  • error_reporting: Which categories of PHP errors PHP should report.
  • display_errors: Whether PHP should add reported errors to the response sent to the browser.
  • log_errors: Whether PHP should write reported errors to a server log.

For local development, a common setup is:

error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');

For production, displaying errors is unsafe because messages can reveal file paths, database details, configuration, or application logic. Production applications normally log errors but show a safe, generic error page.

In the provided code, error_reporting(E_ALL) enables reporting, but it does not force browser display. If display_errors is disabled for the PHP web-server environment, the warning goes only to the log.

There is also a control-flow issue: after a PDO exception is caught and echoed, the script still runs:

header('Location: ' . $_SERVER['PHP_SELF']);
exit;

A redirect tells the browser to request another page. The browser will generally show the redirected page, not the text from the prior response. Handle the error and stop or avoid redirecting when the operation fails.

Mental Model

Think of PHP errors as alerts from a building alarm system:

  • error_reporting decides which sensors are active.
  • display_errors decides whether the alert appears on the public display screen (the browser).
  • log_errors decides whether the alert is written in the security logbook (the server log).

An alert can be recorded in the logbook without being shown publicly.

A redirect is like immediately sending the visitor to a different room. Even if an alert briefly appeared in the first room, the visitor ends up looking at the second room instead.

Take Quiz

Syntax and Examples

Enable detailed errors temporarily during local development:

<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');

Place this at the beginning of the executed PHP file, before output. This is useful for runtime errors in that file.

For errors that happen before the script can run, such as PHP startup or syntax errors, configure the web-server PHP configuration instead:

display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On

The option name is exactly display_errors. A misspelling such as display_erros is ignored because it is not a valid PHP directive.

Initialize variables before branches that use them:

<?php
$err = [];
$form = [
    'display_name' => '',
    'email' => '',
    'password' => '',
];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (empty($_POST['display_name'] ?? '')) {
        $err[] = 'Display name is required.';
    }
}

foreach ($err as $message) {
    echo '<p>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</p>';
}

$err = [] ensures that foreach always receives an array, including on the first GET request.

Step by Step Execution

Consider this small example:

<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');

$errors = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = trim($_POST['email'] ?? '');

    if ($email === '') {
        $errors[] = 'Email is required.';
    }
}

foreach ($errors as $error) {
    echo $error . PHP_EOL;
}

On an initial page visit:

  1. PHP enables reporting and browser display for this request.
  2. $errors becomes an empty array.
  3. The request method is usually GET, so the POST block is skipped.
  4. foreach runs over an empty array.
  5. Nothing is printed, and no warning occurs.

When the form is submitted with an empty email:

  1. The request method is POST.
  2. The null coalescing operator (??) supplies '' if email was not sent.
  3. The validation condition adds a message to $errors.
  4. foreach prints the validation message.

In contrast, this code can warn on a GET request:

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $err = [];
}

foreach ($err as $line) {
    echo $line;
}

$err does not exist unless the request is POST.

Real World Use Cases

  • Local debugging: Show warnings, notices, and stack traces while building a feature on a local machine.
  • Production monitoring: Keep display_errors disabled but use logs to investigate failures without exposing internal details to visitors.
  • Form validation: Create an errors array before validating fields so every request can safely render error messages.
  • Database operations: Catch database exceptions, log technical details, and show users a general failure message.
  • API development: Return controlled JSON error responses rather than raw PHP warnings that would break the API response format.
  • Deployment troubleshooting: Compare the PHP configuration used by the web server with the configuration used by the command line.
Take Quiz

Real Codebase Usage

Real projects usually separate developer diagnostics from user-facing responses.

Validate first, then perform database work

$errors = [];

$email = trim($_POST['email'] ?? '');
if ($email === '') {
    $errors[] = 'Email is required.';
}

if ($errors !== []) {
    // Render the form again with errors.
    return;
}

// Only attempt the database operation after validation succeeds.

Catch, log, and return a safe message

try {
    $statement = $pdo->prepare(
        'INSERT INTO users (display_name, email) VALUES (:display_name, :email)'
    );

    $statement->execute([
        ':display_name' => $displayName,
        ':email' => $email,
    ]);
} catch (PDOException $exception) {
    error_log($exception->getMessage());
    $errors[] = 'We could not create your account. Please try again later.';
}

The technical exception goes to the server log. The user gets a message that is useful but does not reveal database structure or credentials.

Redirect only after success

The Post/Redirect/Get pattern is useful after a successful form submission:

header('Location: /registration-complete.php', true, 303);
exit;

Do not redirect after a failed operation if the current page needs to show validation or database error feedback.

Common Mistakes

Confusing reporting with display

This reports all error levels but does not guarantee browser output:

error_reporting(E_ALL);

For local development, also enable display:

ini_set('display_errors', '1');

Misspelling display_errors

This setting is invalid because its name is misspelled:

display_erros = On

Use:

display_errors = On

Editing the wrong php.ini

PHP running through Apache, PHP-FPM, and the command line can load different configuration files. php --ini describes the CLI configuration; it may not be the configuration used for a browser request.

Create a temporary diagnostic file in a safe local environment:

<?php phpinfo();

Check Loaded Configuration File and the effective values for display_errors and error_reporting. Remove the file afterward, especially on public servers.

Forgetting to initialize $err

Broken code:

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $err = [];
}

foreach ($err as $message) {
    echo $message;
}

Fix it by initializing before the condition:

$err = [];

Redirecting after an exception

This can hide feedback by sending the browser to a new request:

catch (PDOException $e) {
    echo $e->getMessage();
}

header('Location: /register.php');
exit;

Instead, store a safe error message and render the form again, or redirect only in the success path.

Displaying raw database exceptions to users

echo $e->getMessage() may expose SQL, tables, paths, or connection details. It is acceptable only for controlled local debugging. Log the exception and display a generic message in production.

Assuming E_STRICT is required

In modern PHP versions, E_ALL already includes the relevant standard error levels. Prefer the clearer configuration:

error_reporting = E_ALL

Comparisons

Setting or techniqueWhat it doesTypical development useTypical production use
error_reporting(E_ALL)Selects error categories PHP reportsEnable allUsually enable all and log them
display_errorsAdds errors to browser outputOn locallyOff
log_errorsWrites errors to configured logsUsually OnOn
ini_set()Changes an option for the current request when allowedQuick temporary debuggingNot a replacement for deployment configuration
php.iniSets server or SAPI configurationLocal environment defaultsSecure environment defaults
echo $e->getMessage()Prints technical exception detailsTemporary local debuggingAvoid; log it instead
Redirect after POSTStarts a new browser requestAfter successAfter success, with user-safe status messaging

E_ALL | E_STRICT is generally unnecessary on modern PHP versions because E_ALL is the appropriate comprehensive setting. The important distinction is that E_ALL selects errors; it does not itself display them.

Take Quiz

Cheat Sheet

// Local development only: place near the start of the entry script.
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
; php.ini development settings
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On
  • error_reporting = which errors PHP reports.
  • display_errors = whether browser output includes errors.
  • log_errors = whether errors go to logs.
  • The valid directive is display_errors, with two r characters in errors.
  • Initialize arrays before conditionals if they are used later: $errors = [];.
  • Use $_POST['field'] ?? '' when a submitted field may be absent.
  • Redirect only after a successful action when the current response does not need to display errors.
  • Check the browser SAPI's active configuration with a temporary phpinfo() page.
  • Never leave display_errors = On on a public production site.

FAQ

Why does PHP show errors in the log but not in my browser?

Usually log_errors is enabled while display_errors is disabled. PHP is reporting the problem, but it is configured to send it only to the server log.

Is error_reporting(E_ALL) enough to display PHP errors?

No. It chooses which error levels are reported. display_errors must also be enabled for reported errors to be included in the browser response.

Why does display_errors = On in php.ini not work?

Confirm that the directive is spelled correctly and that you edited the configuration file loaded by Apache or PHP-FPM, not only the CLI PHP configuration. Use phpinfo() temporarily to check the loaded file and effective values.

Should I use E_ALL | E_STRICT?

Usually no. In modern PHP, use E_ALL. Adding E_STRICT is redundant in supported modern versions.

Why is $err undefined on the first page visit?

It is assigned only inside the POST branch, but foreach ($err as ...) runs for both GET and POST requests. Initialize it before the condition with $err = [];.

Why does my PDO error disappear after I echo it?

If your code then sends a Location header, the browser follows the redirect and displays the next response. Do not redirect after a failure that must be shown on the current page.

Should a production website display PHP errors?

No. Keep display_errors off in production, log errors, and show users a safe message. Detailed errors can reveal sensitive information.

Can ini_set('display_errors', '1') show every error?

It can help with runtime errors after the script begins executing. Startup and parse errors may require configuration before the script runs, such as the correct php.ini setting.

Take Quiz

Mini Project

Description

Build a small PHP registration-form handler that validates input, safely initializes form state, catches database errors, and redirects only after a successful insert. It demonstrates the difference between user-facing validation feedback and technical error logging.

Goal

Create a form handler that shows validation errors on the same page and logs PDO failures without exposing raw database messages to users.

Requirements

Initialize the form data and error list for every request. Validate a display name and email address on POST requests. Show validation errors above the form. Use a prepared INSERT statement with PDO exceptions enabled. Redirect only after a successful database insert. Log database exceptions and show a safe failure message.

Take Quiz

Keep learning

Related questions

View more