Catching Warnings and Notices

Permanent Link: Catching Warnings and Notices 15. November 2008 RSS Feed for comments on RSS-Feed für Kommentare zu: Catching Warnings and Notices comments feed

Simple example: You want to open a file in PHP and there might be a slight possibility, that the file doesn't exist. Your code probably looks like this:

$fileHandle = fopen('text.txt', 'r');
if (!$fileHandle) {
print("Couldn't open file");
}

According to the php documentation, fopen will return FALSE if it can't open the file, but (and that's the annoying part), it will also generate a warning. Furthermore the php documentation suggests using @ before the function call to suppress the warning. Using @ before a function call is really, really bad coding and it makes your code slow (since @ changes the error_reporting to E_NONE before the function call and changes it back to what you had before after the function call) . But why return false, when it will generate a warning anyhow? Throwing an Exception would be the better way. Here's a way to do it.

First, we create a class containing a public static method that handles the error:

class ErrorHandler
{
    /**
    * catches php errors / warnings / notices and throws
    * an exception instead
    *
    * @param int $errNo
    * @param string $errStr
    */
    public static function handle($errNo, $errStr=NULL)
    {
        switch ($errNo) {
            case E_WARNING:
                throw new RuntimeException($errStr,$errNo);
            break;
            default:
                throw new Exception($errStr,$errNo);
            break;
        }
    }
}

The method handle has the error number and the error message, which are both generated by php. Error number would be something like E_NOTICE, E_WARNING or E_STRICT. The error string in this case would be "failed to open stream: No such file or directory' in /var/www/phpinfo.php:15".

Now we add a public static function method to the same class, that overwrites the php error handler with the method we just wrote:

/**
* Overwrites the PHP error handler and uses our own
*
*/
public static function set()
{
set_error_handler(array(__CLASS__ , 'handle'));
}

Now there's only one thing left to do: We have to call the set method at the beginning of our script:

ErrorHandler::set();

If we now execute the script again, we will not get a warning, instead we'll get an uncaught exception: Fatal error: Uncaught exception 'RuntimeException' with message 'fopen(text.txt) [function.fopen]: failed to open stream: No such file or directory' in /var/www/phpinfo.php:15 Stack trace: #0 [internal function]: ErrorHandler::handle(2, 'fopen(text.txt)…', '/var/www/phpinfo…', 35, Array) #1 /var/www/phpinfo.php(35): fopen('text.txt', 'r') #2 {main} thrown in /var/www/phpinfo.php on line 15

The cool thing is, that now we can simply catch the exception and will not flood the phperror log with Warnings, where they are probably not needed. Our script would now look like this:

ErrorHandler::set();
try {
    $fileHandle = fopen('text.txt', 'r');
} catch(Exception $e) {
    print("Couldn't open file");
}

Note, that you can catch anything but a fatal error.

13 comments

Loïc Hoguins Gravatar

Loïc Hoguin
13.12.2008, 10:52 o'clock

Hello, that's a good technique indeed.

Note however that your handler currently also throw exceptions when errors are silenced with the @ operator. In order to continue the script correctly when an error has been silenced, you need to add the following at the start of your handler: "if (error_reporting() == 0) return;". (see php.net/set_error_handler)

Even if it shouldn't happen often, sometimes you simply need to silence warnings and continue the script anyway.

Dominik Jungowski s Gravatar

Dominik Jungowski
13.12.2008, 14:14 o'clock

I can understand your point indeed,the question would be though: why suppress it with @ (and slowing things down),when i can just put the call in a try-catch-block ;)

Jagger Wangs Gravatar

Jagger Wang
13.12.2008, 16:38 o'clock

Good skill!

Loïc Hoguins Gravatar

Loïc Hoguin
13.12.2008, 21:33 o'clock

@Dominik

When working with third party code you cannot ensure that this code will avoid using @, and not filtering the silenced errors will thus break it. The errors needs to be silenced and no exception be thrown for the third party code to work correctly.

That's the most common example I've met, like when using PEAR with PHP5 and E_STRICT enabled.

You might also consider using the ErrorException class instead of the Exception class.

Dominik Jungowski s Gravatar

Dominik Jungowski
14.12.2008, 01:17 o'clock

Didn't think of that. In that case it surely makes sense

fuseros Gravatar

fusero
27.07.2009, 09:22 o'clock

i've seen that this method is quite popular in order to be able to manage PHP warnigns and notices… but i wonder (as a newbie in php eceptions) if there is a way to custom the exception to be thrown in the static method handle(). For example, i would like to be able to throw an IOException when a file cannot be open, or a MathException when i find a division by zero. Can i do this wothout having to search for key words in error message?

Dirk Pahls Gravatar

Dirk Pahl
17.06.2010, 15:25 o'clock

Hello,

found this post when looking for a method to deal with PHP notices and warnings and wondered about the sentence "Using @ before a function call is really, really bad coding and it makes your code slow.". So I wrote a little benchmark script:

$start = microtime(true);
for($i = 0; $i < 10000; $i++) {
@unserialize("unserializable");
}
$duration = microtime(true) - $start;
print("overall duration with @: $duration\n");

$start = microtime(true);
for($i = 0; $i < 10000; $i++) {
ErrorHandler::set();
try {
unserialize("unserializable");
}
catch(Exception $e) {}
ErrorHandler::reset();
}
$duration = microtime(true) - $start;
print("overall duration with error handler: $duration\n");

The function ErrorHandler::reset() is a call of restore_error_handler() as I want to go on with the initially configured error reporting settings after handling this notice.

The result of this little benchmark is completely different from what is said in the mentioned sentence above:

overall duration with @: 0.0735211372375
overall duration with error handler: 0.959964036942

Using the @ is more than ten times faster on my machine.

Dominik Jungowskis Gravatar

Dominik Jungowski
18.06.2010, 08:00 o'clock

Did the script without @ write an error message? If so, it should be the reason why it's so much slower.

Dirk Pahls Gravatar

Dirk Pahl
18.06.2010, 08:49 o'clock

No, it did'nt write an error message because I caught the exception … The two lines shown are the only output

Dominik Jungowskis Gravatar

Dominik Jungowski
18.06.2010, 10:31 o'clock

Then I would guess it's the try catch that makes it slow. It would be interesting to see what the script is like without the try-catch and without any output.

Dirk Pahls Gravatar

Dirk Pahl
18.06.2010, 10:42 o'clock

I removed throwing exceptions and catching them. After that the output is:

overall duration with @: 0.0508148670197
overall duration with error handler: 0.194847106934

First version is still 4 times faster. I think it's because setting and resetting the error handler in each loop. But even when I set end reset the error handler only one time before and after the loop I get:

overall duration with @: 0.051607131958
overall duration with error handler: 0.0809028148651

Both versions need nearly the same time now but using @ is still a bit faster.

Dominik Jungowskis Gravatar

Dominik Jungowski
18.06.2010, 11:24 o'clock

Which is strange, since @ modifies the error handling before and after each (function) call.

Are both benchmarks in one file or is it one file per benchmark?

Dirk Pahls Gravatar

Dirk Pahl
18.06.2010, 18:56 o'clock

Are both in the same file. When running in different files you get the same results. It seems to me that setting the error handler is even more expensive than what is done when using @.

Write a comment

(will not be published)