CodeIgniter3のエラー・例外処理

  • 11
    Like
  • 0
    Comment
More than 1 year has passed since last update.

CodeIgniter3(以下CI3)でエラー・例外が発生した場合の処理の流れについてです。

対象のバージョンはCodeIgniter3.0.6、PHPは5.6.22で確認しました。

CodeIgniterにおけるエラー・例外発生時の仕組み。

まず前提としてPHPではset_error_handler等を使うことによりエラーハンドラ・例外ハンドラを定義することができます。

マニュアル。
http://php.net/manual/ja/function.set-error-handler.php

そしてCI3においてはCodeIgniter.phpでこれらのハンドラを設定しています。

CodeIgniter.php
set_error_handler('_error_handler');
set_exception_handler('_exception_handler');
register_shutdown_function('_shutdown_handler');

そしてCommon.phpにこれらの関数に定義があります。
基本的にはExceptionクラスをロードして、対応したメソッドを呼び出します。具体的な処理はExceptionクラスにまとめられています。

Common.php
if ( ! function_exists('_error_handler'))
{
    function _error_handler($severity, $message, $filepath, $line)
    {
        $is_error = (((E_ERROR | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity);

        if ($is_error)
        {
            set_status_header(500);
        }

        if (($severity & error_reporting()) !== $severity)
        {
            return;
        }

        $_error =& load_class('Exceptions', 'core');
        $_error->log_exception($severity, $message, $filepath, $line);

        if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
        {
            $_error->show_php_error($severity, $message, $filepath, $line);
        }

        if ($is_error)
        {
            exit(1); // EXIT_ERROR
        }
    }
}

if ( ! function_exists('_exception_handler'))
{
    function _exception_handler($exception)
    {
        $_error =& load_class('Exceptions', 'core');
        $_error->log_exception('error', 'Exception: '.$exception->getMessage(), $exception->getFile(), $exception->getLine());

        if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors')))
        {
            $_error->show_exception($exception);
        }

        exit(1); // EXIT_ERROR
    }
}

if ( ! function_exists('_shutdown_handler'))
{
    function _shutdown_handler()
    {
        $last_error = error_get_last();
        if (isset($last_error) &&
            ($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)))
        {
            _error_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
        }
    }
}

そしてException.phpに実際の処理が書かれています。
ここでビューが読み込まれ、画面にエラーメッセージ表示される形です。

Exception.php
    public function show_exception($exception)
    {
        $templates_path = config_item('error_views_path');
        if (empty($templates_path))
        {
            $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
        }

        $message = $exception->getMessage();
        if (empty($message))
        {
            $message = '(null)';
        }

        if (is_cli())
        {
            $templates_path .= 'cli'.DIRECTORY_SEPARATOR;
        }
        else
        {
            set_status_header(500);
            $templates_path .= 'html'.DIRECTORY_SEPARATOR;
        }

        if (ob_get_level() > $this->ob_level + 1)
        {
            ob_end_flush();
        }

        ob_start();
        include($templates_path.'error_exception.php');
        $buffer = ob_get_contents();
        ob_end_clean();
        echo $buffer;
    }

    public function show_php_error($severity, $message, $filepath, $line)
    {
        $templates_path = config_item('error_views_path');
        if (empty($templates_path))
        {
            $templates_path = VIEWPATH.'errors'.DIRECTORY_SEPARATOR;
        }

        $severity = isset($this->levels[$severity]) ? $this->levels[$severity] : $severity;

        // For safety reasons we don't show the full file path in non-CLI requests
        if ( ! is_cli())
        {
            $filepath = str_replace('\\', '/', $filepath);
            if (FALSE !== strpos($filepath, '/'))
            {
                $x = explode('/', $filepath);
                $filepath = $x[count($x)-2].'/'.end($x);
            }

            $template = 'html'.DIRECTORY_SEPARATOR.'error_php';
        }
        else
        {
            $template = 'cli'.DIRECTORY_SEPARATOR.'error_php';
        }

        if (ob_get_level() > $this->ob_level + 1)
        {
            ob_end_flush();
        }
        ob_start();
        include($templates_path.$template.'.php');
        $buffer = ob_get_contents();
        ob_end_clean();
        echo $buffer;
    }

画面イメージ

Exceptionで呼び出されたメソッドに応じて、実際に表示される画面イメージはこのようになります。

実行したコードはこちら。

controllers/sample.php
<?php
class Sample extends CI_Controller {

    public function normal()
    {
        $this->load->view('view');
    }

    public function exception()
    {
        throw new Exception("例外発生!");
        $this->load->view('view');
    }

    public function php_error()
    {
        throw new Exceptio();  //typo
        $this->load->view('view');
    }

    public function error()
    {
        show_error("エラー表示関数を呼んだ");
        $this->load->view('view');
    }
}
?>
views/view.php
<html>
<head>
<title>タイトル</title>
</head>
<body>
    <h1>正常に表示されました</h1>
</body>
</html>

上記をそれぞれ実行した画面のキャプチャがこちら。
エラーに関しては、PHPのデフォルト時の画面表示とそう大きく変わらないですね。

normal

normal.png

exception

exception.png

php_error

php_error.png

error(参考)

error.png

カスタムするには

デフォルトのException.phpを継承して、対象のメソッドをオーバーライドすることで任意のエラーページに遷移させたり、独自のログ出力など共通処理を追加することが可能です。

これのExceptionsを継承する形です。
http://www.codeigniter.com/user_guide/general/core_classes.html

以上。