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
- 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.
The new Error & Exception subsystem starts with two new classes
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
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
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
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 and Exception events
As mentioned earlier, the new subsystems dispatch events when they handle errors or exceptions. You can listen to the
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.