The history of one class for error handling.

    Preface (Feel free to skip)



    Have a nice time of day.

    I had a desire to share a story with you. The history of the error handling class. Why not just bring the “as is” class and not let you judge its merits and demerits? The reason is simple. In this case, it will not be possible to understand why he became exactly what he is. It will be impossible to detect errors in the logic of its creation. It is impossible to understand whether your situation is similar to mine and whether it is possible for you to use it or, perhaps, it is better to turn your attention to another class of error handling. Fortunately, you will find many of them.

    And with what such a fright did I even start writing my own error handling class? After all, there is a lot of groundwork in this area. The accumulation of serious, very intelligent people. The reason is simple. Different reasons led to the creation of these classes; the creation of these classes pursued different goals. And, as we all know well, there are few bicycles and they all taste different.

    First, I had a need to write several classes that I could use in my projects and which would be simpler and faster than analogs and, most importantly, would implement the functionality that I need. No more and no less. No redundancy.

    First drafts



    No sooner said than done. Classes were written. And, quite naturally, they all had to somehow work with errors. Here are the requirements I made for such processing:
    • Errors should not be thrown into the stream. Classes are used in different projects, and often in scripts that are accessed using AJAX. Anyway, classes should never come into contact with presentation logic.
    • The error may correspond to descriptive text.
    • After the method is executed, it should be possible to determine whether an error occurred during its operation and what its descriptive text is.
    • Checking for an error should not be based on the value returned by the method, because sometimes it is impossible to return false / true, it is necessary to return data.
    So, everything is relatively simple. What we add to the class to implement error handling:
    • The err flag, which will be false if there is no error and the message assigned to the error otherwise. The flag should not be directly accessible outside the class.
    • There must be a method that clears the error flag. If an error has occurred, but it is not critical and its possibility is provided by the program logic, then the error should be reset the next time any class method is called, so that its state always corresponds to the presence or absence of errors during the last call to the class method.
    • There must be a method for cocking the error flag.
    • There should be a method that checks if there was an error and returns false if it is absent and the message assigned to the error otherwise.
    Copy Source | Copy HTML
    1. class test_class{
    2.     private $err;
    3.  
    4.     public function set_err($msg=''){
    5.         $this->err=$msg;
    6.     }
    7.  
    8.     public function clear_err(){
    9.         $this->err=false;
    10.     }
    11.  
    12.     public function is_err(){
    13.         return $this->err;
    14.     }
    15.  
    16.     public function some_method(){
    17.         $this->clear_err();
    18.         if(some_err){
    19.             $this->set_err($msg='Some error appear.');
    20.             return false;
    21.         }
    22.         return true;
    23.     }
    24.  
    25.     public function complex_method(){
    26.         $this->clear_err();
    27.         $this->some_method();
    28.         if($this->is_err()!==false){
    29.             return false;
    30.         }
    31.         return true;
    32.     }
    33. }
    34.  
    35. define('some_err',true);
    36. $test_instance = new test_class;
    37. $test_instance->complex_method();
    38. if($test_instance->is_err()!==false){
    39.     die($test_instance->is_err());
    40. }
    41. die('No error appear.');


    Separation of functionality in a separate class



    Here you go. Everything is working. There is only one unpleasant moment. This code is repeated in every class. While the class was one, everything was fine. But as soon as the number of classes grew, it became, at a minimum, uncomfortable. So the code was allocated in a separate class.
    Copy Source | Copy HTML
    1. class error_class{
    2.     private $err=false;
    3.  
    4.     public function set_err($msg=''){
    5.         $this->err=$msg;
    6.     }
    7.  
    8.     public function clear_err(){
    9.         $this->err=false;
    10.     }
    11.  
    12.     public function is_err(){
    13.         return $this->err;
    14.     }
    15. }
    16. class test_class{
    17.     public $err;
    18.  
    19.     public function __construct(){
    20.         $this->err = new error_class();
    21.     }
    22.  
    23.     public function some_method(){
    24.         $this->err->clear_err();
    25.         if(some_err){
    26.             $this->err->set_err($msg='Some error appear.');
    27.             return false;
    28.         }
    29.         return true;
    30.     }
    31.  
    32.     public function complex_method(){
    33.         $this->err->clear_err();
    34.         $this->some_method();
    35.         if($this->err->is_err()!==false){
    36.             return false;
    37.         }
    38.         return true;
    39.     }
    40. }
    41.  
    42. define('some_err',true);
    43. $test_instance = new test_class;
    44. $test_instance->complex_method();
    45. if($test_instance->err->is_err()!==false){
    46.     die($test_instance->err->is_err());
    47. }
    48. die('No error appear.');
    49.  
    50.  


    Handling critical errors using exceptions.



    Here you go. Already better. You can use it with any class by adding it once at the beginning of the class file that uses it. That's just the information about the presence of errors, he transmits, but does not deal with their processing. It would be rather strange to create an additional class to intercept and direct errors to the handler, so let's expand its functionality a little:
    Copy Source | Copy HTML
    1. require_once("error_class.php");



    • Simplify critical error handling. That is, such errors that should immediately cause the script to stop working and pass an error to the handler.
    • We implement the ability to assign a custom error handler. Such a handler cannot be unified and, accordingly, it should allow connecting a user-defined function.


    We use exceptions to handle critical errors .
    set_exception_handler - Defines a custom exception handler.
    restore_exception_handler - restores the previous handler for exceptions.
    Exception is the base class for all exceptions.

    Everything is elementary.
    • In the class constructor, we add an additional argument $ callback, which contains the callback function.
    • Add the variable $ exception_handler - this is a flag that indicates that a custom exception handler has been assigned. It is needed to determine in the destructor whether the previous exception handler needs to be restored.
    • We add the fire_err method to the class, with the help of which it will be possible to raise exceptions in case of a critical error.
    • We create a class destructor, which will, if necessary, restore the exception handler to its previous state.


    And our code takes the following form: Using the class will now look like this:
    Copy Source | Copy HTML
    1. class error_class{
    2.  
    3.     private $err=false;
    4.     private $exception_handler=false;
    5.  
    6.     public function __construct($callback=null){
    7.         if($callback!=null){
    8.             $this->exception_handler=true;
    9.             set_exception_handler($callback);
    10.         }
    11.     }
    12.  
    13.     public function set_err($msg=''){
    14.         $this->err=$msg;
    15.     }
    16.  
    17.     public function clear_err(){
    18.         $this->err=false;
    19.     }
    20.  
    21.     public function is_err(){
    22.         return $this->err;
    23.     }
    24.  
    25.     public function fire_err($msg='',$code=0){
    26.         $this->err=$msg;
    27.         throw new Exception($msg,$code);
    28.     }
    29.  
    30.     public function __destruct(){
    31.         if($this->exception_handler){
    32.             restore_exception_handler();
    33.         }
    34.     }
    35. }


    Copy Source | Copy HTML
    1.  
    2. define('some_err',true);
    3. $err = new error_class("err_overwork");
    4. $test_instance = new test_class;
    5. $test_instance->complex_method();
    6. if($test_instance->err->is_err()!==false){
    7.     $err->fire_err($test_instance->err->is_err(),-1);
    8. }
    9.  
    10. function err_overwork($error){
    11.     die("Ошибка: ".$error->getMessage()."<br/>Файл: ".$error->getFile()."<br/>Строка: ".$error->getLine());
    12. }


    Handling errors generated by the interpreter.



    Here you go. Now it remains to add only one. The ability (namely the opportunity) to transmit the user-defined error handling functions and errors generated by the interpreter (except, of course, syntax errors). In this way, a uniform display of errors can be achieved. Of course, the need for this is extremely controversial. Use or not - you decide. It seems to me that this can sometimes be appropriate.

    set_error_handler - allows you to pass error handling to the user exit.
    restore_error_handler - restores the previous error handler.
    ErrorException - An exception class to represent the error.

    In principle, there is nothing complicated. Using set_error_handlerinstall the handler and you're done. As additional arguments in the constructor, we accept:
    • $ err_redirect - Whether to catch the errors that the interpreter generates. Values: [true | false].
    • $ level - Permissible level of errors caught. Values: Constants defined for the interpreter . For example: E_ALL.

    But there is a nuance. In the case of an error generated by us, the handler receives an Exception object . But if the error was generated by the interpreter, it receives a set of arguments. Naturally, it is appropriate to make the processor receive homogeneous data regardless of the source. An ErrorException object was developed specifically for this . So, we add the redirect_err function to the error handling class, which will handle the error, namely, to raise an exception that will already be caught by the handler assigned by the user. In addition, we add a flag that will indicate that the errors generated by the interpreter are intercepted and that it is necessary to restore, if necessary, the previous handler in the class’s destructor using the functionrestore_error_handler .
    And now our class takes on a finished and ready-to-use look: And here is a really existing code example, using it:
    Copy Source | Copy HTML
    1. class error_class{
    2.  
    3.     private $err=false;
    4.     private $exception_handler=false;
    5.     private $err_handler=false;
    6.  
    7.     public function __construct($callback=null,$err_redirect=true,$level=E_ALL){
    8.         if($callback!=null){
    9.             $this->exception_handler=true;
    10.             set_exception_handler($callback);
    11.             if($err_redirect){
    12.                 $this->err_handler = true;
    13.                 set_error_handler(array("error_class","redirect_err"),$level);
    14.             }
    15.         }
    16.     }
    17.  
    18.     public function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
    19.         throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    20.         return true;
    21.     }
    22.  
    23.     public function set_err($msg=''){
    24.         $this->err=$msg;
    25.     }
    26.  
    27.     public function clear_err(){
    28.         $this->err=false;
    29.     }
    30.  
    31.     public function is_err(){
    32.         return $this->err;
    33.     }
    34.  
    35.     public function fire_err($msg='',$code=0){
    36.         $this->err=$msg;
    37.         throw new Exception($msg,$code);
    38.     }
    39.  
    40.     public function __destruct(){
    41.         if($this->exception_handler){
    42.             restore_exception_handler();
    43.         }
    44.         if($this->err_redirect){
    45.             restore_error_handler();
    46.         }
    47.     }
    48. }


    Copy Source | Copy HTML
    1. <?php
    2.     session_start();
    3.     require_once("error_class.php");
    4.     require_once("mail_class.php");
    5.  
    6.     $err = new error_class("err_overwork");
    7.  
    8.     if(
    9.        !isset($_POST['email'])||
    10.        !isset($_POST['message'])||
    11.        !isset($_POST['captcha'])
    12.        ){
    13.         $err->fire_err("Не все переменные заданы",-1);
    14.     }
    15.     if($_SESSION['captcha']!=$_POST['captcha']){
    16.         $err->fire_err("Неверно введен код обратного теста Тьюринга",-1);
    17.     }else{
    18.         unset($_SESSION['captcha']);
    19.     }
    20.  
    21.     $mailer = new mail_class();
    22.  
    23.     if(!$mailer->text(addslashes(htmlspecialchars($_POST['message'])))){
    24.         $err->fire_err($mailer->err->is_err(),-1);
    25.     }
    26.     if(!$mailer->to("Студия «Ночной народ»","raven@nightfolk.net")){
    27.         $err->fire_err($mailer->err->is_err(),-1);
    28.     }
    29.     if(!$mailer->from(addslashes($_POST['email']))){
    30.         $err->fire_err($mailer->err->is_err(),-1);
    31.     }
    32.     if(!$mailer->subj('Сообщение от посетителя')){
    33.         $err->fire_err($mailer->err->is_err(),-1);
    34.     }
    35.     if(!$mailer->reply_to(addslashes($_POST['email']))){
    36.         $err->fire_err($mailer->err->is_err(),-1);
    37.     }
    38.     if(!$mailer->send()){
    39.         $err->fire_err($mailer->err->is_err(),-1);
    40.     }
    41.  
    42.     header("location:../confirm.html");
    43.     die();
    44.  
    45.     function err_overwork($error){
    46.         $_SESSION['error_message']=$error->getMessage();
    47.         header("location:../error.php");
    48.         die();
    49.     }
    50. ?>

    Enlightenment (IMPORTANT)


    After stormy and productive communication with a mass of very angry people,
    Read the article " Handling Exceptional Situations in OOP "
    Three times re-reading the documentation on the exception mechanism in php
    And, finally, discussing the problem on the phpclub.ru forum .
    I realized that exceptions are a sufficient mechanism for implementing error handling and it makes no sense to drag something else.
    However, in order not to duplicate the destination code of the default exception handler and the error message handler generated by the interpreter at each entry point, it makes sense that the class exists in the following form: Now you can assign the handler in one line: Well, the script will look like this:
    Copy Source | Copy HTML
    1. class error_class{
    2.  
    3.     private $err_handler=false;
    4.  
    5.     public function __construct($callback=null,$err_redirect=true,$level=E_ALL){
    6.         if($callback==null){
    7.             throw new Exception("Не определена callback функция.",E_ERROR);
    8.         }
    9.         set_exception_handler($callback);
    10.         if($err_redirect){
    11.             $this->err_handler = true;
    12.             set_error_handler(array("error_class","redirect_err"),$level);
    13.         }
    14.     }
    15.  
    16.     public function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
    17.         throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    18.         return true;
    19.     }
    20.  
    21.  
    22.     public function __destruct(){
    23.         restore_exception_handler();
    24.         if($this->err_handler){
    25.             restore_error_handler();
    26.         }
    27.     }
    28. }
    Copy Source | Copy HTML
    1. new error_class('err_overwork');
    2. function err_overwork($error){
    3. }




    Copy Source | Copy HTML
    1. <?php
    2.     session_start();
    3.     header("content-type: text/html; charset=utf-8");
    4.  
    5.     require_once("mail_class.php");
    6.     require_once("error_class.php");
    7.  
    8.     $err = new error_class("err_overwork");
    9.  
    10.     if(
    11.        !isset($_POST['email'])||
    12.        !isset($_POST['message'])||
    13.        !isset($_POST['captcha'])
    14.        ){
    15.         throw new Exception("Не все переменные заданы",E_ERROR);
    16.     }
    17.  
    18.     if($_SESSION['captcha']!=$_POST['captcha']){
    19.         throw new Exception("Неверный код подтверждения",E_ERROR);
    20.     }else{
    21.         unset($_SESSION['captcha']);
    22.     }
    23.  
    24.     $mailer = new mail_class();
    25.     $mailer->text(addslashes(htmlspecialchars($_POST['message'])));
    26.     $mailer->to("Студия «Ночной народ»","raven@nightfolk.net");
    27.     $mailer->from(addslashes($_POST['email']));
    28.     $mailer->subj('Сообщение от посетителя');
    29.     $mailer->reply_to(addslashes($_POST['email']));
    30.     $mailer->send();
    31.  
    32.     header("location:../confirm.html");
    33.     die();
    34.  
    35.     function err_overwork($error){
    36.         $_SESSION['error_message']=$error->getMessage();
    37.         header("location:../error.php");
    38.         die();
    39.     }
    40. ?>


    Послесловие (В принципе — ничего особо важного)



    I hope that this article will be interesting to you or even bring some benefit.
    I will be very happy about this.
    I would be very grateful if you could write your thoughts about the class and the process of its development.
    If you see something that can be improved, write.
    If you see any flaws, vulnerabilities or errors - write .
    And, if you write, then in this world, perhaps, at least for a long time it will become one happy me more.
    And also, write whether you liked the article or if it turned out to be lengthy and tedious. I want to know whether I should write more ... Or it will be just the agony of a graphomaniac.
    PS I can also describe a caching class or template engine. But you never know what. The main thing is that it would be interesting to you.

    Acknowledgments (Read Required)



    Thanks to sunnybear for the invitation to Habrahabr, for the wonderful YASS framework and for being glued. I still liked him at RITA.
    Thanks to habraname for showing interest in YASS, writing a funny wrapper and just pleasant to talk to.
    Thanks to zerkms for taking the time to clarify and pushing for additional experiments.

    Also popular now: