自定义错误处理程序,用于处理对象代码内部和外部的错误


Custom error handler to handle errors inside and outside Object code

我知道关于Stackoverflow已经有很多问题与自定义错误处理程序有关。但是,在阅读了其中的许多内容以及PHP手册之后,我仍然无法解决我的问题。因此,我发布了这个问题。

我的脚本目前的结构是这样的:

require 'file.php';
require 'anotherFile.php';
// several more "require" here. These files contain many functions
function myErrorHandler($errno, $errstr, $errfile, $errline, $errcontext){
    // some code to handle errors here
    
}
class myObject {
    function __construct() {
        // set the values here
    }
    function computeSomething() {
        ...
        doFunction2();
        ...
    }
    function SomethingBadHappened()
    {
    }
}
function doFunction1() {
    // for some reason, an error happens here
    // it is properly handled by the current error handler
}
function doFunction2() {
    // for some reason, an error happens here
    // since it got called by $obj, I want the error handler to run $obj->SomethingBadHappened();
    // but $obj is not known in myErrorHandler function!
}
set_error_handler('myErrorHandler');
// some procedural code here
doFunction1();
doAnotherThing();
// then I use objects
$obj = new myObject();
$obj->run();
// then I may use procedural code again
doSomethingElse();

我的自定义错误处理程序已运行良好。它捕获并处理在设置错误处理程序后执行的代码中发生的所有PHP错误。

我的问题:

如果myObject类的方法中发生错误,我想调用一个非静态方法:

$obj->SomethingBadHappened();

$obj不在myErrorHandler的范围内。如何访问错误处理程序中的$obj以调用$obj的成员函数
我目前有300KB的PHP代码,无法更改所有函数的签名以添加$obj作为参数(函数太多了!)。

我读到可以将自定义错误处理程序定义为对象的方法。但是,如果我这样做,它将无法捕获在创建myObject($obj)实例之前发生的错误。

我也读过关于例外的文章,但这似乎无助于解决我的问题。我不愿意使用全局变量。这里有两个问题可以解释为什么应该避免全局变量:

  • PHP全局函数
  • 谁需要单身

一个有趣的Q,似乎没有人想跳上去,所以我会给你我的2元钱,一切都通过IMO。。。

错误处理是指以某种用户友好的方式将错误最终舍入报告整齐地交给最终用户,并可能为应用程序支持制定内部取证。一般来说,它应该尝试公开或解释应用程序上下文——这太难了,而且本质上是特定于上下文的,所以你最终会打开一罐蠕虫。

如果你想在上下文中分层处理错误,那么你现在已经真正进入了异常和异常处理的世界,这是一个完全不同的运行时模型。在这里,原则上,您可以处理应用程序异常并保留对象上下文,但这可能会被证明难以实现,而且我想不出任何标准模式可以在这里提供帮助。

如果$obj是一个单例(并且您愿意考虑使用单例模板),那么这将允许您做您想做的事情。

另一方面,如果你想要的只是一些有限的上下文可用;处理客户记录XXXX";,那么,为什么不使用处理程序的静态方法和另一个注册方法将错误处理程序封装在一个类中,以注册假定的上下文,甚至注册回调,从而允许您注册特定于对象的方法来提供此上下文呢?当然,您需要非常小心地使用类/对象函数来验证任何注册的回调是否仍在有效范围内,以避免潜在的嵌套错误,但这种框架是可行的。

Jocelyn反馈后的脚注

我理解支持和反对辛格尔顿的论点,这就是为什么我自己不使用它们,并在最初的回答中反对它们。然而,当谨慎使用时,它们确实提供了一些优势,这就是为什么一些最好的应用程序(如MediaWiki引擎)仍然使用它们的原因。PHP作用域的约束仍然存在。如果要从另一个类或函数调用$someObject->method(),则它必须在调用函数的范围内。这根本没有办法。所以你有一些选择:

  • 如果在任何时候只有一个someClass对象处于活动状态,那么您可以通过多种方法将其放置在全局范围内,最简单的方法是将对象复制到某个全局变量,或者使用静态方法返回它(这是经典的单例someClass::get()方法所做的一切)。

  • 另一方面,如果您有多个可能在作用域中的对象,那么是的,您可以将对象作为参数传递,但绝对没有必要这样做。您可以使用push方法将这些注册到错误处理程序中。一个示例实现是创建一个带有4个静态方法和一个静态私有变量的错误处理程序类:

    • myErrorHandler()。正如您所描述的错误处理程序,但现在是一个静态类方法。

    • initErrorHandler()。您调用它来注册上面的静态方法。

    • registerClassCallback($callback),其中$callback是标准的array($obj,'method')参数。

    • unregisterClassCallback($callback),其中$callback是相同的array($obj,'method')参数。

    • $registeredCallbacksregisterClassCallback()添加到的注册回调的私有数组,unregisterClassCallback()从中删除。myErrorHandler()可以简单地使用class_exists()method_exists()对此进行foreach,以在调用之前验证每个注册的回调是否仍在作用域中

这将满足你的需求。请记住,在php中,变量的对象赋值实际上是对象的句柄。您可以将对象(句柄)复制到任何变量上下文中。这不会复制或深度复制基础对象。它实际上是一个引用(尽管在语义上与真正的PHP对象引用略有不同——但这是一个不同的问答)。

我最终决定采用这个设计:

  • 我当前的错误处理程序保持不变(函数myErrorHandler)。它保存发生错误时要执行的代码(代码中的任何位置)
  • 在类myObject中,我添加了另一个错误处理程序(函数myObject_error_handler
  • myObject的构造函数注册新的错误处理程序
  • 函数myObject_error_handler现在可以调用函数SomethingBadHappened,然后调用myErrorHandler来处理错误

这样做的主要好处是,它只需要对现有代码进行很少的更改:

  • 我班上的一个新方法
  • 在构造函数中注册新的错误处理程序
  • 从新错误处理程序调用旧错误处理程序

新代码是:

require 'file.php';
require 'anotherFile.php';
// several more "require" here. These files contain many functions
function myErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
    // some code to handle errors here
}
class myObject {
    function __construct() {
        // set the values here
        // register another error handler
        set_error_handler(array($this, 'myObject_error_handler'));
    }
    function myObject_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
        // call any number of methods of the current class
        $this->SomethingBadHappened();
        // then call the other error handler
        myErrorHandler($errno, $errstr, $errfile, $errline, $errcontext);
    }
    function computeSomething() {
        ...
        doFunction2();
        ...
    }
    function SomethingBadHappened() {
        // does something when an error happens
    }
}
function doFunction1() {
    // for some reason, an error happens here
    // it is properly handled by the current error handler
}
function doFunction2() {
    // for some reason, an error happens here
    // now the function myObject_error_handler will be called automatically
}
set_error_handler('myErrorHandler');
// some procedural code here
doFunction1();
doAnotherThing();
// then I use objects
$obj = new myObject();
$obj->run();
// then I may use procedural code again
set_error_handler('myErrorHandler');
doSomethingElse();