How do you catch recoverable errors in PHP 7+? - php

What happens with set_error_handler() on PHP7 now that all errors are exceptions? makes me think that recoverable errors can be caught but that does not appear to be the case, as demonstrated here:
<?php
$_SERVER['TEST'] = new stdClass;
try {
phpinfo();
} catch (\Throwable $e) {}
If you run that code you'll get this as the output:
$_SERVER['argc'] => 1
$_SERVER['TEST'] =>
Recoverable fatal error: Object of class stdClass could not be converted to string in /Users/username/zzz.php on line 6
So clearly an error is being "issued" but one is not being thrown, unless I'm misunderstanding something?

Not all errors were converted to Throwable errors in PHP 7.
Actually, the docs say:
most errors are now reported by throwing Error exceptions.
(Emphasis mine). Most !== All.
Some errors are still not catchable.
Funnily enough, the error message used you get used to say "Catchable Fatal Error" instead of "Recoverable Fatal Error" before PHP 7.1 was released.
This was reported as a bug, but the solution implemented by the devs was change the error string from Catchable to Recoverable to dispel misconceptions.
In the specific case you are testing, it seems that phpinfo() raises a recoverable error instead of throwing an Error, so it makes sense you can't catch it this way.
Still, not all hope is lost.
What you could do is convert all errors to exceptions by implementing your own error handler. An example of this is described on the ErrorException documentation:
function exception_error_handler($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
The neat thing of this example is that it takes into account your error reporting settings, so only errors that would have been reported under your settings are actually thrown as exceptions. Otherwise, nothing happens.
Testing your code:
<?php
function exception_error_handler($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
$_SERVER['TEST'] = new stdClass;
try {
phpinfo(INFO_VARIABLES);
} catch (\Throwable $e) {echo 'CAUGHT!!!!!!';}
prints the following output:
$_SERVER['TERM_SESSION_ID'] => w0t1p0:xxx-cxx-xxxxxxx-xxxxx
$_SERVER['SSH_AUTH_SOCK'] => /private/tmp/com.apple.launchd.xxxxxxxx/Listeners
$_SERVER['LC_TERMINAL_VERSION'] => 3.3.2
....
$_SERVER['argc'] => 1
$_SERVER['TEST'] =>
CAUGHT!!!!!!%

Related

PHP invalid string concatenation with object instance, what error is thrown? [duplicate]

I am playing with try - catch block:
<?php
try {
$str = "http://rejstrik-firem.kurzy.cz/73631604";
$domOb = new DOMDocument();
$html = $domOb->loadHTMLFile($str);
$domOb->preserveWhiteSpace = false;
$container = $domOb->getElementById('ormaininfotab');
echo $container; // <========= this is intended error which I want catch
}
catch (Exception $e) {
echo "Exception" . $e->getMessage() . ". File: " . $e->getFile() . ", line: " . $e->getLine();
}
catch (Error $e) {
echo "Error" . $e->getMessage() . ". File: " . $e->getFile() . ", line: " . $e->getLine();
}
?>
My result is this:
Catchable fatal error: Object of class DOMElement could not be
converted to string in /var/www/html/cirkve_ares/test.php on line 8
Why is not this error catched by second catch?
As user2782001 mentioned this is not a bug in the eyes of PHP dev's. They even noted that these type of errors should be referenced as 'recoverable':
we should get rid of any references to "catchable" fatal errors (if they still exist) in favor of "recoverable" fatal errors. Using "catchable" here is confusing as they cannot be caught using catch blocks.
On the ErrorException manual page there is a neat workaround converting those "catchable/recoverable" errors to ErrorException.
<?php
function exception_error_handler($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
?>
now you will be able to catch those errors with:
<?php
try {
// Error code
} catch (Error $e) { // this will catch only Errors
echo $e->getMessage();
}
?>
or
try {
// Error code
} catch (Throwable $t) { // this will catch both Errors and Exceptions
echo $t->getMessage();
}
?>
Someone reported this as a bug to PHP's devs, who promptly decided it was not a bug. https://bugs.php.net/bug.php?id=72948&edit=3
This case has been intentionally omitted ...(in practice you can simply convert the recoverable fatal to an exception using an error handler...)
So you still have to use the
set_error_handler()
function, which we were all hoping to leave behind. PHP's devs are so good at never letting your day be too sunny...
There might be some fatal errors which are not even caught by set_error_handler() or \Throwable.
The below implementation will catch the errors which are not even caught by \Throwable as tested in php 7.1. It should only be implemented in your development environment(by just adding it in your development config file) and shouldn't be done in production.
Implementation
register_shutdown_function(function () {
$err = error_get_last();
if (! is_null($err)) {
print 'Error#'.$err['message'].'<br>';
print 'Line#'.$err['line'].'<br>';
print 'File#'.$err['file'].'<br>';
}
});
Example Error
Error# Class Path/To/MyService contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Path/To/MyServiceInterface::add)
Line# 12
File# Path/To/MyService.php
It has been "fixed" as of PHP 7.4 according with Manual PHP, now it throws exception:
Existing recoverable fatal errors in string conversions have been converted to Error exceptions.

PHP - Capture all error_get_last() in One String

Is it possible to capture/gather all the errors on page and concatenate them to a string by using:
$allErrors = "";
$allErrors .= error_get_last(); // Each time an error shows up
I like to log errors in my database and would prefer to log all these PHP errors since I already have SQL-related PHP Fatal Errors logged.
error_get_last(), like the name is suggesting, only gives you the last error. And the fact that most errors will stop your script from running will get you only the last one and none of the previous ones. But you can set an own handler to catch every thrown error and exception. Here is an example
//function for exception handling
function handle_exception (Exception $exception) {
//here you can save the exception to your database
}
//function for error handling
function handle_error ($number, $message, $file, $line, $context = null) {
//pass/throw error to handle_exception
throw new ErrorException ($message, 0, $number, $file, $line);
}
//set error-handler but only for E_USER_ERROR and E_RECOVERABLE_ERROR
set_error_handler ('handle_error', E_USER_ERROR|E_RECOVERABLE_ERROR);
//exception-handler
set_exception_handler ('handle_exception');

Get error before they execute the error message function

I want to receive the error message in php before it gets executed. Basicly what i mean is that if I would have a bad code:
// This code is incorrect, I want to receive the error before it gets handled!
$some_var = new this_class_is_not_made;
Now that class does not exist, so it would be handles by the default error handler in php. But I want to disable the normal error handler and create my own.
Another example:
somefunction( string some_var ); // some_var misses the variable prefix. ( $ )
Example error message:
Fatal error: function 'some_var' is not defined in line: $x!
And this error would be: somefunction( string some_var );
But how would I receive the messages but also disable the normal error system?
EDIT: Making the error system execute a user-defined function
// I would want the error system to execute a function like this:
function(string $errorMessage, int $error_code){
if($error_code < 253){ return "Fatal error"; }
if($error_code < 528 && $error_code > 253){ return "Warning"; }
}
Answer found: By: ShiraNai7
try
{
// Code that may throw an Exception or Error.
}
catch (Throwable $t)
{
// Executed only in PHP 7, will not match in PHP 5
}
catch (Exception $e)
{
// Executed only in PHP 5, will not be reached in PHP 7
}
In PHP 7.0.0 or newer the code will throw Error exception if this_class_is_not_made doesn't exist.
try {
$some_var = new this_class_is_not_made;
} catch (Error $e) {
echo $e->getMessage();
}
Note that that this will also catch any other Error exceptions in case this_class_is_not_made does exist and causes some other error along the way.
In PHP versions prior to 7.0.0 you're out of luck - fatal errors always terminate the main script.
It might be a better idea to use class_exists() instead:
if (class_exists('this_class_is_not_made')) {
$some_var = new this_class_is_not_made;
}
This works in all PHP versions that support classes.

PHP: set_exception_handler not working for error thrown in set_error_handler callback

I have the following code to setup my error/exception handlers:
// Set the exception handler
set_exception_handler(function($e) {
echo 'Exception';
});
// Set the error handler
set_error_handler(function($code, $message, $file, $line) {
throw new ErrorException($message, 0, $code, $file, $line);
});
I have read numerous articles which have said to throw an exception within the set_error_handler callback function. This should mean I only have to handle exceptions. However the set_exception_handler callback function is never called and instead I get the warning:
Warning: Uncaught exception 'ErrorException' with message...
Please note that I am using PHP 5.4.
I tried to run the PHP script below, and it runs fine.
// set the exception handler
set_exception_handler(function($e) {
echo ' Exception';
});
// set the error handler
set_error_handler(function($code, $message, $file, $line) {
echo " Error [$message]";
throw new ErrorException($message, 0, $code, $file, $line);
},-1);
// trigger error
trigger_error('My own error.',E_USER_ERROR);
It returns:
Error [My own error.] Exception
Its exactly the same as yours. I cannot find anything wrong with the piece of code you provided, could the problem be outside it?

Exception handler does not work in closures?

I've trying to use this:
$error_handler = function($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
};
$exception_handler = function($exception) {
self::invokeHttpError(500);
};
set_error_handler($error_handler, E_ALL | E_STRICT);
set_exception_handler($exception_handler);
However, it fails when I call this:
$fn = function() {
$application->test(); // $application is undefined
};
$fn();
The error handler is called, but not the exception handler. What is happening?
Dereferencing an undefined object results in a fatal and uncatchable error. That's why your exception handler and error handler are not called when this happens.
This behaviour really irritates me as a developer though, I wish it would get turned into a catchable exception instead.
Update
One thing you could do to curb this problem is to throw an ErrorException inside your regular error handler. This would prevent an uncatchable fatal error when dereferencing an undefined symbol.

Categories