CodeIgniter3のエラー・例外処理


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](https://qiita-image-store.s3.amazonaws.com/0/62574/557d87ca-2835-383a-357e-fd47490ceba3.png)

### exception
![exception.png](https://qiita-image-store.s3.amazonaws.com/0/62574/c61a9b60-7b27-fa7d-8065-a330eca4f15f.png)


### php_error
![php_error.png](https://qiita-image-store.s3.amazonaws.com/0/62574/1380b04a-c3a9-3292-8d3f-a23eb0db889f.png)


### error(参考)
![error.png](https://qiita-image-store.s3.amazonaws.com/0/62574/177b6bb3-376b-2cab-b425-d7b09d0d296e.png)


## カスタムするには

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

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

以上。