Can I try/catch a warning?
PhpError HandlingTry CatchPhp Problem Overview
I need to catch some warnings being thrown from some php native functions and then handle them.
Specifically:
array dns_get_record ( string $hostname [, int $type= DNS_ANY [, array &$authns [, array &$addtl ]]] )
It throws a warning when the DNS query fails.
try
/catch
doesn't work because a warning is not an exception.
I now have 2 options:
-
set_error_handler
seems like overkill because I have to use it to filter every warning in the page (is this true?); -
Adjust error reporting/display so these warnings don't get echoed to screen, then check the return value; if it's
false
, no records is found for hostname.
What's the best practice here?
Php Solutions
Solution 1 - Php
Set and restore error handler
One possibility is to set your own error handler before the call and restore the previous error handler later with restore_error_handler()
.
set_error_handler(function() { /* ignore errors */ });
dns_get_record();
restore_error_handler();
You could build on this idea and write a re-usable error handler that logs the errors for you.
set_error_handler([$logger, 'onSilencedError']);
dns_get_record();
restore_error_handler();
Turning errors into exceptions
You can use set_error_handler()
and the ErrorException
class to turn all php errors into exceptions.
set_error_handler(function($errno, $errstr, $errfile, $errline) {
// error was suppressed with the @-operator
if (0 === error_reporting()) {
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
try {
dns_get_record();
} catch (ErrorException $e) {
// ...
}
The important thing to note when using your own error handler is that it will bypass the error_reporting
setting and pass all errors (notices, warnings, etc.) to your error handler. You can set a second argument on set_error_handler()
to define which error types you want to receive, or access the current setting using ... = error_reporting()
inside the error handler.
Suppressing the warning
Another possibility is to suppress the call with the @ operator and check the return value of dns_get_record()
afterwards. But I'd advise against this as errors/warnings are triggered to be handled, not to be suppressed.
Solution 2 - Php
The solution that really works turned out to be setting simple error handler with E_WARNING
parameter, like so:
set_error_handler("warning_handler", E_WARNING);
dns_get_record(...)
restore_error_handler();
function warning_handler($errno, $errstr) {
// do something
}
Solution 3 - Php
Be careful with the @
operator - while it suppresses warnings it also suppresses fatal errors. I spent a lot of time debugging a problem in a system where someone had written @mysql_query( '...' )
and the problem was that mysql support was not loaded into PHP and it threw a silent fatal error. It will be safe for those things that are part of the PHP core but please use it with care.
bob@mypc:~$ php -a
Interactive shell
php > echo @something(); // this will just silently die...
No further output - good luck debugging this!
bob@mypc:~$ php -a
Interactive shell
php > echo something(); // lets try it again but don't suppress the error
PHP Fatal error: Call to undefined function something() in php shell code on line 1
PHP Stack trace:
PHP 1. {main}() php shell code:0
bob@mypc:~$
This time we can see why it failed.
Solution 4 - Php
I wanted to try/catch a warning, but at the same time keep the usual warning/error logging (e.g. in /var/log/apache2/error.log
); for which the handler has to return false
. However, since the "throw new..." statement basically interrupts the execution, one then has to do the "wrap in function" trick, also discussed in:
Or, in brief:
function throwErrorException($errstr = null,$code = null, $errno = null, $errfile = null, $errline = null) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
function warning_handler($errno, $errstr, $errfile, $errline, array $errcontext) {
return false && throwErrorException($errstr, 0, $errno, $errfile, $errline);
# error_log("AAA"); # will never run after throw
/* Do execute PHP internal error handler */
# return false; # will never run after throw
}
...
set_error_handler('warning_handler', E_WARNING);
...
try {
mkdir($path, 0777, true);
} catch (Exception $e) {
echo $e->getMessage();
// ...
}
EDIT: after closer inspection, it turns out it doesn't work: the "return false && throwErrorException ...
" will, basically, not throw the exception, and just log in the error log; removing the "false &&
" part, as in "return throwErrorException ...
", will make the exception throwing work, but will then not log in the error_log... I'd still keep this posted, though, as I haven't seen this behavior documented elsewhere.
Solution 5 - Php
Normaly you should never use @ unless this is the only solution. In that specific case the function dns_check_record should be use first to know if the record exists.
Solution 6 - Php
Combining these lines of code around a file_get_contents()
call to an external url helped me handle warnings like "failed to open stream: Connection timed out" much better:
set_error_handler(function ($err_severity, $err_msg, $err_file, $err_line, array $err_context)
{
throw new ErrorException( $err_msg, 0, $err_severity, $err_file, $err_line );
}, E_WARNING);
try {
$iResult = file_get_contents($sUrl);
} catch (Exception $e) {
$this->sErrorMsg = $e->getMessage();
}
restore_error_handler();
This solution works within object context, too. You could use it in a function:
public function myContentGetter($sUrl)
{
... code above ...
return $iResult;
}
Solution 7 - Php
You should probably try to get rid of the warning completely, but if that's not possible, you can prepend the call with @ (i.e. @dns_get_record(...)) and then use any information you can get to figure out if the warning happened or not.
Solution 8 - Php
If dns_get_record()
fails, it should return FALSE
, so you can suppress the warning with @
and then check the return value.
Solution 9 - Php
try checking whether it returns some boolean value then you can simply put it as a condition. I encountered this with the oci_execute(...) which was returning some violation with my unique keys.
ex.
oci_parse($res, "[oracle pl/sql]");
if(oci_execute){
...do something
}
Solution 10 - Php
FolderStructure
index.php //Script File
logs //Folder for log Every warning and Errors
CustomException.php //Custom exception File
CustomException.php
/**
* Custom error handler
*/
function handleError($code, $description, $file = null, $line = null, $context = null) {
$displayErrors = ini_get("display_errors");;
$displayErrors = strtolower($displayErrors);
if (error_reporting() === 0 || $displayErrors === "on") {
return false;
}
list($error, $log) = mapErrorCode($code);
$data = array(
'timestamp' => date("Y-m-d H:i:s:u", time()),
'level' => $log,
'code' => $code,
'type' => $error,
'description' => $description,
'file' => $file,
'line' => $line,
'context' => $context,
'path' => $file,
'message' => $error . ' (' . $code . '): ' . $description . ' in [' . $file . ', line ' . $line . ']'
);
$data = array_map('htmlentities',$data);
return fileLog(json_encode($data));
}
/**
* This method is used to write data in file
* @param mixed $logData
* @param string $fileName
* @return boolean
*/
function fileLog($logData, $fileName = ERROR_LOG_FILE) {
$fh = fopen($fileName, 'a+');
if (is_array($logData)) {
$logData = print_r($logData, 1);
}
$status = fwrite($fh, $logData . "\n");
fclose($fh);
// $file = file_get_contents($filename);
// $content = '[' . $file .']';
// file_put_contents($content);
return ($status) ? true : false;
}
/**
* Map an error code into an Error word, and log location.
*
* @param int $code Error code to map
* @return array Array of error word, and log location.
*/
function mapErrorCode($code) {
$error = $log = null;
switch ($code) {
case E_PARSE:
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
$error = 'Fatal Error';
$log = LOG_ERR;
break;
case E_WARNING:
case E_USER_WARNING:
case E_COMPILE_WARNING:
case E_RECOVERABLE_ERROR:
$error = 'Warning';
$log = LOG_WARNING;
break;
case E_NOTICE:
case E_USER_NOTICE:
$error = 'Notice';
$log = LOG_NOTICE;
break;
case E_STRICT:
$error = 'Strict';
$log = LOG_NOTICE;
break;
case E_DEPRECATED:
case E_USER_DEPRECATED:
$error = 'Deprecated';
$log = LOG_NOTICE;
break;
default :
break;
}
return array($error, $log);
}
//calling custom error handler
set_error_handler("handleError");
just include above file into your script file like this
index.php
error_reporting(E_ALL);
ini_set('display_errors', 'off');
define('ERROR_LOG_FILE', 'logs/app_errors.log');
include_once 'CustomException.php';
echo $a; // here undefined variable warning will be logged into logs/app_errors.log
Solution 11 - Php
As of PHP8, you can do the following instead of setting error handlers to catch Errors and Warnings. I Believe in PHP 7.something you could catch some Errors.
try {
call_user_func('sprintf', array_merge([$string], $args));
} catch (Throwable $e) {
$logger->info('mesage...');
}
You should generally be somewhere that you can pass or access a logger if you bulkhead in this way, as it can obfuscate coder errors, such as passing incorrectly typed parameters to a method, and mask a variety of other problems.
https://php.watch/versions/8.0/internal-function-exceptions
Not sure if notices are caught (likely not), but you can likely solve around examples like this one, by thinking a little more about what you are looking to do.
Both builder pattern, and options patterns provide solutions for this where prior to the site of call, which can be a private function or just after validity checks, you can throw a real custom exception that is attributable only to your code. That will make even built-in functions very safe to use.
One other nice practice is to use either debug_backtrace
, with DEBUG_BACKTRACE_IGNORE_ARGS
or use the getTrace
or getTraceAsString
methods on the Throwable so that some of the context is preseved.
Solution 12 - Php
I would only recommend using @ to suppress warnings when it's a straight forward operation (e.g. $prop = @($high/($width - $depth)); to skip division by zero warnings). However in most cases it's better to handle.