Capturing unexpected termination in PHP / CodeIgni

2019-07-14 06:45发布

I'm using CodeIgniter to host a RESTful API and I'd like to capture any API response that does not return an expected status code. This is probably most easily explained with an example. In general, my code looks like this:

function post_comment()
{
     $comment = $_POST['comment'];
     $result = do_something_with_comment($comment);

     if ($result === true)
     {
         return_json_response(200, 'OK!');
     }
     else
     {
         return_json_response(400, 'Something terrible happened...');
     }
}

Returning either a 200 or 400 is perfectly valid. My problem is: how to capture errors when do_something_with_comment() has a fatal error, or if I leave a print debug inside of do_something_with_comment(). In the former case, I'll never reach return_json_response(). In the latter case, I'll reach it, but the screen debug will corrupt the JSON response.

Is there any way to create a generic wrapper around this to capture any unexpected output or termination?

2条回答
欢心
2楼-- · 2019-07-14 07:23

There's a function register_shutdown_function() within which you can set your own closing handler for every script. Just do what you need there.

BTW: Make your scripts as bullet-proof as you can. Your scripts shouldn't ever have fatal errors, segfaults, or such runtime errors. Any response, even invalid for client is valid in context of request handling. I'd encourage you to take a look at Symfony2 framework or simply at its HttpKernel / HttpFoundation components as they quite nicely wrap this process in a friendly interface.

查看更多
我只想做你的唯一
3楼-- · 2019-07-14 07:27

In general you could:

Use exception /exception handling as much as possible

Register a custom errorhandler that transforms PHP errors into exceptions, for instance put this in top of your config/config.php

function my_error_handler($errno, $errstr, $errfile, $errline)
{
    if (!(error_reporting() & $errno))
    {
        // This error code is not included in error_reporting
        return;
    }
    log_message('error', "$errstr @$errfile::$errline($errno)" );
    throw new ErrorException( $errstr, $errno, 0, $errfile, $errline );
}
set_error_handler("my_error_handler");

Register an uncaught exception handler, put something like this in your config/config.php

function my_exception_handler($exception)
{
    echo '<pre>';
    print_r($exception);
    echo '</pre>';
    header( "HTTP/1.0 500 Internal Server Error" );
}
set_exception_handler("my_exception_handler");

EDIT

Set a termination handler:

function my_fatal_handler()
{
    $errfile = "unknown file";
    $errstr  = "Fatal error";
    $errno   = E_CORE_ERROR;
    $errline = 0;
    $error = error_get_last();
    if ( $error !== NULL )
    {
        echo '<pre>';
        print_r($error);
        echo '</pre>';
        header( "HTTP/1.0 500 Internal Server Error" );
    }
}
register_shutdown_function("my_fatal_handler");

Set a custom assert handler that converts asserts into exceptions, put something like this in your config/config.php:

function my_assert_handler($file, $line, $code)
{
    log_message('debug', "assertion failed @$file::$line($code)" );
    throw new Exception( "assertion failed @$file::$line($code)" );
}
assert_options(ASSERT_ACTIVE,     1);
assert_options(ASSERT_WARNING,    0);
assert_options(ASSERT_BAIL,       0);
assert_options(ASSERT_QUIET_EVAL, 0);
assert_options(ASSERT_CALLBACK, 'my_assert_handler');

Then, and this is your answer, use wrappers like this in your controllers

public function controller_method( )
{
    try
    {
        // normal flow
    }
    catch( Exception $e )
    {
        log_message( 'error', $e->getMessage( ) . ' in ' . $e->getFile() . ':' . $e->getLine() );
        // on error
    }
}

You can tune and customize the whole thing to your likings!

Hope this helps.

EDIT

You will also need to intercept the CI show_error method. Place this in application/core/MY_exceptions.php:

class MY_Exceptions extends CI_Exceptions
{
    function show_error($heading, $message, $template = 'error_general', $status_code = 500)
    {
        log_message( 'debug', print_r( $message, TRUE ) );
        throw new Exception(is_array($message) ? $message[1] : $message, $status_code );
    }
}

And leave in application/config/database.php this setting on FALSE to have database errors converted into exceptions.

$db['default']['db_debug'] = TRUE;

CI has a few (very) weak points, such as exception-handling but this will go a long way correcting that.

查看更多
登录 后发表回答