A PHP Pattern To Avoid Try/Catch Blocks Repetition | by Lucas Pereyra | May, 2022


And keep them simple

Photo by Bia Andrade on Unsplash

A couple of days ago, I saw this YouTube short from a highly recommended YouTube channel that shows a very elegant way of dealing with try / catch blocks nesting and repetition in JavaScript, and I thought whether something similar could be done for PHP.

We use try / catch blocks in PHP to make our programs fault-tolerant or to provide error handling up to a certain point. With try / catch blocks we can gracefully process certain errors that might occur at runtime without necessarily making our application stop.

Error handling might involve providing feedback to the final user to let them know what went wrong, sending status and debugging data to logging services for later further analysis, correcting the error by using some default configurations or even ignoring the error without taking any actions at all .

Many PHP native functions, third-party libraries, and frameworks throw certain types of errors / exceptions if something unexpected happens during their execution. According to the type of error, the execution context and error handling policies that each one of them implements, different specific types of exceptions might be thrown, and each one of these could need to be handled in a specific way, thus leading the client code to look something like this:

Imagine this repeated pattern is spread in many other functions or methods, it is certainly quite verbose and cumbersome to work with, and it makes the code a little bit more complex to follow.

By encapsulating the error-prone code in a commandwe can easily wrap it with an error handler that will be responsible for managing the errors and dealing with the try / catch logic:

This is a combination of the command, template methodand decorator design patterns that results in a very flexible mechanism for handling errors without spreading try / catch blocks to all the codebase. By providing different implementations of the handleError method, different levels of abstraction and flexibility can be reached, and different error handling approaches could be supported.

In this section, I’ll provide some usage examples that show how the handleError method could be implemented to fit different error handling policies and scenarios, and how flexible this pattern might be. I hope this helps you understand how this pattern works and provides some insights for readers to think about more examples and scenarios where it would be applicable.

Example # 1: Handling errors in the wrapper itself

According to the type of error and the error handling approaches our program might use, we might want to report some specific types of error that occurred (ie, using an external logging service) whereas just ignoring the rest of them. An error handler wrapper that can be configured to report certain errors and ignore others may work for this scenario, and the implementation could be similar to the following:

Example # 2: Using an error accumulator

Often if an error of type X occurs, the execution flow and response to the end user may be different than if an error of type Y occurs. We might think, for example, that the command’s execution result could be influenced by the specific types of error that happened.

A first approach for supporting these scenarios would be to let the command itself handle any errors that pop up during its execution. A second, more sophisticated one, would be having an error accumulator that holds the current state of the program regarding errors and exceptions of interest:

This very simple error accumulator version could be a more sophisticated one that besides registering any errors that happened, sends logs to an external logging service, enables some warning status at the program level, or triggers some background health checking process.

Example # 3: Composition of error handlers

A more complex yet highly flexible version of this pattern can be reached by combining it with the chain of responsibility design pattern, hence, allowing us to customize our error handling logic so as not to tie it to a specific implementation. Take a look at the following snippets:

This last example shows a very flexible approach. I provide a default implementation as the last chain steel which does not do anything with the errors that were not handled by the previous steels. This can be changed to throw up the exceptions that were not treated, to avoid hiding undesired errors behind this non-reporting logic.

In addition, a flexible error handler subclass that makes use of two client-defined PHP callbacks for evaluating the canHandle method and performing the error handling could be created as an ErrorHandler subclass, too. This would allow you to make a custom on-the-fly error handler just by defining it through callbacks.

These are just some basic examples I’ve provided to illustrate how this pattern could be used. More good insights and fancy designs could arise from this starting point that might help you deal with the try / catch blocks spread.

As you may have noticed, by combining design patterns we get very rich powerful and flexible solutions, hence I encourage you to do so, but always be aware of not over-engineering things that could be solved with simpler approaches.



Please enter your comment!
Please enter your name here