CakePHPの運用における、ほんの小さな、まぁ問題になることはないだろうと思われるような隙間を埋める。そんな話です。

今後もっといい方法が見つかれば更新します。とりあえずやりたいことだけは上手く行ってるのでメモ。

バージョンは2系 (2.3.0) です。phpバージョンは5.4.4 。サンプルではコールバックにクロージャを用いているので、5.2系だとそこだけ変更する必要があります。

問題

CakePHP の開始直後のプロセスでFatal Error が発生した場合に、それなりの画面表示とエラーの内容の取得をしたい。

CakePHPで組み込みのエラーハンドリングを作りこんでいったとしても、Core/bootstrap.php のロードが完了するまでは使用できない。例えばアプリケーションディレクトリが何らかの理由で壊れてしまって Configure::bootstrap() から app/Config/core.php を取得できない場合など。あと、何だろう。とにかく予測できないような何かってことです。

目標

上記のようなエラーが発生した場合にとりあえず次の2つを満たしたい

  1. 独自のエラー画面を用意しておいてそれを表示する
  2. 何のエラーだったかを取得する

ただし次のことは今回は問わない

  1. エラー画面を別のリソースにするのか、index.php にハードコードするのか
  2. 取得したエラーをどのように扱うのか

解決方法

とりあえずこうやった。

register_shutdown_function()

php の register_shutdown_function() を使いました。この関数の第一引数に、エラーハンドリング用のコールバックを渡します。

具体的には webroot にある index.php の先頭にこれを追加します。

<?php
// webroot/index.php の冒頭部分
ini_set('display_errors', 0); // Configure::write('debug', x); が反映されるまではとりあえずエラー表示をオフにしておく

// register_shutdown_function() でエラー処理を定義
// php5.2系ではクロージャは使えないので、別の方式でコールバックを指定してください
register_shutdown_function(
    function() {
        // CakePHPのエラーハンドリングが完了してたら何もしないため
        // (でももっと上手いやり方はないものか。。。)
        if (defined('INCLUDED_BOOTSTRAP')) {
            return;
        }

        // error_get_last() でエラー内容を取得
        $error = error_get_last();
        //... ここで $error の処理。調べたり、ログに保存したり。。。

        // エラー画面を出力。ここでは同じディレクトリに error.html というファイルを設置しているという仮定です。
        // ここで直接エコーするのもまぁ、確実に出力されるという点では良いかもしれない
        include('error.html');
    }
);

// ...略...

そして、同じ index.php の Cake/bootstrap.php が読み終えてからのところに次のコードを挿入します。
Dispatcher の処理以降は先に設定したエラーハンドリングに処理をさせないためのフラグを立てています。

// Cake/bootstrap.php の読み込みが無事終わったらフラグを立てる
define('INCLUDED_BOOTSTRAP', true);

// ...略...

これで試しにアプリのディレクトリを存在しない箇所に指定してみる。

// ありもしないアプリケーションのディレクトリを指定する
define('APP_DIR', 'falt');

すると、一応上記の目標は満たせてます。

課題

上記のサンプルコードで 定数定義チェックなどをして無駄な処理をしないようにしていますが、これもっとマシなやり方があるだろうとは思う。通常は App::shutdown() が登録される。本当はそれが完了したかどうかさえ分かれば良いのだけれども、その方法はどうしても分からない。

無駄な処理がどこに生じるかというと、このサンプルではCore/bootstrap.php のインクルードの最中に行われた register_shutdown_function 以降で ‘INCLUDED_BOOTSTRAP’ を定義するまでの期間です。ここにおいてのみ CakePHP の処理と自前の処理が両方行われている。

あと気になるのが、この処理ですらエラーを補足できない場合好ましからざる事態(サーバーダウンなど)もあるわけだから、より大きな観点からエラー・例外を補足する仕組みを構築することを考えたほうが良いので、その際にはここで書いたようなコードは無用になるだろうなということ。ただ、今の自分ではこれが精一杯かな。

PHPおよびサーバーに関する本質的な理解があれば、より良いアプローチがあるような気がしてならないですが、ひとまずこれで。