Good question. I think this is a general problem with E_RECOVERABLE_ERROR in PHP.
What you have in your question is the exception handler, not the error handler. The error handler is causing the actual problem you discuss here with catchable fatal errors (E_RECOVERABLE_ERROR).
PHP 7 and HHVM have this already solved.
It's worse with Magento because the error handler does not deal with this since PHP 5.2 error class.
A more useful kind of error handling would be dealing with this error class and to turn these errors into ErrorExceptions. Example (not by me, from here):
set_error_handler(function($errno, $errstr, $errfile, $errline) { if ($errno === E_RECOVERABLE_ERROR) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } return false; });
So in the light of Magento, the default error handler is the global function mageCoreErrorHandler in app/code/core/Mage/Core/functions.php. It get's registered via Mage::app() by the init() method of the Mage_Core_Model_App (app/code/core/Mage/Core/Model/App.php) (via protected _initEnvironment() method).
An observer on controller_front_init_before which registers your own PHP error handler on top should suffice then (error handlers in PHP are stackable):
$previous = set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$previous) { if ($errno === E_RECOVERABLE_ERROR) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } if ($previous) { return call_user_func($previous, $errno, $errstr, $errfile, $errline); } return false; });
catchable fatal errors then are turned into exceptions and you can deal with them in your own extension code or they are uncaught and will be seen in the exception log (instead of having your shop run gaga on wrong types like the current behaviour is, dead programs don't lie). In PHP 7 the exception to look for isn't ErrorException then but TypeException (which is a BaseException) for the now catchable fatal errors.
All other errors are passed on to the error handler of Magento.
Note: I have not tried this, it's a write-up but I know the problem you're asking about and the error handling analysis has been done against 1.5.1.0 and verified against 1.9.1.0 through code analysis. The error handler stacking should work. I append a little extended example code that demonstrates most parts working.
I have not yet packaged this as a magento extension but it should be straight forward with modman. I'll put it on github then.
Appendix: Error Handler Demo
The following code-example (online demo) demonstrates the stacking of error handlers and exception throwing on catchable fatal error:
<?php /** * error handler demonstration * * stackable error handle with previous call and catchable error exceptions * * @author hakre <http://hakre.wordpress.com> * @link https://magento.stackexchange.com/a/64972/4115 */ set_error_handler(function() { $args = func_get_args(); var_dump("me is the previous error handler", $args); }); $previous = set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$previous) { if ($errno === E_RECOVERABLE_ERROR) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } if ($previous) { return call_user_func($previous, $errno, $errstr, $errfile, $errline); } return false; }); $test = function(callable $test) {}; $a = $undefined; // provoke little warning $test(new stdClass); // provoke catchable fatal error
Program Output
string(32) "me is the previous error handler" array(4) { [0]=> int(8) [1]=> string(29) "Undefined variable: undefined" [2]=> string(45) "/tmp/execpad-0eca072b619d/source-0eca072b619d" [3]=> int(28) } Fatal error: Uncaught exception 'ErrorException' with message 'Argument 1 passed to {closure}() must be callable, object given, called in /tmp/execpad-0eca072b619d/source-0eca072b619d on line 30 and defined' in /tmp/execpad-0eca072b619d/source-0eca072b619d:26 Stack trace: #0 /tmp/execpad-0eca072b619d/source-0eca072b619d(26): {closure}(4096, 'Argument 1 pass...', '/tmp/execpad-0e...', 26, Array) #1 /tmp/execpad-0eca072b619d/source-0eca072b619d(30): {closure}(Object(stdClass)) #2 {main} thrown in /tmp/execpad-0eca072b619d/source-0eca072b619d on line 26