fuelphpでエラー処理を実装する方法

はじめに

意外とおろそかになってしまうこともあるのですが、サービスの規模によってはちゃんとしておかないと後々問題になるのでできる範囲で僕がやっていることをまとめておきます。

とりあえず最低限やっておきたいこと
sqlのエラーなど致命的なエラーが発生した場合はそれ以降処理が続行されないような実装
・致命的なエラーが発生した場合はアラートメールなどを送信するようにする
・致命的なエラーが発生した場合はちゃんとログに詳細が残るようにしてあとから調査できるようにする

当たり前の事なのですが、これくらいを実装しておけばとりあえずは大丈夫かなぁと今までの経験上思ってます

entryポイントでエラー処理を拾えるような修正

「HttpNotFoundException」は拾えるように最初から書いてあるのですが「HttpServerErrorException」は拾えるような実装になっていないので下記のように追記します。

public/index.php

try 
{
  ・・・
}
catch (HttpNotFoundException $e)
{
  ・・・
}
// ★ここから追加(HttpNotFoundExceptionのブロックをそのままコピーして404の部分を500に変えてるだけ)
catch (HttpServerErrorException $e)
{
	\Request::reset_request(true);

	$route = array_key_exists('_500_', Router::$routes) ? Router::$routes['_500_']->translation : Config::get('routes._500_');

	if($route instanceof Closure)
	{
		$response = $route();

		if( ! $response instanceof Response)
		{
			$response = Response::forge($response);
		}
	}
	elseif ($route)
	{
		$response = Request::forge($route, false)->execute(array($e))->response();
	}
	else
	{
		throw $e;
	}
}
・・・

こんな感じで「HttpServerErrorException」のブロックを追記しています。
>$response = Request::forge($route, false)->execute(array($e))->response();
ちょっと自分で追加した場所としては、executeメソッドへの引数で「array($e)」を渡すように修正しました。

fuel/app/config/route.phpを編集

internal serverエラーが発生した際にfuel/app/controller/error.phpに制御が移るように設定する。

<?php
return array(
	'_root_'  => 'index',  // The default route
	'_404_'   => 'error/404',    // The main 404 route
	'_500_'   => 'error/500',    // The main 500 route
);

エラーレポート送信用のメール設定

vi fuel/app/config/define.php

<?php
return array(
    "error" => array(
        "mail_to" => "hoge@example.com",
        "mail_from" => "piyo@example.com",
        "mail_title" => "[サービス名]システムエラー発生",
    ),
);

この定義ファイルは自分ルールです。作り方は各自考えてください
※いちおうこちらにまとめてありますのでよければみてみてください

エラー用のcontrollerを作成する

vi fuel/app/classes/controller/error.php

<?php
class Controller_error extends Controller
{
    public function action_500($e)
    {
        // エラー内容をメール送信
        $email = Email::forge('jis');
        $email->from(Config::get('define.error.mail_from'), '');
        $email->subject(Config::get('define.error.mail_title'));
        $email->body(print_r($e,1));
        $email->to(Config::get('define.error.mail_to'));
        $email->send();

        // エラー用のviewを表示
    }
}

これでエラーが発生したときにメールが送信されるので、早い段階でバグ等を発見できます。

controllerまたはmodel等でエラーが発生した場合を考慮した実装

class Controller_Example extends Controller
{
    public function action_index()
    {
        $id = Input::get("id");

        // 指定されたidでユーザ情報を取得
        // なければパラメータの改ざん等なので本来存在しないページという解釈で404をかえす
        $users = Model_Users::get($id);
        if (empty($users)) {
            throw new HttpNotFoundException;
        }

        // このページへの閲覧履歴などのログを登録
        if (!Model_Log::insert(array(
          "user_id" => $id,
          "created" => date("Y-m-d H:i:s"),
        ))) {
           // validate済みで正当なデータでのデータベースへの登録にも関わらずのエラー発生なので致命的なエラーと解釈
           // サーバエラーの送信で上記で実装したアラートメールも送信される
           Log::error("Error | Model_Log insert error | " $id)
           throw new HttpServerErrorException;
        }
    }
}

こんなかんじで各controllerで実装しておきます。
実際のところsqlのエラーがおきる場合は大抵致命的なエラーなので、僕の場合はsqlを実行する箇所(Model側)でエラーがあったら「HttpServerErrorException」例外を投げて実行したsqlsqlエラーの内容をログに出力するようにしています。

さいごに

見た目の機能を作ることに集中してしまいがちかもしれませんが、エラー処理は大事な実装だと思っています。
まだまだエラー処理としては不十分なところもあるかもしれませんが、とりあえずこれくらい実装しておけばある程度大事になる前にバグなどを発見できるのではないかと自分的には考えてます

細かい話でしたが、以上でした