error_get_last() and custom error handler

2019-02-13 09:55发布

问题:

odbc_errormsg does not report error messages from odbc_execute the way it's supposed to. It merely throws a warning. So I've been forced to write a hack to parse the error message via error_get_last.

I'm using set_error_handler and error_get_last returns NULL unless I either:

  1. disable my error handler,

  2. or make it return FALSE.

I'd suppose this is due to PHP's builtin error handler taking care of storing the error details somewhere so they can be retrieved later.

Is there a way to emulate such behaviour in my custom error handler so error_get_last() can be used normally?

Please note I already know several ways to retrieve error info at any time. My question is how to make error_get_last usable.


Update: I think I'd better post some code.

PHP has error_get_last(), which allows to do this:

@fopen('xxx');
var_dump( error_get_last() );

... and get this:

array(4) {
  ["type"]=>
  int(2)
  ["message"]=>
  string(46) "fopen() expects at least 2 parameters, 1 given"
  ["file"]=>
  string(69) "C:\Documents and Settings\ALVARO.GONZALEZ\Mis documentos\tmp\test.php"
  ["line"]=>
  int(3)
}

This breaks if you replace the builtin error handler:

function custom_error_handler($errno, $errstr, $errfile, $errline){
    $ignore = ($errno & error_reporting()) == 0;
    if(!$ignore){
        echo "[Error happened: $errstr]\n";
    }
    return TRUE;
}
set_error_handler('custom_error_handler');

@fopen('xxx');
var_dump( error_get_last() ); // NULL

If you keep both error handlers...

function custom_error_handler($errno, $errstr, $errfile, $errline){
    $ignore = ($errno & error_reporting()) == 0;
    if(!$ignore){
        echo "[Error happened: $errstr]\n";
    }
    return FALSE;
}
set_error_handler('custom_error_handler');

error_reporting(E_ALL);
echo $foo;

... you get side effects:

[Error happened: Undefined variable: foo]

Notice: Undefined variable: foo in C:\Documents and Settings\ALVARO.GONZALEZ\Mis documentos\tmp\test.php on line 15

Call Stack:
    0.0004     329720   1. {main}() C:\Documents and Settings\ALVARO.GONZALEZ\Mis documentos\tmp\test.php:0

... instead of just:

[Error happened: Undefined variable: foo]

I want to my custom error handler to interface properly with error_get_last. I want error_get_last to work fine.

回答1:

Right, this is a bizarre solution, but I think it will suit your purposes.

After a bit of playing around, I have discovered that this:

function my_error_handler ($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) {

  // Handle the error here

  @trigger_error($errstr);
  return TRUE;

}

// Just to make sure PHP is not outputting anything
error_reporting(-1);
ini_set('display_errors',1);

set_error_handler('my_error_handler');

// An E_USR error...
trigger_error('Some error');
var_dump(error_get_last());

// ...and a native one
$key = count();
var_dump(error_get_last());

Results in this:

array(4) {
  ["type"]=>
  int(1024)
  ["message"]=>
  string(10) "Some error"
  ["file"]=>
  string(69) "C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test.php"
  ["line"]=>
  int(7)
}
array(4) {
  ["type"]=>
  int(1024)
  ["message"]=>
  string(45) "count() expects at least 1 parameter, 0 given"
  ["file"]=>
  string(69) "C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\test.php"
  ["line"]=>
  int(7)
}

Calling @trigger_error() from within your error handler, and not returning FALSE, causes error_get_last() to return something other than NULL, but because the error is suppressed with @, PHP doesn't output anything. It seems that in the interests of avoiding an infinite recursion, calling trigger_error() from within the registered error handler function does not call the error handler - which works to our advantage here.

Obviously, the error code has been modified, but you could convert it to the relevant E_USR_* code if you need - but I suspect what you really want is the string value, which this method will allow you get. You have, unfortunately, also lost the line number and file information - although you could possibly get this back by doing something involving a stack trace inside the error handler, or at the very least including it in the string from the arguments passed.

This is a horrible, horrible, horrible hack - but since there is no officially sanctioned way to do this, a hack is essentially what you are asking for.



回答2:

You could change your custom error handler to return false, just when the error is ignored (@-operator used).

function custom_error_handler($errno, $errstr, $errfile, $errline){
    $ignore = ($errno & error_reporting()) == 0;
    if ($ignore) {
        return FALSE;
    }
    echo "[Error happened: $errstr]\n";
    return TRUE;
}