Post

Cleaning/restoring using helper objects

Just wanted to show all of you who use OO PHP a technique that is quite familiar to old-time C++ programmers, but generally not so well known in the PHP world (think auto_ptr). This technique allows for much cleaner code in scenarios where you have to allocate a resource (or change global state) and have to cleanup before exiting the (function/method) scope.

I first applied this when I had a method in a class that needed to execute a function that might create an error condition that I wanted to intercept because I needed to do some stuff before letting the exception escape. The classic PHP approach would be something like this:

1
2
3
4
5
6
7
8
9
10
11
12
function dosomething()
{
     set_error_handler(`myerrorhandler`);
     try {
         call_function_that_may_throw();
     }
     catch (Exception $e) {
         // Do some stuff
         throw $e;
     }
     restore_error_handler();
}

The problem is that as written the error handler will not be restored if we (re) throw the exception. So we fix that:

1
2
3
4
5
6
7
8
9
10
11
12
13
function dosomething()
{
     set_error_handler(`myerrorhandler`);
     try {
         call_function_that_may_throw();
     }
     catch (Exception $e) {
         // Do some stuff
         restore_error_handler();
         throw $e;
     }
     restore_error_handler();
}

But now we are starting to repeat the code. So here is what it looks like with the alternate, more object oriented technique:

1
2
3
4
5
6
7
8
9
10
11
function dosomething()
{
     $handler = new ErrorHandler(`myerrorhandler`);
     try {
         call_function_that_may_throw();
     }
     catch (Exception $e) {
         // Do some stuff
         throw $e;
     }
}

So, where did the restore_error_handler go? Now it won’t be restored at all! Not so. Let’s have a look at the ErrorHandler class:

1
2
3
4
5
6
7
8
9
10
class ErrorHandler {
     public function __construct($handler)
     {
         set_error_handler($handler);
     }
     public function __destruct()
     {
         restore_error_handler();
     }
}

Now, if we look at the last version of the dosomething function above, you will notice that whether the function ends by throwing an exception, or because it returns normally, the $handler variable will go out of scope and will be destructed. The class’s destructor function restores the previous state. This approach allows you to forget about when/where to let go/restore resources.

The technique can be generalized to any other kind of object that needs to clean up after itself. If you already have existing objects, make sure you have an appropriate __destruct method. The above illustrates how you can introduce some new objects to encapsulate create/restore behavior for non-object type functions.

WARNING PHP makes no guarantees about the order in which variables that go out of scope get destructed. This is generally not an issue, but if you were to, for example, create two handlers like the above in the same scope, they may get broken down in the opposite order from what you expect. Since restore_error_handler carries no context, it doesn’t matter in this case: as long as it gets done twice. It may not be true of other scenarios. Take this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ErrorReportingChange {
    private $_old_mask;
    public function __construct($reporting_mask)
    {
        $this->_old_mask = error_reporting($reporting_mask);
    }
    public function __destruct()
    {
        error_reporting($this->_old_mask);
    }
}
 
function test()
{
    $er1 = new ErrorReportingChange(E_ALL);
    $er2 = new ErrorReportingChange(E_WARN);
    // Do something
}

Lets assume the error_reporting level is 0 before we execute test(). Creating $er1 will set it to E_ALL (and remember 0), and then creating $er2 will set it to E_WARN (and remember E_ALL). So far so good. We do some work and exit. If destruction happens in the order $er2 and then $er1 things are peachy and we end up with 0 as the error_reporting level. If destruction happens the other way around though, we end up with an error_reporting level of E_ALL!

One simple way of getting around this problem is to have each object that implements this behavior hold a reference to other objects that should be destructed later:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ErrorReportingChange {
    private $_old_mask;
    private $_references;
    public function __construct($reporting_mask, $references = null)
    {
        $this->_old_mask = error_reporting($reporting_mask);
        $this->_references = $references;
    }
    public function __destruct()
    {
        error_reporting($this->_old_mask);
        if (!empty($references)) {
            unset($this->_references);
        }
    }
}
 
function test()
{
    $er1 = new ErrorReportingChange(E_ALL);
    $er2 = new ErrorReportingChange(E_WARN, $er1);
    // Do something
}

Now $er2 is holding a reference to $er1. If PHP wants to destruct $er1, it finds it has a reference count of 2, so it is not considered (yet), but its count is reduced to 1. $er2 has a reference count of 1, so its __destruct function is called. It does what it needs to do, and then unsets the reference(s) it holds. This causes the reference count of $er1 to go from 1 to 0, and `$er1’ is finally destroyed.

This post is licensed under CC BY 4.0 by the author.