A beautiful error page on WordPress with spatie/laravel-ignition

I always wanted and admired Laravel new error page when they migrated to spatie/laravel-ignition package. Today, I decided to experiment the implementation of this package into my WordPress project which already using composer (roots/bedrock) configuration.

Updated on 29 November 2024 with refactored codes. I realised the early implementation was skipping other types of errors, such as normal PHP errors which are not Throwable or Exception.

Abdussalaam Syaahid

Firstly, import the package into your project using composer:

composer require spatie/laravel-ignition

Then, implement the code below into your web/wp-config.php to load it as early as possible. I decided to add the codes here before I need few of WordPress core function such as add_action.

<?php

/**
 * Do not edit this file. Edit the config files found in the config/ dir instead.
 * This file is required in the root directory so WordPress can find it.
 * WP is hardcoded to look in its own directory or one directory up for wp-config.php.
 */

use Spatie\Ignition\Ignition;

require_once(dirname(__DIR__) . '/vendor/autoload.php');
require_once(dirname(__DIR__) . '/config/application.php');
require_once(ABSPATH . 'wp-settings.php');


// Initialize Ignition if in the development environment
if (defined('WP_ENV') && WP_ENV !== 'production' && !defined('WP_CLI')) {
	// Initialize Ignition
	$ignition = Ignition::make()
		->registerMiddleware([
			function ($report, $next) {
				$report->group('WordPress Info', [
					'Version' => get_bloginfo('version'),
					'Theme' => wp_get_theme()->get('Name'),
				]);
				return $next($report);
			}
		])
		->setTheme('dark')
		->applicationPath(dirname(__DIR__)) // Set to project root
		->shouldDisplayException(true);

	// Register Ignition
	$ignition->register();

	ob_start();

	register_shutdown_function(function () use ($ignition) {
		$error = error_get_last();
		if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
			$exception = new \ErrorException(
				$error['message'],
				0,
				$error['type'],
				$error['file'],
				$error['line']
			);

			// Clear all previous output
			while (ob_get_level() > 0) {
				ob_end_clean();
			}

			// Display Ignition's error page
			if (class_exists(Ignition::class)) {
				$ignition->handleException($exception);
			} else {
				echo '<pre>';
				print_r($exception);
				echo '</pre>';
			}

			exit; // Stop further execution

			// Flush output buffer if no error occurred
			ob_end_flush();
		}
	});

	set_exception_handler(function (\Throwable $e) use ($ignition) {
		// Clear output buffer to prevent partial rendering
		while (ob_get_level() > 0) {
			ob_end_clean();
		}

		// Display Ignition's error page
		if (class_exists(Ignition::class)) {
			$ignition->handleException($e);
		} else {
			echo '<pre>';
			print_r($e);
			echo '</pre>';
		}

		exit; // Prevent further execution
	});

	add_action('wp', function () use ($ignition) {
		add_action('template_redirect', function () use ($ignition) {
			$error = error_get_last();
			if ($error !== null) {
				// Convert WordPress errors to exceptions
				$exception = new \ErrorException(
					$error['message'],
					0,
					$error['type'],
					$error['file'],
					$error['line']
				);

				// Clear all output and handle with Ignition
				while (ob_get_level() > 0) {
					ob_end_clean();
				}

				if (class_exists(Ignition::class)) {
					$ignition->handleException($exception);
				} else {
					echo '<pre>';
					print_r($exception);
					echo '</pre>';
				}

				exit;
			}
		});
	});
}

You might want to implement this constant to disable Query Monitor plugin error handler which can be implemented in the config/application.php.

Config::define('QM_DISABLE_ERROR_HANDLER', true);

That is all, and happy coding!