21

I have a web service site that is restful enabled, so other websites/ajax script can make a call to the site to get/set data. However, whenever the web service site somehow returns a PHP fatal error, the HTTP status that is returned is 200 instead of 500. Is there a way to fix it so that whenever a fatal error occurs, returns 500 instead of 200? Or, if it is not possible, how can I change my client to recognize the fatal error returned by the webservice?

1

6 Answers 6

29

PHP does send a HTTP 500 on a Fatal error.
Let me cite a comment in this PHP bug-report (a xdebug bug prevents this behavior):

It does happen. It requires that:

1) display_errors = off
2) No headers have been sent yet by the time the error happens
3) Status is 200 at that time.

<?
    ini_set('display_errors', 0); 
    foobar();
    // This will return a http 500
?>

You can catch fatal errors with a shutdown function and error_get_last:

register_shutdown_function(function() {
    $lastError = error_get_last();

    if (!empty($lastError) && $lastError['type'] == E_ERROR) {
        header('Status: 500 Internal Server Error');
        header('HTTP/1.0 500 Internal Server Error');
    }
});
1
  • 2
    I would not that if you have xdebug enabled, it will override this with it's own error handler, which seems to also return an HTTP 200.
    – thaddeusmt
    Commented Sep 29, 2015 at 21:25
14

One possible way would be to set the default response to 500, if everything executes successfully set the response to 200:

http_response_code(500);
render_my_page();
http_response_code(200);
2
  • 7
    Throw in a large page buffer to prevent the infamous error about output already started and that should do it. Commented Feb 25, 2010 at 4:28
  • 1
    That, or make sure that you put <?php header('500 Internal Server Error'); ?> at the beginning of your script rather than before the tricky part. Commented Jun 6, 2010 at 12:46
3

Create a custom error handler (set_error_handler) and call header("HTTP/1.0 500 Service not available");.

Edit:

Per the first comment to my answer, you cannot trap true fatal errors. However, PHP will default to setting a 500 error code on fatal errors if output buffering is disabled and errors are not displayed to the screen.

<?php
        $x = y();
?>

The above code will return a 500 error code if nothing has been sent to the screen.

So if you want this kind of error to set the proper code, do your own buffering:

<?php
        $buffer = 'blah';
        $x = y();  // will trigger 500 error
        echo $buffer;
?>
4
  • 9
    Custom error handlers can't catch fatal errors (e.g. call to method on a non object; time limit/memory limit exhausted) or parse errors. From the docs: The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING php.net/set_error_handler Commented Feb 25, 2010 at 4:27
  • Yes, that's worth mentioning. I wasn't assuming he meant fatal literally, as some people use it to mean any type of error that cannot be recovered from.
    – Matthew
    Commented Feb 25, 2010 at 4:44
  • Then you'll need to disable ALL output buffering and refrain from displaying any data until the end of the script (as my edited answer shows) or default to a 500 code and adjust to 200 on success (as others have suggested).
    – Matthew
    Commented Feb 25, 2010 at 23:40
  • You can catch them with register_shutdown_function, but you'll be at the end of execution.
    – John Hunt
    Commented Mar 22, 2013 at 16:37
3
function fatal_error_handler() {

  if (@is_array($e = @error_get_last())) {
    if (isset($e['type']) && $e['type']==1) {
      header("Status: 500 Internal Server Error");
      header("HTTP/1.0 500 Internal Server Error");
      }
    }

}
register_shutdown_function('fatal_error_handler');
2

I developed a way to catch all error types in PHP (almost all)! I have no sure about E_CORE_ERROR ( I think it will not work for that error)! But, for other fatal errors (E_ERROR, E_PARSE, E_COMPILE...) works fine using only one error handler function! There's my solution:

Put this following code on your main file (index.php):

<?php

define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR | 
        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);

define('ENV', 'dev');

//Custom error handling vars
define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);
define('LOG_ERRORS', TRUE);

register_shutdown_function('shut');

set_error_handler('handler');

//Function to catch no user error handler function errors...
function shut(){

    $error = error_get_last();

    if($error && ($error['type'] & E_FATAL)){
        handler($error['type'], $error['message'], $error['file'], $error['line']);
    }

}

function handler( $errno, $errstr, $errfile, $errline ) {

    switch ($errno){

        case E_ERROR: // 1 //
            $typestr = 'E_ERROR'; break;
        case E_WARNING: // 2 //
            $typestr = 'E_WARNING'; break;
        case E_PARSE: // 4 //
            $typestr = 'E_PARSE'; break;
        case E_NOTICE: // 8 //
            $typestr = 'E_NOTICE'; break;
        case E_CORE_ERROR: // 16 //
            $typestr = 'E_CORE_ERROR'; break;
        case E_CORE_WARNING: // 32 //
            $typestr = 'E_CORE_WARNING'; break;
        case E_COMPILE_ERROR: // 64 //
            $typestr = 'E_COMPILE_ERROR'; break;
        case E_CORE_WARNING: // 128 //
            $typestr = 'E_COMPILE_WARNING'; break;
        case E_USER_ERROR: // 256 //
            $typestr = 'E_USER_ERROR'; break;
        case E_USER_WARNING: // 512 //
            $typestr = 'E_USER_WARNING'; break;
        case E_USER_NOTICE: // 1024 //
            $typestr = 'E_USER_NOTICE'; break;
        case E_STRICT: // 2048 //
            $typestr = 'E_STRICT'; break;
        case E_RECOVERABLE_ERROR: // 4096 //
            $typestr = 'E_RECOVERABLE_ERROR'; break;
        case E_DEPRECATED: // 8192 //
            $typestr = 'E_DEPRECATED'; break;
        case E_USER_DEPRECATED: // 16384 //
            $typestr = 'E_USER_DEPRECATED'; break;

    }

    $message = '<b>'.$typestr.': </b>'.$errstr.' in <b>'.$errfile.'</b> on line <b>'.$errline.'</b><br/>';

    if(($errno & E_FATAL) && ENV === 'production'){

        header('Location: 500.html');
        header('Status: 500 Internal Server Error');

    }

    if(!($errno & ERROR_REPORTING))
        return;

    if(DISPLAY_ERRORS)
        printf('%s', $message);

    //Logging error on php file error log...
    if(LOG_ERRORS)
        error_log(strip_tags($message), 0);

}

ob_start();

@include 'content.php';

ob_end_flush();

?>

I hope this helps many people!

1
  • It's the first time I see a "Status:" HTTP header.
    – wadim
    Commented Mar 28, 2014 at 13:54
-4

I would think that you'd want to catch and fix all Fatal errors before deploying the application, since many of them are code errors, missing includes, non-existent objects, which are all development errors. Even out-of-memory errors can be minimized against with coding techniques that are memory frugal (one of the biggest wins is using unbuffered queries and processing the data and emitting output as the resultset is returned, instead of throwing around huge arrays).

3
  • 1
    yea, I should, but it is kinda annoying during development
    – Jeffrey04
    Commented Feb 25, 2010 at 8:17
  • 1
    Sometimes fatal errors are beyond your control. APC gets in a bad state sometimes, and throws errors until you restart apache. Then, magically, without a single code change, the fatal errors go away. Commented Sep 15, 2011 at 21:42
  • 2
    This answer essentially says, "Just write perfect code and make sure it's perfect and anything that your code depends on is also perfect." Ok, I'll get right on that.
    – Ellesedil
    Commented Sep 17, 2015 at 19:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.