414

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:

  1. set_error_handler seems like overkill because I have to use it to filter every warning in the page (is this true?);

  2. 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?

dreftymac
  • 31,404
  • 26
  • 119
  • 182
user121196
  • 30,032
  • 57
  • 148
  • 198

13 Answers13

423

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.

stollr
  • 6,534
  • 4
  • 43
  • 59
Philippe Gerber
  • 17,457
  • 6
  • 45
  • 40
  • 5
    is it advisable to set my own error handler right before the function call, then restore_error_handler when the error checking is done? – user121196 Aug 06 '09 at 22:19
  • 3
    will this be thread safe if there are many concurrent requests, and each request does 1.set_error_handler(). 2.doit 3.restore_error_handler ? – user121196 Aug 06 '09 at 22:23
  • 1
    1. if this is good practice? I really don't know. but if the dudes from Zend use this method, it can't really be that bad. 2. each request is self-contained, so there wont be any problem. – Philippe Gerber Aug 06 '09 at 23:01
  • 2
    +1 for the avoidance of using @ to suppress errors. E_WARNING is actually a non-fatal error. In general you should always try to handle errors appropriately. If your application requires the usage of set_error_handler then do so. It is usually advisable to log errors and disable the display of them in a production environment. Upon checking the logs you can see where to make changes in your development environment. Too many instances where I've seen @fopen/@unlink and wonder why the developer didn't perform checks to avoid the errors or handle the error using set_error_handler. – Will B. Jan 21 '14 at 17:36
  • 7
    A note about turning warnings into exceptions: a warning won't abort your app—an *uncaught* exception will do! – Álvaro González May 07 '15 at 11:30
  • @ÁlvaroGonzález: If you turn *everything* into exceptions, even a E_NOTICE will abort your app - and those are emitted in unexpected (undocumented) locations. For example, this seems all clean and functional, but also emits notices: https://www.php.net/manual/en/function.apcu-fetch.php – Piskvor left the building Jul 29 '19 at 09:30
  • 1
    Note that if you intend to temporarily set the error handler and catch/throw the exceptions, you should put the `restore_error_handler()` in a `finally`, otherwise it might not get reset. – mbomb007 Sep 18 '19 at 15:19
  • Will set_error_handler install a global error handler or only for my very script that is currently running? If it's the latter, why bother calling restore_error_handler at all? If it's the former, this solution has 1000 problems. – user3700562 Jun 19 '21 at 15:55
  • Just a note that for PHP8, you may have other options https://stackoverflow.com/a/69849208/1548557 – MrMesees Nov 05 '21 at 06:14
  • @MrMesees this only works with TypeError and ValueError (e.g. providing bad input arguments). but it won't work with warnings due to runtime errors. See https://3v4l.org/p2Eka as an example. – Philippe Gerber Jan 10 '22 at 21:09
175

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
}
Prokhor Sednev
  • 668
  • 7
  • 14
Robert
  • 1,751
  • 1
  • 10
  • 2
33

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.

alexandre-rousseau
  • 2,321
  • 26
  • 33
GuruBob
  • 855
  • 1
  • 10
  • 21
6

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:

Is there a static way to throw exception in php

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.

Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278
  • And how can we like revert this change ? – Elikill58 Feb 09 '23 at 07:50
  • As per boolean evaluation logic `return false && ` will never look beyond `&&` because encountering `false` in a `&&` chain once is enough to be certain it remains `false`. Thus, neither literals, nor variables, nor functions will be evaluated afterwards. So the order becomes important. If anything, make it `return func() && false`. This is called [short-circuit evaluation](https://en.wikipedia.org/wiki/Short-circuit_evaluation). – AmigoJack Mar 20 '23 at 10:25
5

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;
}
Bugfighter
  • 51
  • 1
  • 2
  • The final parameter in the handler function (`$err_context`) has been removed in PHP 8 (https://www.php.net/manual/en/function.set-error-handler.php). – Roger Dueck Jun 23 '22 at 21:10
4

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.

florynth
  • 41
  • 1
3

If dns_get_record() fails, it should return FALSE, so you can suppress the warning with @ and then check the return value.

Amber
  • 507,862
  • 82
  • 626
  • 550
2

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.

rpjohnst
  • 1,612
  • 14
  • 21
2

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.

MrMesees
  • 1,488
  • 19
  • 27
1

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
}
Farhad
  • 4,119
  • 8
  • 43
  • 66
gborjal
  • 21
  • 2
0

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
Juned Ansari
  • 5,035
  • 7
  • 56
  • 89
-1

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.

  • 4
    This is one time you definitely don't want to use @ - you have control over the operation and can check if it's a division by zero or not before doing it. – Eborbob Feb 21 '17 at 10:53
-1

Since PHP7 you can catch most errors and warnings like so:

try {
    whatever();
} catch (Throwable $e) {
}

More: https://www.php.net/manual/en/language.errors.php7.php

cprn
  • 1,561
  • 1
  • 19
  • 25