I’ve added a new class to my framework, Ando_ErrorFactory.
Here I use it to make PHP trigger an E_CORE_ERROR:
{[ .example | 1.hilite(=php=) ]}
-- s t a r t --
PHP Fatal error: Class Oops must implement interface Traversable as part of either Iterator or IteratorAggregate in Unknown on line 0
PHP Stack trace:
PHP 1. {main}() /.../test1.php:0
PHP 2. Ando_ErrorFactory::E_CORE_ERROR() /.../test1.php:14
PHP 3. Ando_ErrorFactory->E_CORE_ERROR_by_declaring_a_class_directly_implementing_Traversable() /.../vendor/Ando/ErrorFactory.php:227
PHP 4. eval() /.../vendor/Ando/ErrorFactory.php:444
shutdown_error: E_CORE_ERROR (Fatal error)
error = Array
(
[type] => 16
[message] => Class Oops must implement interface Traversable as part of either Iterator or IteratorAggregate
[file] => Unknown
[line] => 0
)
Notice that the lines starting with “PHP” belong to STDERR (redirected to STDOUT, controlled by the ´error_reporting´ call), while the rest belong to the real STDOUT.
The take away is that we forcefully triggered an E_CORE_ERROR (fact confirmed by the STDERR portion) and our shutdown handler was able to log it (fact confirmed by the STDOUT portion). Given that such an error is a non catchable shutdown error, there is no other way to get a look at it than from inside a shutdown handler.
Here is what happens when instead we forcefully trigger an E_RECOVERABLE_ERROR:
-- s t a r t --
non_shutdown_error: E_RECOVERABLE_ERROR (Catchable fatal error)
error = Array
(
[type] => 4096
[message] => Object of class stdClass could not be converted to string
[file] => /.../vendor/Ando/ErrorFactory.php(510) : eval()'d code
[line] => 1
[context] => Array
(
=> echo new stdClass();
)
)
#0 Ando_ErrorFactory::non_shutdown_error(4096, Object of class stdClass could not be converted to string, /.../vendor/Ando/ErrorFactory.php(510) : eval()'d code, 1, Array ( => echo new stdClass();)) called at [/.../vendor/Ando/ErrorFactory.php(510) : eval()'d code:1]
#1 eval() called at [/.../vendor/Ando/ErrorFactory.php:510]
#2 Ando_ErrorFactory->E_RECOVERABLE_ERROR_by_converting_to_string_an_object_of_a_class_without__to_string() called at [/.../vendor/Ando/ErrorFactory.php:294]
#3 Ando_ErrorFactory::E_RECOVERABLE_ERROR() called at [/.../tests/tmp2.php:22]
-- e n d --
shutdown_error: (none) (Unknown error)
The presence of the ´-- e n d --´ line in the output confirms, as expected, that E_RECOVERABLE_ERROR is not a shutdown error. Thanks to that, we had a chance to get to the backtrace from our non shutdown error handler, while the STDERR didn't get written to because we signaled to PHP we successfully handled the error by returning ´true´. In fact, here is what would happen if we returned ´false´:
-- s t a r t --
non_shutdown_error: E_RECOVERABLE_ERROR (Catchable fatal error)
error = Array
(
[type] => 4096
[message] => Object of class stdClass could not be converted to string
[file] => /.../vendor/Ando/ErrorFactory.php(510) : eval()'d code
[line] => 1
[context] => Array
(
=> echo new stdClass();
)
)
#0 Ando_ErrorFactory::non_shutdown_error(4096, Object of class stdClass could not be converted to string, /.../vendor/Ando/ErrorFactory.php(510) : eval()'d code, 1, Array ( => echo new stdClass();)) called at [/.../vendor/Ando/ErrorFactory.php(510) : eval()'d code:1]
#1 eval() called at [/.../vendor/Ando/ErrorFactory.php:510]
#2 Ando_ErrorFactory->E_RECOVERABLE_ERROR_by_converting_to_string_an_object_of_a_class_without__to_string() called at [/.../vendor/Ando/ErrorFactory.php:294]
#3 Ando_ErrorFactory::E_RECOVERABLE_ERROR() called at [/.../tests/tmp2.php:22]
shutdown_error: E_RECOVERABLE_ERROR (Catchable fatal error)
error = Array
(
[type] => 4096
[message] => Object of class stdClass could not be converted to string
[file] => /.../vendor/Ando/ErrorFactory.php(510) : eval()'d code
[line] => 1
)
PHP Catchable fatal error: Object of class stdClass could not be converted to string in /.../vendor/Ando/ErrorFactory.php(510) : eval()'d code on line 1
PHP Stack trace:
PHP 1. {main}() /.../tests/tmp2.php:0
PHP 2. Ando_ErrorFactory::E_RECOVERABLE_ERROR() /.../tests/tmp2.php:22
PHP 3. Ando_ErrorFactory->E_RECOVERABLE_ERROR_by_converting_to_string_an_object_of_a_class_without__to_string() /.../vendor/Ando/ErrorFactory.php:294
PHP 4. eval() /.../vendor/Ando/ErrorFactory.php:510
As you see, by returning ´false´, we told PHP that we didn't successfully handle the error and to continue its default course, which in this case, being E_RECOVERABLE_ERROR a shutdown error by default, consisted in shutting down the script. As a confirmation, notice the absence of the ´-- e n d --´ line in the output.
A fun fact I discovered while programming this class is that a fatal error is not necessarily a non-catchable shutdown error. In fact, both E_RECOVERABLE_ERROR and E_USER_ERROR are catchable shutdown errors. Likewise, E_PARSE, E_CORE_WARNING and E_COMPILE_WARNING are non catchable non shutdown errors. For each of these variations ErrorFactory has a corresponding method to return that set of errors.
{[ .example2 | 1.hilite(=php=) ]}
all_errors: Array
(
[0] => E_ERROR
[1] => E_WARNING
[2] => E_PARSE
[3] => E_NOTICE
[4] => E_CORE_ERROR
[5] => E_CORE_WARNING
[6] => E_COMPILE_ERROR
[7] => E_COMPILE_WARNING
[8] => E_USER_ERROR
[9] => E_USER_WARNING
[10] => E_USER_NOTICE
[11] => E_STRICT
[12] => E_RECOVERABLE_ERROR
[13] => E_DEPRECATED
[14] => E_USER_DEPRECATED
)
catchable_errors: Array
(
[0] => E_WARNING
[1] => E_NOTICE
[2] => E_USER_ERROR
[3] => E_USER_WARNING
[4] => E_USER_NOTICE
[5] => E_STRICT
[6] => E_RECOVERABLE_ERROR
[7] => E_DEPRECATED
[8] => E_USER_DEPRECATED
)
shutdown_errors: Array
(
[0] => E_ERROR
[1] => E_CORE_ERROR
[2] => E_COMPILE_ERROR
[3] => E_RECOVERABLE_ERROR
[4] => E_USER_ERROR
)
non_catchable_errors: Array
(
[0] => E_ERROR
[1] => E_PARSE
[2] => E_CORE_ERROR
[3] => E_CORE_WARNING
[4] => E_COMPILE_ERROR
[5] => E_COMPILE_WARNING
)
non_shutdown_errors: Array
(
[0] => E_WARNING
[1] => E_PARSE
[2] => E_NOTICE
[3] => E_CORE_WARNING
[4] => E_COMPILE_WARNING
[5] => E_USER_WARNING
[6] => E_USER_NOTICE
[7] => E_STRICT
[8] => E_DEPRECATED
[9] => E_USER_DEPRECATED
)
catchable_shutdown_errors: Array
(
[0] => E_RECOVERABLE_ERROR
[1] => E_USER_ERROR
)
catchable_non_shutdown_errors: Array
(
[0] => E_WARNING
[1] => E_NOTICE
[2] => E_USER_WARNING
[3] => E_USER_NOTICE
[4] => E_STRICT
[5] => E_DEPRECATED
[6] => E_USER_DEPRECATED
)
non_catchable_shutdown_errors: Array
(
[0] => E_ERROR
[1] => E_CORE_ERROR
[2] => E_COMPILE_ERROR
)
non_catchable_non_shutdown_errors: Array
(
[0] => E_PARSE
[1] => E_CORE_WARNING
[2] => E_COMPILE_WARNING
)
Note that the non_shutdown_error and shutdown_error handlers are included as a proof of concept. You are supposed to program yourself proper handlers, if and how you see fit. But you can certainly use them for testing something out, like I did above.