5

In my homemade PHP MVC framework, I've written a little error handler that wraps PHP errors in an exception, then throws it.

class ErrorController extends ControllerAbstract {

    ...

    public static function createErrorException($number, $message = NULL, $file = NULL, $line = NULL, array $context = array()) {
        throw new ErrorException($message, $number, 0, $file, $line);
    }
}

Which is then registered using set_error_handler(). This works fine, with the exception (no pun intended) of fatal errors. My custom error handler is still called, but I can't catch the ErrorException that is thrown.

An example of such an error would be trying to include a file that doesn't exist:

    try {
        require 'Controller/NonExistentController.php';
    } catch (ErrorException $exc) {
        echo $exc->getTraceAsString(); // code never reaches this block
    }

My custom error handler is called and the exception is thrown, but the code never reaches the "catch" block. Instead, PHP generates HTML (bad!):


Warning: Uncaught exception 'ErrorException' with message 'require(Controller/NonExistentController.php): failed to open stream: ...

Followed by:

Fatal error: Program::main(): Failed opening required 'Controller/NonExistentController.php' (include_path='.:') in ...

I do not want to attempt recovering from a fatal error, but I do want my code to exit gracefully. In this instance, that means sending back an XML or JSON response indicating an internal error, because this is a REST application and that's what my clients expect. HTML responses would most likely break the client applications as well.

Should I go about this differently?

tereško
  • 58,060
  • 25
  • 98
  • 150
Steven Liekens
  • 13,266
  • 8
  • 59
  • 85
  • 1
    Haha, PHP error handling, always makes me giggle, "@, the warning about the non-existent file won’t be printed. But it will be printed if scream.enabled is set in php.ini. Or if scream.enabled is set manually with ini_set. But not if the right error_reporting level isn’t set. If it is printed, exactly where it goes depends on display_errors, again in php.ini. Or ini_set." yes.... – Alec Teal Nov 16 '13 at 14:29
  • 1
    Oh and my fav "Catchable fatal error" – Alec Teal Nov 16 '13 at 14:30
  • I admit I'm spoiled by how easy exception handling is in C#, but surely it can't be that difficult in PHP? – Steven Liekens Nov 16 '13 at 14:31
  • There's A LOT wrong with PHP, the == operator is wrong, strpos returning 0 which can be false... have a read of http://me.veekun.com/blog/2012/04/09/php-a-fractal-of-bad-design/ I honestly don't think this can be solved, A for effort, but .... is it worth trying? – Alec Teal Nov 16 '13 at 14:33
  • I'm just not happy with PHP generating HTML output whenever it pleases. I haven't looked at how to configure php.ini files though. I thought that the world had moved on from ini files. – Steven Liekens Nov 16 '13 at 14:35
  • There's no other way of hiding this annoying messages than the `@` operator, but as @Alec Teal said, if `scream.enabled` is set, there's nothing you can do, but this is often disabled. – Henrique Barcelos Nov 16 '13 at 14:35

2 Answers2

7

Look at documentation about require on php.net:

require is identical to include except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include only emits a warning (E_WARNING) which allows the script to continue.

In your case you can handle fatal errors with help of register_shutdown_function, which requires PHP 5.2+:

function customFatalHandler() {
  $error = error_get_last();

  if( $error === NULL) {
      return;
  }

  $type   = $error["type"];
  $file = $error["file"];
  $line = $error["line"];
  $message  = $error["message"];

  echo "Error `$type` in file `$file` on line $line with message `$message`"; 
}

register_shutdown_function("customFatalHandler");

Also this can be helpfull for you

  1. error_get_last() and custom error handler
  2. Why doesn't PHP catch a "Class not found" error?
Community
  • 1
  • 1
Nick Bondarenko
  • 6,211
  • 4
  • 35
  • 56
  • I didn't even understand the difference between `require` and `include` until it slapped me in the face. Thanks for clearing that up! – Steven Liekens Nov 16 '13 at 14:54
  • I managed to get your solution to work, but it doesn't suppress the HTML warnings. What's up with that? – Steven Liekens Nov 16 '13 at 16:15
  • The answer is http://stackoverflow.com/questions/13952328/register-shutdown-function-still-outputs-original-error-message – Nick Bondarenko Nov 16 '13 at 16:21
  • You're right. Prefixing the `require` statement with the `@` operator also worked. I'm still wondering how I'm supposed to know all this. – Steven Liekens Nov 16 '13 at 16:31
  • Argh! This is seriously tripping me up. It seems that 1 line of code can generate multiple errors (for example 1x `E_WARNING` and 1x `E_ERROR`) depending on the error reporting setting. Does that sound right to you? – Steven Liekens Nov 16 '13 at 18:41
0

I'm sorry to have such a negative answer, but you (VERY PROBABLY) can't.

PHP is designed to chug on for as long as it can for some weird reason, you have a typo? silently initialise that variable to null, you put [] after something, evaluate as null if operator makes no sense!

This is extra messy with error handling, there are like 4 systems at play that do not work together. PHP does some error handling by "monkey patching", this causes a problem if you try to handle something in an error handler by throwing, it isn't "unpatched" until the handler exits.

With the shutdown handler, throwing from there can cause huge problems! It bit me on the ass before, I don't want to talk about it :P

It's also implementation defined as to whether files "resources" in PHP speak) are closed when shutting down, it was created to help plugins tidy up on web-servers. (hence you can have a list of them)

Alec Teal
  • 5,770
  • 3
  • 23
  • 50
  • Just when I thought I was starting to get the hang of it. Very discouraging. :( – Steven Liekens Nov 16 '13 at 14:48
  • @StevenLiekens I HATE Php, I hate how "arrays" are neither lists or associative containers, I hate error handling, I hate the bolted on features that don't really work in the bigger picture. PHP is an exceptional language because it never comes naturally, you will always need to search, is it `str2upper` or `str_to_upper` no! It's `strtoupper` wait `str_replace` wtf? If you can use Python, USE PYTHON. It is a language that makes sense, it is consistent and well thought out, and far easier than PHP. – Alec Teal Nov 16 '13 at 14:50
  • Well, I'm cross-compiling, so I'd probably spend far more time getting Python to work than working around the PHP stupidities. But I'll consider it. – Steven Liekens Nov 16 '13 at 15:53
  • Cross compiling....php? Regardless Python is a lot easier to get work, a lot easier to extend .... it's really worth looking at. – Alec Teal Nov 16 '13 at 16:16
  • I mean the actual PHP interpreter, not my scripts. I'm compiling for a different CPU architecture (MIPS to be exact), where no precompiled binaries exist. I got PHP to work, but Python might be a different story altogether. – Steven Liekens Nov 16 '13 at 16:19
  • @StevenLiekens it's not, I thought you meant compiling PHP scripts for a second! (PHP does have that C++ thing...) anyway, no, Python is far FAR easier, I was able to compile it for my graphic calculator (Casio FX 98600GII nothing fancy with a colour screen or more than 64kb of memory :P) easily, Python is built to be small and portable, that is why it is a core part of every Linux and BSD distro out there. – Alec Teal Nov 16 '13 at 16:26