2017-10-07

Integration of PHP Error Handling


Integration of PHP Error Handling

Last week, on how to integrate PHP 5 error handling, I was asked to explain at multiple development projects.

As used herein, "Integration of PHP Error Handling" means, for example, the following development requirements.
# Normal Error
# Exception
# PHP Core Error
↓
We want to integrate and manage error handling such as logs and mails when these occur.
In order to realize the above, you need to understand the habit and mechanism of PHP error.
Here, we describe these realization methods.


Notice

# In this article, we will focus on these implementation methods.
# In this article, we do not mention general concepts such as "difference between error and exception".
# In this article, we describe it only for suspend processing.
# In this article, we do not mention restoration processing, trigger, processing level etc.
# This article applies only to PHP 5. It does not apply to PHP 7.


Flow

One way to realize this "PHP error integration processing" is the flow of error processing as follows.
Normal Error Processing  ←  Normal Error
↓
Exception Processing  ←  Exception
↓
Shutdown Processing  ←  PHP Core Error


Normal Error Handling

First, take handling authority of PHP Normal Error or User-Defined Error from PHP.
In PHP, the following functions are prepared.
mixed set_error_handler(callable $error_handler [, int $error_types = E_ALL | E_STRICT])
In order to take processing authority from PHP, create a callback function and register it in this function.
(Be sure to register it as early as possible of a series of request processing.)
Within the callback function, take the normal error and rethrow as an exception.
In short, the goal is to take the normal error handling authority from PHP, and pass it to the exception.
However, in this callback function, it is one point that PHP core error can not be taken.
public function handleError()
{
    //* Error Handler Definition
    function handleError($_number, $_message, $_file, $_line, $_contexts)
    {
         //* Not Includ Error Reporting
         if (! (error_reporting() & $_number)) {
             return;
         }
        //* to ErrorException
        throw new ErrorException($_message, 500, $_number, $_file, $_line);
    }

    //* Error Handler Set
    set_error_handler('handleError');
}


Exception Handling

Next, take exception processing authority which was not caught from PHP.
In PHP, the following functions are prepared.
callable set_exception_handler(callable $exception_handler)
In order to take processing authority from PHP, create a callback function and register it in this function.
(Be sure to register it as early as possible of a series of request processing.)
As a result, all normal errors and all uncaught exceptions are aggregated in one place.
But this is not enough.
We have not taken PHP Core Error yet.
Therefore, processing logic is not placed here.
public function handleException()
{
    //* Exception Handler Definition
    function handleException($_e)
    {
        //* Exception Context
         $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'] = $_e;

         //* Error Processing to Shutdown Logic
         exit;
    }

    //* Exception Handler Set
    set_exception_handler('handleException');
}


PHP Core Error Handling

In PHP 5, set_error_handler () can not take the processing authority of core error issued by PHP.
PHP 5 does not throw an exception of core error.
Therefore, in order to capture the PHP core error, the following function is used.
void register_shutdown_function(callable $callback [, mixed $parameter [, mixed $... ]])
This function makes it possible to register a callback function to be executed when script processing is completed or when exit () is called.
Utilizing this property, it is possible to integrate all processing such as error, exception, PHP core error, etc. as a result.
public function handleShutdown($_error_mails = array())
{
    //* Shutdown Function Definition
    function handleShutdown($_error_numbers = array(), $_error_mails = array(), $_http_status_codes = array())
    {
        //* Exception or Error
        if (! empty($_SERVER['X_EXCEPTION_HANDLER_CONTEXT'])) {
            $e = $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'];
            unset($_SERVER['X_EXCEPTION_HANDLER_CONTEXT']);
            $message = $e->__toString();
            $code = $e->getCode();
        } else {
            $e = error_get_last();
            //* Normal Exit
            if (empty($e)) {
                return;
            }

            //* Core Error
            $message = $_error_numbers[$e['type']] . ': ' . $e['message'] . ' in ' . $e['file'] . ' on line ' . $e['line'];
            $code = 500;
        }

        //* Error Logging
        error_log($message, 4);

        //* Error Mail
        $cmd = 'echo "' . $message . '" | mail -S "smtp=smtp://' . $_error_mails['host'] . '" -r "' . $_error_mails['from'] . '" -s "' . $_error_mails['subject'] . '" ' . $_error_mails['to'];
        $outputs = array();
        $status = null;
        $last_line = exec($cmd, $outputs, $status);

        //* HTTP Status Code
        header('HTTP/1.1 ' . $code . ' ' . $_http_status_codes[$code]);

        //* Shutdown
        exit($code . ' ' . $_http_status_codes[$code]);
    }

    //* Shutdown Function Registration
    $error_numbers = self::$error_numbers;
    $http_status_codes = self::$http_status_codes;
    register_shutdown_function('handleShutdown', $error_numbers, $_error_mails, $http_status_codes);
}


to Class Library

When these are made into a general purpose class library, it becomes as follows.
Logging, e-mail, exception context delivery, etc. should be changed according to circumstances.
class AppE
{

    public static $error_numbers = array(
        1 => 'Fatal',
        2 => 'Warning',
        4 => 'Parse Error',
        8 => 'Notice',
        16 => 'Core Fatal',
        32 => 'Core Warning',
        64 => 'Compile Error',
        128 => 'Compile Warning',
        256 => 'Ex Error',
        512 => 'Ex Warning',
        1024 => 'Ex Notice',
        2048 => 'Strict Error',
        4096 => 'Recoverable Error',
        8192 => 'Deprecated',
        16384 => 'Ex Deprecated',
        32767 => 'All',
    );

    //* HTTP Status Code
    public static $http_status_codes = array(
        'default' => 200,
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status',
        226 => 'IM Used',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
       404 => 'Not Found',
       405 => 'Method Not Allowed',
       406 => 'Not Acceptable',
       407 => 'Proxy Authentication Required',
       408 => 'Request Timeout',
       409 => 'Conflict',
       410 => 'Gone',
       411 => 'Length Required',
       412 => 'Precondition Failed',
       413 => 'Request Entity Too Large',
       414 => 'Request-URI Too Long',
       415 => 'Unsupported Media Type',
       416 => 'Requested Range Not Satisfiable',
       417 => 'Expectation Failed',
       418 => "I'm a teapot",
       422 => 'Unprocessable Entity',
       423 => 'Locked',
       424 => 'Failed Dependency',
       426 => 'Upgrade Required',
       500 => 'Internal Server Error',
       501 => 'Not Implemented',
       502 => 'Bad Gateway',
       503 => 'Service Unavailable',
       504 => 'Gateway Timeout',
       505 => 'HTTP Version Not Supported',
       506 => 'Variant Also Negotiates',
       507 => 'Insufficient Storage',
       509 => 'Bandwidth Limit Exceeded',
       510 => 'Not Extended',
    );


    public function __construct()
    {}


    public function handleError()
    {
        //* Error Handler Definition
        function handleError($_number, $_message, $_file, $_line, $_contexts)
        {
             //* Not Includ Error Reporting
             if (! (error_reporting() & $_number)) {
                 return;
             }
            //* to ErrorException
            throw new ErrorException($_message, 500, $_number, $_file, $_line);
        }

        //* Error Handler Set
        set_error_handler('handleError');
    }


    public function handleException()
    {
        //* Exception Handler Definition
        function handleException($_e)
        {
            //* Exception Context
             $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'] = $_e;

             //* Error Processing to Shutdown Logic
             exit;
        }

        //* Exception Handler Set
        set_exception_handler('handleException');
    }


    public function handleShutdown($_error_mails = array())
    {
        //* Shutdown Function Definition
        function handleShutdown($_error_numbers = array(), $_error_mails = array(), $_http_status_codes = array())
        {
            //* Exception or Error
            if (! empty($_SERVER['X_EXCEPTION_HANDLER_CONTEXT'])) {
                $e = $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'];
                unset($_SERVER['X_EXCEPTION_HANDLER_CONTEXT']);
                $message = $e->__toString();
                $code = $e->getCode();
            } else {
                $e = error_get_last();
                //* Normal Exit
                if (empty($e)) {
                    return;
                }

                //* Core Error
                $message = $_error_numbers[$e['type']] . ': ' . $e['message'] . ' in ' . $e['file'] . ' on line ' . $e['line'];
                $code = 500;
            }

            //* Error Logging
            error_log($message, 4);

            //* Error Mail
            $cmd = 'echo "' . $message . '" | mail -S "smtp=smtp://' . $_error_mails['host'] . '" -r "' . $_error_mails['from'] . '" -s "' . $_error_mails['subject'] . '" ' . $_error_mails['to'];
            $outputs = array();
            $status = null;
            $last_line = exec($cmd, $outputs, $status);

            //* HTTP Status Code
            header('HTTP/1.1 ' . $code . ' ' . $_http_status_codes[$code]);

            //* Shutdown
            exit($code . ' ' . $_http_status_codes[$code]);
        }

        //* Shutdown Function Registration
        $error_numbers = self::$error_numbers;
        $http_status_codes = self::$http_status_codes;
        register_shutdown_function('handleShutdown', $error_numbers, $_error_mails, $http_status_codes);
    }

}


Afterword

Many PHP frameworks provide interfaces for extended error handlers and extended exception handlers, but their contents merely implement similar things.
Recently, when the story of this layer came up, the number of engineers who understood these technologies has become really few.
This is what has been said from around the time the non-Java web application framework came out.
I realize that it has become a reality at many development sites.
A lot of recent engineers use the framework, that is not a means, it has become a purpose.
It can be seen from the fact that the number of engineers who do not know the basis of Web application development has increased.
In this way, the factor that the number of tool engineers has increased.
One of them is the increase in speed development method like Silicon Valley.
It is by no means negative.
In short, if we categorize the category of development engineers more subdivided, the misrecognition of mutual recognition will probably decrease.