Translate

2013年10月24日木曜日

Controller_Restを継承したabstractクラスのbefore()メソッド内部でレスポンスを返す

FuelPHPにおいてRestfulな処理を実装する時には、まずController_Restを継承したController_Hogeなんかを作って、そこにメソッドをグリグリと記述していくのだけれども、開発が進むと「ユーザの認証処理もろもろの初期化とか全部親クラスのbefore()とかafter()でやらせたいなー」となる事が多々あると思います。というか絶対そうなります。ところが、プロジェクトによってはCoreクラスの編集が禁止されていたりするわけで、そんな場合にはConroller_Restに直接の変更を加えることは当然できません。で、回避方法としてはController_Restを継承したabstractクラスController_Myrestクラスなどを作り、そこにbefore()やらafter()を実装した上で、実際にリクエストで呼び出されるクラスではこのクラスを継承する感じになると思います。

abstract class Controller_Myrest extends \Controller_Rest
{
    protected $user;
    publc function before()
    {
        parent::before();
        $this->user = Model_User::find($this->param('user_id'));
    }
}

で、このController_Myrest::before()の中で、例えば上記のようにUser_Modelを取得して$this->userに格納するという事を毎回行うとして、仮にuser_idが違っていたり不正だったりした時には下記のようなエラーハンドリングが必要となるわけです。

    publc function before()
    {
        parent::before();
        $this->user = Model_User::find($this->param('user_id'));
        if(!($this->user instanceof Model_User))
        {
            //エラー処理
        }
    }

※エラーハンドリングは適当なのでそのまま使っちゃダメです。

処理を中断させるにはExceptionを発生させたりexit()したりと色々ありますが、これらを使うとステータスコードに問答無用で500がセットされてしまいます。せっかくのRestコントローラなので、ちゃんとレスポンスコードを返してあげたいところです。が、before()内部でreturnしてもその先に進んでしまい、実際に呼び出された子クラスのメソッドが実行されてしまうので、ここでは別の内部URLにリダイレクトさせ、そちらで正式なレスポンスを返すことにしようと思います。ここでは仮に'/default/unauthorized'というURLにリダイレクトさせ、そこで強制的に404を返す、という形をとります。

まず、abstractクラスのbefore()。

    publc function before()
    {
        parent::before();
        $this->user = Model_User::find($this->param('user_id'));
        if(!($this->user instanceof Model_User))
        {
            //エラー処理
            \Response::redirect('/default/unauthorized');
        }
    }

それから、/default/unauthorizedの実装。

class Controller_Default extends Controller_Rest
{
    public function action_unauthorized()
    {
        return \Response::forge(NULL, 404);
    }
}

これで無事に404が返されるようになります。キモはController_DefaultではController_Myrestを継承しないこと。でないとリダイレクトがループします。本当は直接に処理を中断させて、かつステータスコードも返せるようないい方法があるのかも知れないけど、とりあえずこれで動いたのでよしとします。

2013/11/06 追記

before()内で以下のように書いて、例外を返せばいいことに気がついた。とりあえず404を返すが、必要があれば独自の*Exceptionクラスを実装して、そっちを呼べばよし。

throw new HttpNotFoundException;