Curry
PHP Framework

エラー制御

例外発生時の処理

Curryでは、発生した例外のインスタンスを捕捉し、規定の処理を行う仕組みが組み込まれています。
例外は実装者によって意図的にcatchされない限り、curryによってcatchされます。
そして規定されたエラー処理を行います。

URLルーティング失敗時の例外

URLによって指定されたコントローラーに対応するコントローラークラス、またはアクションに対するアクションメソッドが存在しない、またはアクセス出来ない場合、専用のビューテンプレートが出力されます。
専用のビューテンプレートは、ビューテンプレートディレクトリ内の、error/not_found.php(tpl)です。
存在しないURLへアクセス時に表示したいコンテンツをカスタマイズしたい場合は、このテンプレートを編集することで可能です。

通常の例外

ここで言う通常の例外とは、URLルーティング失敗の例外を除く全ての例外を指します。
この場合、Curryはビューテンプレートディレクトリ内の、error/error.php(tpl)のエラーテンプレートを出力します。このテンプレートに対しては例外の詳細情報がテンプレート変数として出力され、例外の内容を画面に表示することができます。

ただし一般的に例外の情報をサイト閲覧者に見せるのはセキュリティ上も良いことではありません。開発時はエラーテンプレートに例外の情報を出力するようにすると便利ですが、サイト運用時は変数の出力を行わず、テンプレートには固定のエラーメッセージ、例えば「システムエラーが発生しました。」などを表示するようにするとよいでしょう。

app/views/templates/error/error.php
<?php echo $file; ?>(<?php echo $line; ?>)
<br />
<?php echo $message; ?>
<br /><br />
<?php echo nl2br($trace); ?>

これは標準のエラーテンプレートです。このままだと例外時にサイト閲覧者に例外情報が見えてしまうので、サイト運用時は例えば以下のようにします。

app/views/templates/error/error.php
システムエラーが発生しました。

これでどのような例外が発生しても閲覧者には固定のメッセージしか表示されません。

エラーテンプレートを任意に指定

例外時に表示するテンプレートはerror/error.php(tpl)が基本ですが、これを任意に指定することも可能です。
ただし、エラーテンプレートの格納ディレクトリはerrorで固定です。ファイル名のみ指定が可能です。

例えば開発環境用と公開環境用で例外発生時のエラー表示方法を変えたいという要望はありがちです。
開発環境ではエラーの詳細がすぐわかるような形に、公開環境ではお決まりの「システムエラーが発生しました」的なエラーを表示するのみに留めるなど。
そのような要望に答えるにはエラーテンプレートを分け、何かの判定基準によって開発環境か公開環境かを判定し、エラーテンプレートを切り分ける、という方法が考えられます。

指定するにはViewAbstractクラスに存在するsetErrorTemplateメソッドを利用します。
指定するのはファイル名です。拡張子の指定は不要です。

app/views/templates/error/error.tpl
<p>システムエラーが発生しました。</p>
app/views/templates/error/debug.tpl
<?php echo $file; ?>(<?php echo $line; ?>)
<br />
<?php echo $message; ?>
<br /><br />
<?php echo nl2br($trace); ?>
app/controllers/plugin.php
class Plugin extends PluginAbstract
{
    public function preProcess()
    {
        $ini = Ini::load('system.ini', 'system');
        if ($ini['debug_mode'] == 1) {
            $this->view->setErrorTemplate('debug');
        }
    }
}

例えば上の例は公開環境用のエラーテンプレートとしてerror.tpl、
開発環境用のエラーテンプレートとしてdebug.tplを用意し、
system.iniというiniファイルを作って開発環境か公開環境かを判断するフラグを設定し、
それを参照してどちらのエラーテンプレートを利用するかを切り分ける処理を行なっています。

例外処理のカスタマイズ

例外発生時の処理は、ErrorHandlerStandardクラスに規定されています。
任意の処理を定義したい場合、このErrorHandlerStandardを継承した"ErrorHandler"クラスを作成し、コントローラーディレクトリに配置します。

app/controllers/error_handler.php
class ErrorHandler extends ErrorHandlerStandard
{
    public function error()
    {

    }

    public function notFound()
    {

    }
}

例外の種類ごとにメソッドを作成します。
例えばURLルーティング失敗時には、NotFoundExceptionという例外がthrowされます。これはExceptionクラスを継承したNotFoundExceptionクラスのインスタンスですが、この場合、ErrorHandlerのnotFoundメソッドが呼び出されます。

つまり、例外のクラス名から"Exception"を除外したものをメソッドの名前規則に変換した名前のメソッドが呼び出されるわけです。

例えばExceptionクラスを継承したFileNotExistsExceptionという例外クラスを定義したとしましょう。この場合、ErroHandlerでfileNotExistsというメソッドを定義すれば、FileNotExistsExceptionがthrowされた場合に呼び出される事になります。

ちなみに、例外のクラス名に対応するメソッドが存在しない場合、errorメソッドが呼び出されます。errorメソッドはデフォルトの例外処理という位置付けです。

実際にErrorHandlerの処理を定義してみましょう。

app/controllers/error_handler.php
class ErrorHandler extends ErrorHandlerStandard
{
    public function error()
    {
        die('システムエラーが発生しました');
    }

    public function notFound()
    {
        $this->redirect('/');
    }
}

この場合、存在しないコントローラー・アクションへのリクエスト時は強制的にトップページを表示、それ以外は「システムエラーが発生しました。」と表示してスクリプトの強制ストップという処理になります。

例外情報のログ出力

例外発生時、Loggerクラスを利用して例外の詳細情報をログファイルに出力することができます。
デフォルトでlogs/system.logに出力されるようになっています。
出力したくない場合は、Initializerなどで無効に設定します。

htdocs/index.php
class Initializer extends InitializerStandard
{
    public function initialize()
    {
        ErrorHandlerStandard::enableLogging(false);
    }
}

例外のログだけ別ログとして出力する

Loggerクラスの仕組みとして用途によって出力するファイルを分けることができるようになっています。これを利用して例外だけ別ログとすることが可能です。

class Initializer extends InitializerStandard
{
    public function initialize()
    {
        // 例外ログ出力用
        Logger::setLogName('exception.log', 'exp');
        ErrorHandlerStandard::enableLogging('exp');
    }
}

LoggerクラスのsetLogNameメソッドによって出力先のログファイル名を指定しますが、第2引数でそのログファイルに対して任意のキーを設定することで、複数のログファイルを同時に利用できる仕組みになっています。
これを利用し、ここではexcept_logというログファイルに対して"exp"というキーを割り当てました。そしてErrorHandlerStandardのenableLoggingメソッドにこのキーを指定することで、対象のログファイルへ出力が行われるようになります。

app/controllers/index_controller.php
class IndexController extends Controller
{
    public function index()
    {
        Logger::info('通常のログです。');

        throw new Exception('例外ログです。');
    }
}
logs/log
[2011-08-01 01:01:01] [INFO ] 通常のログです。
logs/except_log
[2011-08-01 01:01:01] [ERROR] FILE:~/site/app/controllers/index_controller.php LINE:9 例外ログです。
#0 ~/curry/core/dispatcher.php(210): IndexController->index()
#1 ~/site/htdocs/index.php(25): Dispatcher->dispatch()
#2 {main}