How to catch undefined functions with set_error_handler in PHP - php

I'm taking the leap: my PHP scripts will ALL fail gracefully!
At least, that's what I'm hoping for...`
I don't want to wrap (practically) every single line in try...catch statements, so I think my best bet is to make a custom error handler for the beginning of my files.
I'm testing it out on a practice page:
function customError($level,$message,$file,$line,$context) {
echo "Sorry, an error has occured on line $line.<br />";
echo "The function that caused the error says $message.<br />";
die();
}
set_error_handler("customError");
echo($imAFakeVariable);
This works fine, returning:
Sorry, an error has occurred on line 17. The function that caused the
error says Undefined variable: imAFakeVariable.
However, this setup doesn't work for undefined functions.
function customError($level,$message,$file,$line,$context) {
echo "Sorry, an error has occured on line $line.<br />";
echo "The function that caused the error says $message.<br />";
die();
}
set_error_handler("customError");
imAFakeFunction();
This returns:
Fatal error: Call to undefined function: imafakefunction() in
/Library/WebServer/Documents/experimental/errorhandle.php on line 17
Why isn't my custom error handler catching undefined functions? Are there other problems that this will cause?

set_error_handler is designed to handle errors with codes of: E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE. This is because set_error_handler is meant to be a method of reporting errors thrown by the user error function trigger_error.
However, I did find this comment in the manual that may help you:
"The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called."
This is not exactly true. set_error_handler() can't handle them, but ob_start() can handle at least E_ERROR.
<?php
function error_handler($output)
{
$error = error_get_last();
$output = "";
foreach ($error as $info => $string)
$output .= "{$info}: {$string}\n";
return $output;
}
ob_start('error_handler');
will_this_undefined_function_raise_an_error();
?>
Really though these errors should be silently reported in a file, for example. Hopefully you won't have many E_PARSE errors in your project! :-)
As for general error reporting, stick with Exceptions (I find it helpful to make them tie in with my MVC system). You can build a pretty versatile Exception to provide options via buttons and add plenty of description to let the user know what's wrong.

I guess you needs to use register_shutdown_function also
For example:
register_shutdown_function( array( $this, 'customError' ));.
function customError()
{
$arrStrErrorInfo = error_get_last();
print_r( $arrStrErrorInfo );
}

From the documentation (emphasis added):
The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.
Calling undefined functions triggers an E_ERROR, thus it can not be handled by the error callback (or by exception handlers for that matter). All that you can do is set error_reporting to 0.
PS, if you are rolling your own error handler, you should take care to handle correctly the # operator. From the documentation (emphasis added):
It is important to remember that the standard PHP error handler is completely bypassed. error_reporting() settings will have no effect and your error handler will be called regardless - however you are still able to read the current value of error_reporting and act appropriately. Of particular note is that this value will be 0 if the statement that caused the error was prepended by the # error-control operator.

Why isn't my custom error handler catching undefinedd functions? Are there other problems that this will cause?
At a guess, I'd say that undefined function errors travel through a different execution path than other error types. Perhaps the PHP designers could tell you more, except I doubt PHP is in any way designed.
If you'd like your scripts to fail gracefully while still writing them PHP-style, try putting the entire page in a function and then call it within a try..catch block.

I've been playing around with error handling for some time and it seems like it works for the most part.
function fatalHandler() {
global $fatalHandlerError, $fatalHandlerTitle;
$fatalHandlerError = error_get_last();
if( $fatalHandlerError !== null ) {
print($fatalHandlerTitle="{$fatalHandlerTitle} | ".join(" | ", $fatalHandlerError).
(preg_match("/memory/i", $fatalHandlerError["message"]) ? " | Mem: limit ".ini_get('memory_limit')." / peak ".round(memory_get_peak_usage(true)/(1024*1024))."M" : "")."\n".
"GET: ".var_export($_GET,1)."\n".
"POST: ".var_export($_POST,1)."\n".
"SESSION: ".var_export($_SESSION,1)."\n".
"HEADERS: ".var_export(getallheaders(),1));
}
return $fatalHandlerTitle;
}
function fatalHandlerInit($title="phpError") {
global $fatalHandlerError, $fatalHandlerTitle;
$fatalHandlerTitle = $title;
$fatalHandlerError = error_get_last();
set_error_handler( "fatalHandler" );
}
Now I have an issue where if the memory is exhausted, it doesn't report it every time. It seems like it depends on how much memory is being used.
I did a script to load a large file (takes ~6.6M of memory) in an infinite loop.
Setup1:
ini_set('memory_limit', '296M');
fatalHandlerInit("testing");
$file[] = file("large file"); // copy paste a bunch of times
In this case I get the error to be reports and it dies on 45 file load.
Setup2 - same but change:
ini_set('memory_limit', '299M');
This time I don't get an error and it doesn't even call my custom error function. The script dies on the same line.
Does anyone have a clue why and how to go around that?

Very interesting thing that I've discovered today as I was facing the similar problem. If you use the following - it will catch the error with your custom error handler function / method:
ini_set('display_errors', 'Off');
error_reporting(-1);
set_error_handler(array("Cmd\Exception\Handler", "getError"), -1 & ~E_NOTICE & ~E_USER_NOTICE);
By setting 'display_errors' to 'Off' you can catch still catch them with the handler.

At a guess, I'd say that undefined function errors travel through a different execution path than other error types. Perhaps the PHP designers could tell you more, except I doubt PHP is in any way designed.

Related

error_get_last() returns NULL in PHP 7 when a custom exception handler is set

Ok, this took some time to break it down. Here it is:
There is an included faulty script which is the following for the remainder of this post:
faulty.php
<?php
$a = 4 // missing semicolon
$b = 2;
Then consider the following script for handling the error. Note, that the custom exception handler is initially not registered.
script.php
<?php
// disable default display of errors
ini_set('display_errors', 0);
// register functions
#set_exception_handler('catchException'); // initially not set
register_shutdown_function('catchError');
// define error function
function catchError(){
echo "PHP version: ".phpversion();
if(is_null(error_get_last())) echo "<h1>No errors fetched!</h1>";
else echo "<h1>Error fetched:</h1>";
var_dump(error_get_last());
}
// define exception function (not used in all examples)
function catchException(){}
// include faulty script
include("D:/temp/faulty.php");
Result with no custom exception handler
The results for PHP 5 and 7 are identical. The error_get_last() function returns the last ocurred error (Screenshot).
Result with custom error handler
Now we set a custom function uncommenting the line
set_exception_handler('catchException');
This will work fine in PHP 5, however in PHP 7 the error_get_last() function returns NULL (Screenshot).
Question
Why is this? Especially confusing as the custom exception handler is empty, e.g. not "successfully handling" the error.
How to prevent this?
All the best and thanks for hints!
Update: problem and solution
The thing (not really a problem) is that PHP 7 throws an exception of type ParseError rather then producing an error. Thus, it is best handled with an exception handler. Make a nice exception handler to handle the exception well:
function catchException($e){
echo "<h1>".get_class($e)."</h1>";
echo $e->getMessage()."<br>";
}
PHP 7 throws a ParseError exception instead of triggering an error of type E_PARSE. The default exception handler seems to trigger an error if an uncaught exception is encountered. However if you replace it with set_exception_handler() it no longer happens unless you do it yourself.
See PHP docs:
PHP 7 changes how most errors are reported by PHP. Instead of reporting errors through the traditional error reporting mechanism used by PHP 5, most errors are now reported by throwing Error exceptions.

PHP exceptions thrown in error handler not caught by exception handler

I chose this title because I have the exact same problem as stated in here:
PHP exceptions thrown in error handler are not caught by exception handler
The author accepted the answer which said he obviously was doing something wrong.
My error and exception handler were working fine that last two years, but now im facing the exactly same problem.
I did a code update and also a server update (Plesk 11.5 running, the PHP version should be the same and is 5.3.2). I checked my code for the error, but a test made it clear that this cant be the problem:
I wrote the following testfile:
function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
throw new Exception("this was an error");
}
function exceptionHandler($e) {
echo 'exceptionHandler';
}
set_error_handler('errorHandler');
set_exception_handler('exceptionHandler');
// test one:
throw new Exception(); // outputs "exceptionHandler"
// test two - uncomment the first test of course!
$test->blabla();
The second test should also output "exceptionHandler", but it doesn't!
The output is "Fatal error: Call to a member function blabla() on a non-object in ......./exeptiontest.php on line 0"
This problem drives me crazy at the moment. Any suggestions here? Any PHP settings that cause this?
Update (After reading your comment).
After an error handler has been executed program flow would went back to the expression after that where the error occurred. But it is unreliable to pass back program flow to a fatally failed script. That's why error handlers won't get called on fatal errors. The documentation says:
The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.
As a workaround (depending on your needs), you may define a shutdown function using register_shutdown_function().
Original Answer (It turned out that this was not the problem here)
You need to read the documentation of set_exception_handler() carefully, especially the code example:
function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "\n";
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
echo "Not Executed\n";
Meaning in your case that $test->blabla() will never gets executed.
You might expect that the exception handler function works like a catch block, but that isn't the case. True is, that if an exception occurs and no catch block is defined, the program flow will take over to the exception handler which may gracefully shutdown the script - but not more. If you want to handle exceptions the right way, use try / catch
Just to make it more clear: exception handlers behave different than error handlers. After returning from an exception handler the program will terminate while the program flow wents back to the expression after the error when returning from an error handler.

Is it possible to catch all PHP errors occured in a file

I want to catch all PHP errors (E_ERROR, E_WARNING, E_NOTICE,..) in a page and log it to MongoDB in order to read and comment better on it.
I found a way to catch last occured error with 'error_get_last' and send it to anywhere i want with 'register_shutdown_function' Handle fatal errors in PHP using register_shutdown_function()
But what if my file have more than one errors, like warnings and notices, how can i get all errors in a file? Is this possible? What i need is something like 'error_get_all'.
Here you can find my code, this code has one fatal error, two warning and one notice:
function shutdown() {
$error = error_get_last();
if ($error['type'] === E_ERROR || $error['type'] === E_WARNING || $error['type'] === E_NOTICE) {
var_dump($error);
}
}
register_shutdown_function('shutdown');
spl_autoload_register('foo');
$x = 5;
$y = 0;
echo 'x/y: ' . $x / $y . '<br/>';
foreach ($noarray as $noelement) {
echo 'no element: ' . $noelement . '<br/>';
}
The answer is to combine solutions:
Use 'set_error_handler' to catch all possible errors it can (see http://php.net/manual/en/function.set-error-handler.php), as well as using the described register_shutdown_function to log the errors which are missed by this.
By doing this, non-fatal errors during runtime will be caught by the custom handler, continuing on until end of script or a fatal error occurs which would be caught by either the custom or the shutdown function depending on type.
Use set_error_handler.
The following error types cannot be handled with a user defined
function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING,
E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the
file where set_error_handler() is called.
For catching fatal errors and parsing errors you're using right funciton
register_shutdown_function
But when application reach fatal error, or parsing or any error E_ERROR the process of execution ending at that point of the script, so in this case you can catch only one such a error in script.
I will suggest you to use register_shutdown_function just in case to catch fatal errors, and other errors catch with function set_error_handler to catch other errors as warning etc, for exceptions set_exception_handler by that you can easy track all errors in system and validate them also.
If you will write object for error handling then the logic for you can be:
$handler = new Handler();
$handler->handleExceptions(); //using set_exception_handler exceptions
$handler->handleError(E_ALL); //using set_error_handler all other errors
$handler->handleShutdown(); //using register_shutdown_function fatals, parsing
By that you will have options to turn off/on errors and store errors where you need them or send by email from one place, also simple way to get full error for debuging.
May be you want to use a framework like this:
http://code.google.com/p/lagger/
Fatal errors past the first one encountered are impossible to catch, as the PHP interpreter never reaches them (the script terminates after it executes your shutdown handler). Non-fatal errors can be caught (you will need a separate error_handler installed for them in addition to your shutdown handler), PHP won't terminate the script so if you don't terminate it in your error handler either, execution will continue after each non-fatal error and you will catch them all. If you want to still terminate after the entire file has been executed, you might want to keep a flag somewhere, clear it before you include the file and raise it in case of error; after the file has been included, you can test this flag and see if any error has been caught. Keep in mind that the included file will have fully executed at that point (if it did not have any fatal errors) or you might never reach that point (if it has fatal errors).
If you're running untrusted user code, you might be interested in Runkit Sandboxes. You can also take a look at runkit_lint_file() for some pre-include validation.

Handle fatal errors in PHP using register_shutdown_function()

According to the comment on this answer it is possible to catch Fatal Errors through a shutdown function which cannot be caught using set_error_handler().
However, I couldn't find out how to determine if the shutdown has occured due to a fatal error or due to the script reaching its end.
Additionally, the debug backtrace functions seem to be defunct in the shutdown function, making it pretty worthless for logging the stack trace where the Fatal Error occured.
So my question is: what's the best way to react on Fatal Errors (especially undefined function calls) while keeping the ability to create a proper backtrace?
This works for me:
function shutdown() {
$error = error_get_last();
if ($error['type'] === E_ERROR) {
// fatal error has occured
}
}
register_shutdown_function('shutdown');
spl_autoload_register('foo');
// throws a LogicException which is not caught, so triggers a E_ERROR
However, you probably know it already, but just to make sure: you can't recover from a E_ERROR in any way.
As for the backtrace, you can't... :( In most cases of a fatal error, especially Undefined function errors, you don't really need it. Pinpointing the file/line where it occured is enough. The backtrace is irrelevant in that case.
One way to distinguish between fatal error and proper application shutdown with the register_shutdown_function is to define a constant as the last line of your program, and then check if the constant is defined:
function fatal_error() {
if ( ! defined(PROGRAM_EXECUTION_SUCCESSFUL)) {
// fatal error has occurred
}
}
register_shutdown_function('fatal_error');
define('PROGRAM_EXECUTION_SUCCESSFUL', true);
If the program reaches the end, it could not have encountered a fatal error, so we know not to run the function if the constant is defined.
error_get_last() is an array with all the information regarding the fatal error that you should need to debug, though it will not have a backtrace, as has been mentioned.
Generally, if your php program has encountered a fatal error (as opposed to an exception), you want the program to blow up so you can find and fix the problem. I've found register_shutdown_function useful for production environments where you want error reporting off, but want some way to log the error in the background so that you can respond to it. You could also use the function to direct the user to a friendly html page in the event of such an error so that you don't just serve up a blank page.
Just a nice trick to get the current error_handler method =)
<?php
register_shutdown_function('__fatalHandler');
function __fatalHandler()
{
$error = error_get_last();
//check if it's a core/fatal error, otherwise it's a normal shutdown
if($error !== NULL && $error['type'] === E_ERROR) {
//Bit hackish, but the set_exception_handler will return the old handler
function fakeHandler() { }
$handler = set_exception_handler('fakeHandler');
restore_exception_handler();
if($handler !== null) {
call_user_func($handler, new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
}
exit;
}
}
?>
Also i wan't to note that if you call
<?php
ini_set('display_errors', false);
?>
Php stops displaying the error, otherwise the error text will be send to the client prior to your error handler

PHP's error levels

The only error level in PHP that will halt the script but can be caught and dealt with have to be triggered with the trigger_error() function correct? I'm referring to the "E_USER_ERROR" error level. The "E_ERROR" error will simply halt the script and I can't do anything about it as the developer.
E_ERROR will simply stop the script. It's meant to be used for:
Fatal run-time errors. These indicate errors that can not be recovered from, such as a memory allocation problem. Execution of the script is halted.
Ref
You cannot handle the following other error types for similar reasons:
E_PARSE
E_CORE_ERROR
E_CORE_WARNING
E_COMPILE_ERROR
E_COMPILE_WARNING
set_error_handler() however can handle the follow errors:
E_WARNING
E_NOTICE
E_USER_ERROR (using trigger_error)
E_USER_WARNING (using trigger_error)
E_USER_NOTICE (using trigger_error)
E_STRICT
E_RECOVERABLE_ERROR
You can catch E_ERROR using a shutdown script
from my posting http://jarofgreen.wordpress.com/2011/02/04/catching-php-errors/
register_shutdown_function(‘x’);
function x() {
$error = error_get_last();
if ($error) {
// deal with $error['file'],$error['line'],$error['message'],$error['type']
}
}
You can create your own error handling and halt the script and actually do anything you want when an error occurs.
set_error_handler()
Not really sure what your driving at or trying to do here, but if you're looking for a way to "catch" and deal with 'errors' - Maybe look into exceptions.
From PHP Manual on Exceptions
An exception can be thrown, and caught
("catched") within PHP. Code may be
surrounded in a try block, to
facilitate the catching of potential
exceptions. Each try must have at
least one corresponding catch block.
Multiple catch blocks can be used to
catch different classes of exeptions.
Normal execution (when no exception is
thrown within the try block, or when a
catch matching the thrown exception's
class is not present) will continue
after that last catch block defined in
sequence. Exceptions can be thrown (or
re-thrown) within a catch block.

Categories