New in CakePHP - Improved Error and Exception Handling
CakePHP 4.4.0RC1 was released recently and I wanted to go over the new error subsystem that is being added for 4.4. I haven’t ever really loved the interface that CakePHP provided for error and exception handling. I identified a few problems that I wanted to resolve:
- The way errors were rendered was implemented with inheritance. This meant that code was harder to test, reuse and extend. It also means that application code has to handle selecting the right class to use instead of being able to declaratively configure the framework.
- The way fatal errors was handled was awkward. Instead of being treated like exception they were part of
ErrorHandler
. - Once an error was handled by the framework, you’re exposed to the basic PHP interface, and I believe we can model PHP Errors as an object and do better.
- DebugKit relies on some hacks in the error system that no longer work in PHP8. For this reason alone, we needed to make a change.
I wanted to address these problems in a forward facing way giving users lots of time to adopt the new solution before we remove the deprecated implementation in 5.0.
Getting started
The new Error & Exception subsystem starts with two new classes ErrorTrap
and ExceptionTrap
. These classes act as entry points to handling errors and exceptions. The trap classes provide you pluggable interfaces to control how errors are rendered, and logged. They also expose application level events that your application (and DebugKit) can interact with. To migrate to the new system first remove the code that uses ErrorHandler
and ConsoleErrorHandler
and add the following:
- use Cake\Error\ErrorTrap;
- use Cake\Error\ExceptionTrap;
- (new ErrorTrap(Configure::read('Error')))->register();
- (new ExceptionTrap(Configure::read('Error')))->register();
- // In your src/Application.php ensure you have:
- $middlewareQueue->add(new ErrorHandlerMiddleware(Configure::read('Error')));
This code attaches the ErrorTrap
and ExceptionTrap
as PHP’s default handlers for error and exceptions. We also pass the same configuration into the ErrorHandlerMiddleware
, which will let the middleware use ExceptionTrap
with the same configuration as your default handler does. Beyond changing the entry point, Exception handling is mostly unchanged.
Extending error handling
The new system provides a few extension points and interfaces to allow you to swap in custom behavior if you need it. Error rendering requires you to implement the Cake\Error\ErrorRendererInterface
which looks like this interface:
- interface ErrorRendererInterface
- {
- /**
- * Render output for the provided error.
- *
- * @param \Cake\Error\PhpError $error The error to be rendered.
- * @param bool $debug Whether or not the application is in debug mode.
- * @return string The output to be echoed.
- */
- public function render(PhpError $error, bool $debug): string;
- /**
- * Write output to the renderer's output stream
- *
- * @param string $out The content to output.
- * @return void
- */
- public function write(string $out): void;
- }
You may notice that the parameter to render
is PhpError
. This is a new addition to 4.4 and it models a PHP errors to implement an interface that closely emulates PHP exceptions.
The constructor for an error renderer gets all of the configuration data passed to the ErrorTrap
. You can configure which error renderer is used with the Error.errorRenderer
configuration option. Extending error logging works the same as before and you need to implement Cake\Error\ErrorLoggerInterface
and configure Error.logger
.
Error and Exception events
As mentioned earlier, the new subsystems dispatch events when they handle errors or exceptions. You can listen to the Error.beforeRender
and Exception.beforeRender
events on the global event manager.
That’s it for today. If you have any feedback on the new error subsystem, please open a GitHub Issue and let us know how it could be better.
i really appreciate your efforts Mark.
you are my role model.follows you from more than 7+ years
Ashish RB on 3/24/22
Mark I watched you stress over the use of “Trap” in the names but I think it fits perfectly. Like “trapping” rats as soon as they’re discovered in a pantry, then you can do what you want with them. I think the analogy fits. Keep up the great work.
Jamison on 6/15/22
Thank you for the kind words Ashish and Jamison.
mark story on 12/29/22