Question

I do know there are already many questions on Stackoverflow related to custom error handlers. But, after reading many of them, as well as the PHP manual, I am still unable to solve my problem. Thus I am posting this question.

My script is currently structured this way:

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();

My custom error handler is already working fine. It catches and processes all PHP errors that occur in the code executed after the error handler is set.

My problem:

If an error occurs within a method of myObject class, I would like to call a non-static method:

$obj->SomethingBadHappened();

$obj is not in the scope of myErrorHandler. How can I access $obj inside the error handler to call a member function of $obj?
I currently have 300KB of PHP code, and I can't change the signature of all the functions to add $obj as a parameter (there are too many functions!).

I read that it is possible to define the custom error handler as a method of an object. But, if I do that, it won't be able to catch errors that happen before creating the instance of myObject ($obj).

I also read about exceptions, but it doesn't seem to help solving my problem. I am not willing to use global variables. Here are 2 questions that explain why global variables should be avoided:

Was it helpful?

Solution 2

I finally decided for this design:

  • my current error handler remains unchanged (function myErrorHandler). It holds the code to be executed when an error happens (anywhere in the code)
  • in the class myObject I added another error handler (function myObject_error_handler)
  • the constructor of myObject registers the new error handler
  • the function myObject_error_handler may now call the function SomethingBadHappened, then calls myErrorHandler to process the error

The major benefit of doing so it that it required very few changes to the existing code:

  • one new method in my class
  • register the new error handler in the constructor
  • a call to the older error handler from the new error handler

The new code is:

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();

OTHER TIPS

An interesting Q which no one seems to want to jump on so I'll give you my 2cents worth and everything is qualified by an IMO ...

Error handling is about tidily handing errors end rounding off reporting to the end-user in some form of user-friendly way, plus possibly laying down internal forensics for Application Support. In general it should attempt to expose or interpret application context -- just too difficult and by its nature context-specific so you'll end up opening a can of worms.

If you want to handle errors hierarchically within context then you've now really moved into the world of Exceptions and Exception Handling and this is an entirely different runtime model. Here you could catch -- in principle -- handle application exceptions and retain object context, but this could prove unwieldy to implement and I can't think of any standard patterns that would help here.

If $obj were a singleton (and you were willing to contemplate using a singleton template) then this would allow you to do what you want.

On the other hand if all that you want is some limited context to be available e.g. "Processing customer record XXXX", then why not encapsulate your error handler in a class using a static method for the handler and another registration method to register the putative context, or even to register callbacks which would allow you to register object-specific methods to provide this context? Of course you'd need to be very careful to use the Classes/Object Functions to validate that any registered callbacks were still in valid scope, to avoid potential nested errors, but this sort of framework could be made workable.

Footnote following Jocelyn's Feedback

I understand the arguments pro and con Singletons, and that's why I don't use them myself and also deprecated them in my initial answer. However, they do offer some advantages when when used with care, which is why some best-of-breed applications such as the MediaWiki engine still use them. The constraint of PHP scoping still exists. For you to call $someObject->method() from another class or function then it must be within the scope of the calling function. There is simply no way around this. So you have some options:

  • If there is only one object of someClass active at any time then you can place this in global scope by a number of methods, the simplest being to copy the object to some global variable, or to use a static method to return this (which all that a classical singleton someClass::get() method does anyway.

  • On the other hand if your have multiple objects that could be putatively in scope, then yes you could pass objects around as parameters, but there is absolutely no need to do this. You could use push method of registering these with the error handler. One example implementation would be to create and error handler class with 4 static methods and a static private variable:

    • myErrorHandler(). The error handler as you describe, but now a static class method.

    • initErrorHandler(). You call this to register the above static method.

    • registerClassCallback($callback), where $callback is a standard array($obj,'method') parameter.

    • unregisterClassCallback($callback), where $callback is the same array($obj,'method') parameter.

    • $registeredCallbacks. A private array of registered callbacks that registerClassCallback() adds to and unregisterClassCallback() removes from. myErrorHandler() can simply do a foreach over this using class_exists() and method_exists() to verify each registered callback is still in scope before calling it.

This will do what you seek. Remember that in php, an object assignment to a variable is effectively a handle to the object. You can copy object (handles) to any variable context. This does not copy or deep copy the underlying object. It is effectively a reference (though subtly semantically different from an true PHP object reference -- but that's a different Q&A).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top