📜 ⬆️ ⬇️

Exception hierarchy in modern PHP application

The task of publication: it is possible to describe the way of organizing the hierarchy of exceptions and their processing in the application. Without reference to frameworks and specific architecture. The described method is the de facto standard in the community: it is used in many serious libraries and frameworks. Including Zend, symfony. Despite its consistency and universality, I did not find a formal description of the proposed approach in Russian. After repeated oral presentation of the concept to colleagues, the idea was born to issue it in the form of a publication on Habrahabr.


In PHP, starting with the 5th version, an exception mechanism is available. In the current, 7th version, this mechanism has been improved and revised to uniformly handle various errors using the try{} catch... construct


In the standard library (SPL) PHP provides a ready-made set of base classes and interfaces for exceptions. In the 7th version of this set was expanded interface Throwable . Here is a diagram of all types available in version 7 (image - link):


PHP7 exception type diagram


For junior developers, it may be useful to first understand all the subtleties of syntax, the logic of exceptions, and error handling in general. I can recommend the following articles in Russian:



Highlights of your application's exception hierarchy.


Common interface


Use a common interface (marker) for all exceptions defined in your application. The same rule applies to individual components, modules, packages, i.e. subspaces of your code names. For example, \YourVendor\YourApp\Exception\ExceptionInterface , or \YourVendor\YourApp\SomeComponent\Exception\ExceptionInterface , in accordance with PSR-4.


An example can be viewed in any component of symfony, Zend, etc.


For each situation - its own type


Each new exception should result in the creation of a new type (class) of an exception. The class name should semantically describe this situation. Thus, the code calling the exception constructor, and its cast, will be: read, self-documented, transparent.


Symmetrically and the code with catching the exception will be explicit. The code inside the catch should not attempt to determine the situation by the message or other indirect signs.


Extend base types


Base types serve to inherit from your own types of exceptions from your application. This is directly stated in the documentation .


An example can be viewed in any component of symfony, or Zend.


Forwarding and transformation in accordance with the level of abstraction


Since exceptions pop up throughout the call stack, there may be several places in the application where they are caught, skipped, or converted. As a simple example: a standard PDOException is logical to catch in the DAL layer, or in the ORM, and forward your own DataBaseException instead, which in turn is caught in the layer above, for example, the controller, where to convert to HttpException. The latter can be intercepted in the dispatcher code, at the topmost level.


Thus, the controller does not know about the existence of PDO - it works with an abstract repository.


The decision of what should be forwarded, what is transformed, and what is processed, in each case lies with the programmer.


Use the features of the standard designer


You should not override the standard Exception constructor - it is ideally designed for its tasks. Just do not forget to use it for its intended purpose and call the parent with all the arguments, if overload is required. Summing up this point with the previous one, the code might look like this:


 namespace Samizdam\HabrahabrExceptionsTutorial\DAL; class SomeRepository { public function save(Model $model) { // ..... try { $this->connection->query($data); } catch (\PDOException $e) { throw new DataBaseException(\i18n('Error on sql query execution: ' . $e->getMessage(), $e->getCode()), $e); } } } // ..... namespace Samizdam\HabrahabrExceptionsTutorial\SomeModule\Controller; use Samizdam\HabrahabrExceptionsTutorial\Http\Exception; class SomeController { public function saveAction() { // ..... try { $this->repository->save($model); } catch (DataBaseException $e) { throw new HttpException(\i18n('Database error. '), HttpException::INTERNAL_SERVER_ERROR, $e); } } } // ..... namespace Samizdam\HabrahabrExceptionsTutorial\Http; class Dispatcher { public function processRequest() { // ..... try { $controller->{$action}(); } catch (HttpException $e) { //    http_response_code($e->getCode()); echo $e->getMessage(); } } } 

Summary


Why build a whole hierarchy involving interfaces, types, subtypes, and all this polymorphism for the sake of error handling? What is the meaning of this abstraction and how is its price justified?


When designing an application, abstraction is what adds flexibility, and allows you to postpone specific solutions and detailed implementation, until all the nuances are known, and the requirements for the code are not yet known, or they may change. This is an investment that pays off over time.


When your exceptions simultaneously have a marker interface, a super type SPL (like RuntimeException), a specific type that corresponds to the situation, you can fully control their processing and flexibly change its strategy in the future. By laying these abstractions at an early stage of development, in the future, as the requirements for handling errors appear and become more stringent, you will have at your disposal a tool that will help to fulfill these requirements.


At the prototype stage, it is enough to show the inscription "Opanki", for this it is enough to catch any Throwable in index.php.


In the alpha version will not be superfluous to distinguish situations 401, 404 and 500.


In beta testing, you will probably want to output the traces of all previous exceptions to form bug reports.


By the beginning of the operation, you will need a single point for logging exceptions.


And all the time the application is developing, you will only, as needed, add code with processing logic, without the need to make changes to the main code , where exceptions are generated.


')

Source: https://habr.com/ru/post/329140/


All Articles