ぐらめぬ・ぜぷつぇんのはてダ(2007 to 2011)

2007年~2011年ごろまで はてなダイアリー に書いてた記事を引っ越してきました。

AkActionController#redirectTo()メソッドと、Viewのrendering処理について

あるURLにリダイレクトさせたい場合(Akelosの場合はデフォルトでHTTP 302 でLocationヘッダーを送出)、AkActionController#redirectTo()メソッドを使います。
ところで、redirectTo()を呼んだ後、Viewのrendering処理は発生するのでしょうか?

確かにブラウザ側としては302+Locationで、与えられたURLにジャンプすればおしまいですが、PHP側としては、302を出力したなら以降の処理はskipしたいのが正直なところです。例えばrenderingで、layoutやviewの中に、副作用を及ぼす処理が入っているかもしれませんし。

結論としては、viewやlayoutのレンダリングは行われません。もちろんcontrollerのaction中で、redirectTo()の後ろにPHPスクリプトを続けていれば実行されてしまいますが、レンダリングは実行されません。
例:

class FooController extends AkActionController {
    function bar() {
        ...
        if (...) {
            $this->redirectTo('http://...');
        }
        ... // ここはredirectTo()を呼んだ後、実行されてしまいます。
    }
}

この例であれば、どうせlayoutやviewはレンダリングされないのですから、即returnしてしまった方が安心です。
修正後:

class FooController extends AkActionController {
    function bar() {
        ...
        if (...) {
            $this->redirectTo('http://...');
            return;
        }
        ... // redirectTo()を呼べばreturnされるので、実行されません。
    }
}

裏舞台をのぞいてみましょう。
AkActionController :

<?php
...
function redirectTo($options = array(), $parameters_for_method_reference = null) {
    ...
    $this->Response->redirect($options); // ここで実際に HTTP 302 + Locationヘッダーが送出
    $this->Response->redirected_to = $options;
    $this->performed_redirect = true;
    ...
}

function _hasPerformed() {
    return !empty($this->performed_render) || !empty($this->performed_redirect);
}

function process(...) {
    ...
    $this->performActionWithFilters($this->_action_name);

    if (!$this->_hasPerformed()){
        $this->_enableLayoutOnRender ? $this->renderWithLayout() : $this->renderWithoutLayout();
    }
    $this->Response->outputResults();
}
...

redirectTo()で実際にヘッダーが送出されると、"performed_redirect"フラグがONになります。AkActionControllerの起動I/Fであるprocess()メソッド中では、performActionWithFilters()でアクションメソッドを実行した後、_hasPerformed()メソッドでperformed_redirectフラグをチェックし、OFFの時にrendering処理を呼び出しています。
これにより、controllerのaction中でひとたび(あるいはfilter中で)redirectTo()が呼ばれれば、基本的にはrendering処理は行われない事が保証されます。

では、例えば次のようなコードで無理矢理render()を呼ぶことでレンダリング処理をさせる事はできるか?ですが・・・

$this->redirectTo('http://...');
$this->render('....');

render()メソッドの中はかなり入り組んでて、パラメータを調整し直してはrender****に委譲して、さらにその中を追ってみるとまたrenderWith(out)Layout()を呼んでいたりして非常に混沌としていますが・・・。

単純にrender()を呼び出しただけであれば、render()メソッドの冒頭の以下の分岐ではじかれる筈です。(partialbは別格)

function render($options = null, $status = 200) {
    if(empty($options['partial']) && $this->_hasPerformed()){
        $this->_doubleRenderError(Ak::t("Can only render or redirect once per action"));
        return false;
    }
    ...

という感じで、redirectTo()を呼ぶ後のrendering処理は基本的に、Akelos側で自動的に無効化してくれるものと思って大丈夫そうです。