Advanced PHP Exceptions handling: How to

Abdulbasit Rubeya
6 min readOct 25, 2024

--

Serve error pages— freefrontend

When we talk about exception handling, what generally comes to mind for most of us is the traditional try-catch blocks. While these constructs are fundamental to handling errors gracefully in PHP, effective exception handling goes beyond just catching exceptions.

In today’s article, we will explore advanced techniques for handling exceptions in PHP, focusing on error handling, logging, and useful enhancements like enabling debug states etc

Error Handling

Error handling is a crucial part of any robust application. PHP provides several mechanisms for handling errors, and one of the most effective methods is to convert errors into exceptions.

In any Program, errors can occur at any moment, whether due to incorrect user input, server misconfiguration, or external resource failures. To manage these errors effectively, it’s essential to implement a robust error handling mechanism. One common approach is to create a dedicated error handler class.

class ErrorHandler
{
public function __construct()
{
// Set custom error and exception handlers
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
}

// Error handler to convert errors into exceptions
public function handleError($severity, $message, $file, $line)
{
throw new ErrorException($message, 0, $severity, $file, $line);
}

// Exception handler to log exceptions and display error messages
public function handleException(Throwable $exception)
{
// Handle exception (will be detailed in the Logging section)
}
}

__contstrctor

  • This method sets up the custom error and exception handlers using set_error_handler and set_exception_handler.
  • When an error occurs, handleError is invoked, and when an unhandled exception is thrown, handleException is called.

handleError

  • This method converts traditional PHP errors into exceptions.
  • It receives parameters like severity, message, file, and line number, and throws an ErrorException with this information.
  • This allows for a unified approach to error handling since both errors and exceptions can now be processed by the same exception handler.

Logging

This would be our next step, a crucial aspect of error handling, enabling developers to keep track of issues and understand the context in which they occurred, let’s modify our ErrorHandler class, we’ll modify the __constructor and the handleException methods

class ErrorHandler{

private $logFile;

public function __constructor(){

// Set the log file location
$this->logFile = 'storage/logs/error-' . date('Y-m-d') . '.log';

// Ensure the logs directory exists
if (!file_exists(dirname($this->logFile))) {
mkdir(dirname($this->logFile), 0777, true);
}

...

}

... other methods

// Exception handler to log exceptions
public function handleException(Throwable $exception)
{
$message = '[' . date('Y-m-d H:i:s') . '] ' . $exception->getMessage() . PHP_EOL;
$message .= 'File: ' . $exception->getFile() . ' on line ' . $exception->getLine() . PHP_EOL;
$message .= 'Stack trace: ' . $exception->getTraceAsString() . PHP_EOL . PHP_EOL;

file_put_contents($this->logFile, $message, FILE_APPEND);
}

}
  • The $logFile property is initialized to create a log file with the current date. This keeps logs organized by date, making it easier to find specific logs.
  • The constructor checks if the logs directory exists. If it doesn’t, it creates the directory with proper permissions.

handleExcpetion

  • This method constructs a log message that includes:
    — The current timestamp
    — The exception message
    — The file and line number where the exception occurred
    — The stack trace of the exception
  • Finally, it appends this log message to the log file using file_put_contents.

Why Log Errors?

Logging exceptions, allows one to track errors over time and analyze patterns, making it easier to identify and resolve issues. The log file created in this implementation contains timestamped entries that provide invaluable insights into application behavior.

Presentation

An effective error handling strategy not only logs errors but also presents them in a user-friendly manner. The handleException method can be extended to display meaningful error messages to users.

public function handleException(Throwable $exception)
{
// Log exception details
// (log code above)

echo <<<HTML
Error: {$exception->getMessage()}<br>
File: {$exception->getFile()} on line {$exception->getLine()}<br><br>
Stack trace: <pre>{$exception->getTraceAsString()}</pre>
HTML;

exit;
}

Enhancements

A good error handler does not just end with auto logging and error dumping, but we should also be able to define how data is presented and where logging occurs

Inside the ErrorHandler class let’s add another property $debugState and two extra methods.

class ErrorHandler{

private $logFile;
private $debugState;

# previous methods

public function setLogFile($file)
{
$this->logFile = $file;
return $this;
}

public function setDebugState($state)
{
$this->debugState = $state;
return $this;
}

}

To reflect the debug state we’ll have to modify the handleException method so as we can choose how is the error displayed based on the debug state

public function handleException(Throwable $exception)
{
// Log exception details

if($this->debugState) {
echo <<<HTML
Error: {$exception->getMessage()}<br>
File: {$exception->getFile()} on line {$exception->getLine()}<br><br>
Stack trace: <pre>{$exception->getTraceAsString()}</pre>
HTML;
} else {
echo "<h1>Something went wrong. Please try again later.</h1>";
}
}
  • The handleException method checks the debugState property to determine how much detail to show to the user.
  • If debugState is true, it displays detailed information about the error, including:
    — The error message
    — The file and line number where the error occurred
    — The stack trace in a preformatted block for better readability
  • If debugState is false, it shows a generic user-friendly error message, protecting sensitive information from being exposed to the end user.

Now that we have everything we need to start a basic handler, our class should look something like this

class ErrorHandler
{
private $logFile;
private $debugState;

public function __construct()
{
// Set the log file location
$this->logFile = 'storage/logs/error-' . date('Y-m-d') . '.log';

// Ensure the logs directory exists
if (!file_exists(dirname($this->logFile))) {
mkdir(dirname($this->logFile), 0777, true);
}

// Set custom error and exception handlers
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
}

// Error handler to convert errors into exceptions
public function handleError($severity, $message, $file, $line)
{
throw new ErrorException($message, 0, $severity, $file, $line);
}

// Exception handler to log exceptions and display error messages
public function handleException(Throwable $exception)
{
// Log exception details
$message = '[' . date('Y-m-d H:i:s') . '] ' . $exception->getMessage() . PHP_EOL;
$message .= 'File: ' . $exception->getFile() . ' on line ' . $exception->getLine() . PHP_EOL;
$message .= 'Stack trace: ' . $exception->getTraceAsString() . PHP_EOL . PHP_EOL;

file_put_contents($this->logFile, $message, FILE_APPEND);

if($this->debugState) {
echo <<<HTML
Error: {$exception->getMessage()}<br>
File: {$exception->getFile()} on line {$exception->getLine()}<br><br>
Stack trace: <pre>{$exception->getTraceAsString()}</pre>
HTML;
} else {
echo "<h1>Something went wrong. Please try again later.</h1>";
}

exit;
}

public function setDebugState($state)
{
$this->debugState = $state;
return $this;
}

public function setLogFile($file)
{
$this->logFile = $file;
return $this;
}
}

Usage

Here’s how you can use the ErrorHandler class in a simple PHP application:

// Include the ErrorHandler class
require_once 'ErrorHandler.php';

// Instantiate the ErrorHandler
$errorHandler = new ErrorHandler();

// set up the configurations (optional)
$errorHandler->setDebugState(true);
$errorHandler->setLogFile('error.log')

// you can also use a method chain with the configurations
// $errorHandler
// ->setLogFile('error.log')
// ->setDebugState(true);


// Sample function that may throw an exception
function divide($a, $b) {
if ($b === 0) {
throw new Exception('Division by zero is not allowed.');
}
return $a / $b;
}

echo divide(10,0); // we expect our handler to intercept this

Customizing Your Error Handler

While the basic implementation of an error handler is functional, there are several enhancements you can consider:

  1. Error Severity Levels: Customize error handling based on severity, allowing for different logging and display strategies depending on the type of error (e.g., notices vs. critical errors).
  2. Email Notifications: Implement a mechanism to send email notifications for critical errors to ensure that the development team is promptly informed.
  3. User-Friendly Error Pages: Customize error pages for different types of exceptions to improve user experience, guiding users back to the main functionalities of your application.

The possibilities are endless, As you build and refine your PHP applications, consider these strategies to improve your error handling process, Happy Coding!

--

--

Abdulbasit Rubeya
Abdulbasit Rubeya

Responses (3)