Update: this article is mentioned in a few places as a practical example of using closures. Some languages pass variables into a closure automatically. In PHP it needs to be done explicitly using the use keyword. See line #7 of the code example.
I was inspired by a question that I was asked on Twitter to write a quick code snippet.
As you may know, set_error_handler
can be used to set a custom error handler in PHP. It will catch any errors that happen in the script (with a few notable exceptions). If the function returns false then error handling resumes as normal; otherwise it is assumed that the custom handler took care of things. The problem is that you can only have one error handler active at one time. The purpose of this code is to provide a error handeling stack for PHP.
Using this code you can have more than one error handler while taking advantage of the set_error_handler
function.
Because this example uses closures, it will only work in PHP 5.3 or newer.
<?php
function push_error_handler( $error_handler, $error_types = 32767 )
{
$old_callback = null;
$callback = function( $errno, $errstr, $errfile, $errline, $errcontext )
use ( &$old_callback, $error_handler )
{
$result = call_user_func($error_handler, $errno , $errstr, $errfile, $errline, $errcontext);
if ( $result === false && $old_callback != null )
return call_user_func( $old_callback, $errno , $errstr, $errfile, $errline, $errcontext );
return $result;
};
$old_callback = set_error_handler($callback, $error_types);
}
?>
Let’s see it in action:
<?php
function test1( $a, $b, $c, $d, $e ) { echo "Test 1 -- "; return false; }
function test2( $a, $b, $c, $d, $e ) { echo "Test 2 -- "; return false; }
function test3( $a, $b, $c, $d, $e ) { echo "Test 3 -- "; return false; }
function test4( $a, $b, $c, $d, $e ) { echo "Test 4 -- "; return false; }
push_error_handler( 'test1' );
push_error_handler( 'test2' );
push_error_handler( 'test3' );
push_error_handler( 'test4' );
trigger_error('testing');
?>
The output is:
Test 4 -- Test 3 -- Test 2 -- Test 1 --
Notice: testing in /Users/andrew/recursive_error_handler.php on line 32
It is worth a closer look. First we initialize a variable that will be needed by the closure later on. Then we define the closure (which will be our actual error handling function) and register it as the error handler. The function seterrorhandler returns the old handler which we then assign to the variable that we created earlier. We also return the old handler to keep compatibility with the normal seterrorhandler.
The closure itself calls the new error handler. If the handler returned false (resume error handeling) then we call the previous error handler (if there was one). We also make sure to pass the error code and the old callback function into the closure. It is important that the old callback is passed by reference (note the &
).
If you want to learn some more, here are some handy links:
- PHP Manual for seterrorhandler and anonymous functions and closures.
- My book, Expert PHP and MySQL, also talks about closures in depth starting on page 78.
Note: this method only works if almost all the error handlers in the application use this or a similar method. Otherwise the chain will be broken. I say “almost” because the first registered error handler doesn’t have to worry about calling the previous handler since there isn’t any at that point.