FuelPHPでエラー(Oops!)画面をカスタマイズする方法

FuelPHPでは環境によってエラーが発生した際の画面が異なります。

これらのViewは「COREPATH/views/errors」に定義されているので、
Oops画面をカスタマイズする場合は、
「APPPATH/views/errors/production.php」
というファイルを用意して中身を書き換えればOKです。

※ Fuelのcoreクラスの内容のほとんどがAPPPATH配下に同じ階層と同名のファイルを配置することでcoreに手を入れずに処理の上書きが可能です。

画面の確認

FUEL_ENVがproduction以外の場合

エラートレース画面

おなじみのエラートレース画面です。
開発時に頻繁に表示されるので見慣れているかと思います。
エラー発生個所やスタックトレースを表示してくれるので何処で何が起きたかがその場で確認ができて便利ですね!

FUEL_ENVがproductionの場合

Oops画面

内容はシンプルですね。
core配下のファイルを見ても下記のようにほとんどピュアなHTMLで記述されています。

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>FuelPHP Framework</title>
	<style type="text/css">
		* { margin: 0; padding: 0; }
		body { background-color: #EEE; font-family: sans-serif; font-size: 16px; line-height: 20px; margin: 40px; }
		#wrapper { padding: 30px; background: #fff; color: #333; margin: 0 auto; width: 800px; }
		h1 { color: #000; font-size: 55px; padding: 0 0 25px; line-height: 1em; }
		.intro { font-size: 22px; line-height: 30px; font-family: georgia, serif; color: #555; padding: 29px 0 20px; border-top: 1px solid #CCC; }
		p { margin: 0 0 15px; line-height: 22px;}
	</style>
</head>
<body>
<div id="wrapper">
	<h1>Oops!</h1>
	<p class="intro">An unexpected error has occurred.</p>
</div>
</body>
</html>

Oops!のカスタマイズ

たとえばこの画面を、下記のように変更したい場合。
「Oops!」
 → システムエラーが発生しました。
「An unexpected error has occurred.」
 → お手数をおかけしますが、「XXX-XXXX-XXXX」へお問合せ下さい。

1. 「APPPATH/views/errors/production.php」へファイルをコピー

2. コピーしたファイルの下記の部分を修正

<div id="wrapper">
	<h1>システムエラーが発生しました。</h1>
	<p class="intro">お手数をおかけしますが、「XXX-XXXX-XXXX」へお問合せ下さい。</p>
</div>

3. 画面確認

カスタムエラー画面

h1のスタイルを調整しないとなかなか自己主張が強いですねw
あとは元の画面のUIに合わせてレイアウトを整えるCSSを記述すればカスタマイズ完了です。

カスタマイズのポイント

後述しますが、
production.phpのViewインスタンスが生成された後にexitが呼ばれているため、
Exceptionが発生した段階で後続の処理が呼ばれなくなります。

共通のレイアウトを利用したい場合は、
・ファイル内に共通部品のHTMLを記述する
・ファイル内で共通部品のView::forge()する
といった方法が必要になります。

どちらもメリットデメリットありますが、
・HTMLを直接記述する方法は、共通部分変更時の修正ファイルが増えるがフレームワークに依存する部分がなくなる。
・View::forge()する方法は、共通部品の使い回しができるが「View::forge()」でエラーが発生する可能性が残る。
あたりかなと思います。

とはいえ滅多に表示される画面ではない(はず)なので最低限のHTMLを直接記述する質素なページの方が結果的にメンテが少なくてよいかと思います。

詳細解説

環境変数の切り替え方法

FuelPHPの環境変数は、「public/.htaccess」で指定する方法とApacheで指定する方法の2種類があります。

1. 「public/.htaccess」で指定する

.htaccessの2行目をコメントインして引数に任意の文字列を設定する。

# Multiple Environment config, set this to development, staging or production
# SetEnv FUEL_ENV production

2. Apacheで指定する

<VirtualHost *:80>
    DocumentRoot /usr/local/apache2/htdocs/fuel-sample/public
    ServerName fuel-sample.example.com
    SetEnv FUEL_ENV production
</VirtualHost>

どちらでも指定しない場合は、「app/bootstrap.php」の下記の記述に従って環境変数が「development」に設定されます。

/**
 * Your environment.  Can be set to any of the following:
 *
 * Fuel::DEVELOPMENT
 * Fuel::TEST
 * Fuel::STAGING
 * Fuel::PRODUCTION
 */
\Fuel::$env = (isset($_SERVER['FUEL_ENV']) ? $_SERVER['FUEL_ENV'] : \Fuel::DEVELOPMENT);

// Initialize the framework with the config file.
\Fuel::init('config.php');

個人的にはフレームワークに依存する設定をApacheの設定ファイルに記述するのは好みではないので、
環境毎に.htaccessの書き換えを行っています。
たとえば、事前に「SetEnv FUEL_ENV production」をコメントインした.htaccessを準備しておき、
デプロイのプロセスに.htaccessを上書きするフローを行うなど。

エラー画面の表示切り替え部分

まず、「core/bootstrap.php」内の「set_exception_handler」でエラー時の挙動を制御するhandlerを定義しています。

set_exception_handler(function (\Exception $e)
{
	// reset the autoloader
	\Autoloader::_reset();

	// deal with PHP bugs #42098/#54054
	if ( ! class_exists('Error'))
	{
		include COREPATH.'classes/error.php';
		class_alias('\Fuel\Core\Error', 'Error');
		class_alias('\Fuel\Core\PhpErrorException', 'PhpErrorException');
	}

	return \Error::exception_handler($e);
});

ここでreturn値として「\Error::exception_handler($e);」となっているのがわかりました。

「core/classes/errorhandler.php」からexception_handlerの内容を確認します。

public static function exception_handler(\Exception $e)
{
	if (method_exists($e, 'handle'))
	{
		return $e->handle();
	}

	$severity = ( ! isset(static::$levels[$e->getCode()])) ? $e->getCode() : static::$levels[$e->getCode()];
	logger(static::$loglevel, $severity.' - '.$e->getMessage().' in '.$e->getFile().' on line '.$e->getLine());

	if (\Fuel::$env != \Fuel::PRODUCTION)
	{
		static::show_php_error($e);
	}
	else
	{
		static::show_production_error($e);
	}
}

メソッド内で、環境変数の値を読み取って、
・環境変数がproduction以外なら、「static::show_php_error($error);」
・環境変数がproductionなら、「static::show_production_error($error);」
と表示の切り分けをしています。

最終的に、show_production_errorメソッド内でproduction用のViewのインスタンスを生成しているようです。

/**
 * Shows the errors/production view and exits.  This only gets
 * called when an error occurs in production mode.
 *
 * @return  void
 */
public static function show_production_error(\Exception $e)
{
	// when we're on CLI, always show the php error
	if (\Fuel::$is_cli)
	{
		return static::show_php_error($e);
	}

	if ( ! headers_sent())
	{
		$protocol = \Input::server('SERVER_PROTOCOL') ? \Input::server('SERVER_PROTOCOL') : 'HTTP/1.1';
		header($protocol.' 500 Internal Server Error');
	}
	exit(\View::forge('errors'.DS.'production'));
}

exitしている点も注目ですね。
これによってViewをレンダリングした後にコントローラーで後続の処理が行われないため、production.phpの内容がピュアなHTMLで記述されているのかなと。

参考サイト

[FuelPHP]production時のエラー

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る