Improving Laravel’s Error Handler
This is an example of our refactoring around Lumen/Laravel’s API Error Handler over time based on our automated code analysis.
Managing large projects gets really difficult over time. Each iteration we add some logic which leads to a complex system. Every aspect of the project is important and we want to keep it clean and SOLID as much as possible. Once we see or detect that a class gets complicated and needs to be refactored we should clean it up not to cause problems in the future.
I’ll show you how we solved a problem in one of our projects. Our error processing logic became complex and it was handled by a single class. Over time we’ve cleaned up the code leading to separating pieces of logic in separate files to honor the Single Responsibility Principle.
We had a Cyclomatic complexity (CYC) = 19 which is a high number. Next to it is our result of refactoring with 6 CYC.
This is the second time we’ve made refactoring in this class. I’ll show you each iteration and how we were able to clean up the code and show good metrics.
We can still make some improvements and we will show you how below. Let’s see our stack first.
We use the Lumen PHP framework (Based on Laravel) which has it’s suggested structure for handling this kind of logic.
Laravel is really helpful and easy to use, but sometimes you still need to handle cases by yourself and use different patterns to make it cleaner.
One of those cases is rendering exceptions and hiding application logic from the end-user. End-users still need to see relevant errors in their context.
We use Exception handler to respond with only needed info about errors in our API requests. It’s preconfigured and all exceptions are reported and rendered back to the user.
For development purposes, it is showing full details as a response. And for production, it’s displaying friendly nice-looking messages with no application logic in it.
It supports Custom Exceptions with their own direct response or report format which is useful for application errors. They are called reportable exceptions:
Now, we will go through a timeline of code evolution. I’ll only show significant changes and break it down to 5 timestamps or you can check the full revision with visual changes directly on GIST here:
Initial changes (22/08/2019) to default Handler:
With added logic (05/09/2019) for ValidationException and unique QueryException:
Here we had a warning about 13 CYC.
With added logic (19/11/2019) for ModelNotFoundException and Guzzle’s RequestException:
Red flags show us that we have a problem and CYC is 19 on the method. We decided that this should be simplified and organized better.
Started refactoring (22/01/2020) to move complext logic in separate classes:
All complex logic is separated in different classes and we have 12 CYC. Not great, but it’s a start. The code can live this way until we have some spare capacity to improve it. We use a mapping approach to associate an exception to its handler. Here is what the most complex handler looks like:
You can see all handlers in my GIST here
Final changes (23/03/2020) to clean up all “if” statements and use a handler for all exceptions we want to handle:
Our code goes from readable to a large complex sheet of code. Then overuse of “if” statements. In the end, we are mapping each custom exception logic with their own class that holds the logic to it with 6 CYC. We can now add as many formatted error responses as we want without increasing the code’s complexity.
This is only a small part of the application logic and it’s evolving slow. And sometimes this makes it more difficult to spot places that need change. Like the boiling frog which put in tepid water stays until cooked. If the frog had a warning or an error that pops when the temperature passes a threshold like our metrics it could be alive. Our code, thankfully, is out of danger now and bugs will be easily spotted.
You can check the code in this post on GIST in the following links: