{ 2011.12.14 }

CakePHP2.0で現状の問題点などまとめました。

    はてなブックマーク - CakePHP2.0で現状の問題点などまとめました。
    このエントリーをはてなブックマークに追加

    そろそろ誕生日の萩原です。誕生日プレゼントください。去年も似たようなことを言ったような気がするのは気のせいです。

    最近、CakePHP2を色々触ってまして、色々困ったことがあったので、まとめておこうかなと思います。(CakePHP2.0.4現在)
    (下でCakePHP2.xなんか知りませんと言われてますが・・・)

    AppControllerをapp/Controller以外に置く場合の注意

    CakePHP2からは皆さんご存知の通り、AppController及び、AppModelが、app直下に設置が出来なくなりました。

    CakePHP2ではAppControllerはapp/Controller以下に設置をすること、と言うことになっています。

    しかし、個人的には出来れば、直接アクセスするコントローラとはAppControllerは分けたいところです。

    そこで、AppControllerをapp/Libに設置をしようと考えまして設置を行っていました。

    app/Lib以下にAppControllerを設置後は、AppControllerを継承するコントローラにはApp::usesの記述が必要になります。

    <?php
     
    App::uses(&#039;AppController&#039;, &#039;Lib&#039;);
    
    class PostsController extends AppController {
     
    }

    しかし、作業をしていく内に、たまにAppController内のアクションが読めないというエラーが出ることがありました。(debug2でも)

    元凶はapp/tmp/cache/persistent/cake_core_file_mapのキャッシュ(こいつがなかなかの曲者です・・・)だと言うことはすぐにわかったのですが、どうにも再現性も取れず、対策がなかなかつかめない状態でした。(1ヶ月くらい悩んでいました・・・)

    結局のところ現象としては

    (1)404ページ(missing controllerなど)にアクセス
    (2)CakeErrorControllerを見に行く
    (3)CakeErrorControllerがapp/Controller以下にセットしていない場合は、CakeコアのCakeErrorControllerを見に行く
    (4)当然、コアのコントローラにApp::usesの記述があるはずもなく、継承するAppControllerの場所としてApp/Controller以下を見に行く
    (5)ないので、CakeコアのAppControllerにアクセスしに行く
    (6)さらに、キャッシュにAppControllerの位置はCakeコアにある、と記述
    (7)その後、通常のシステムのページにアクセスしても、AppControllerとして、CakeコアのAppControllerを見に行く
    (8)App/Lib以下に設置したAppConttroller内のアクションが呼ばれない

    ということが起こっていました。

    解決策としては

    ①AppControllerを素直にApp/Controller内に設置する
    ②CakeErrorControllerをApp/Controller内にコピーしてApp::usesをセットする

    ①はまずまず問題ないく動作をするはずです。

    ②についてはCakeコアでAppControllerを継承しているものが、他には通常あまり使わないであろう、PagesControllerくらいだったので、こちらでもおそらく問題なく動作すると思います。

    (2011/12/15追記)

    LibにAppControllerを置くと言う件について、Libの中にControllerのディレクトリを作ってその中に置けばいいという指摘を頂きました。
    試してみると、app/Lib/Controller/AppController.phpという形で設置をしたら、確かに問題なくusesなしでもapp/Lib/Controller/AppController.phpを見に行ってくれました。
    これならCakeErrorを書き換える必要もなくなるので、これが一番良い解決法かもしれません。

    app以下のクラス名とPlugin以下のクラス名が同名だと誤動作する

    これは具体例を挙げた方が早いと思います。

    app/Controller/PostsController.php(①)と、app/Plugin/Post/Controller/PostsController.php(②)の二つの同名のコントローラがあるとします。

    初めに①にアクセスした後、②にアクセスをしようとすると、①にアクセスをしてしまうというものです。(逆もしかり)

    これが起きてしまう原因はapp/tmp/cache/persistent/cake_core_file_mapのキャッシュがプラグインを考慮していないためです。(またお前か・・・)

    実のところ、AdminPluginを作成してそこに管理画面を実装しようとしていたところでしたので、これはかなり致命的でした。

    解消方法自体はコアを触れば出来なくもないのですが、コアのアップデートなど一切できなくなるのでお勧めはしません。

    どうしても動作させたいのであれば、lib/Cake/Core/App.phpをプラグインを考慮するように直接編集すると、動作はします。

    まぁ、ただ当分はapp以下とPlugin以下で同名のクラスが入るような実装は避けた方が良いでしょう。

    (2011/12/16追記)

    CakePHPチームにチケットを投げてみましたが、同名のクラスは現在使えない、Cake3で計画中と言われました。

    PaginatorのorderにSQLが記述できない

    CakePHP2からPaginatorがコンポーネントに分離されたことは皆さんご存知かとは思いますが、CakePHP2では現在のところPaginatorでorderにSQLがそのまま記述が出来ません。

    例えば

    $this->Paginator->settings = 
        array(&#039;order&#039; => array(&#039;`hoge` is null ASC&#039;)):

    のようなソートを行いたい時に上記のorderが無視されます。(実際に必要な場面がありました・・・)

    これは、コアのPaginatorComponentを見ればわかるのですが、352行目~の記述で

    if ($object->hasField($field)) {
    	$order[$alias . &#039;.&#039; . $field] = $value;
    } elseif ($object->hasField($key, true)) {
    	$order[$field] = $value;
    } elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field, true)) {
    	$order[$alias . &#039;.&#039; . $field] = $value;
    }

    とありまして、例外が全く、無視される形になっています。

    この記述自体は1.3にもあるのですが、1.3では最後にarray_mergeをしているので、SQLも取り込んでくれていたのですが、2.0では無視された状態です。

    どうしても特殊なorderをかけたい場合は現状では素書きのSQLやDBのviewなどで対応しておいた方が良いかもしれません。

    (今回私の場合は、元々orderをかけたかったテーブルがviewだったのでviewにソート用のカラムを泣く泣く追加しました・・・)

    (2011/12/16追記)

    CakePHPチームにチケットを投げてみたところ、orderを文字列で指定したら動くじゃん!って言われたので現在配列で指定したんだけど・・・と返しているところです。

    SSLページでのCookieのセキュア属性を外す方法

    SSLページがあるページとないページが混同する場合、Cookieのセキュア属性が付いていると、セッションを共有できないため、セキュア属性を外してあげる必要があります。

    1.3の場合は以下の対応が必要になります。

    app/config/core.php

    //	Configure::write(&#039;Session.save&#039;, &#039;php&#039;);
    	Configure::write(&#039;Session.save&#039;, &#039;session_custom&#039;);

    app/config/session_custom.php

    <?php
     
    if (empty($_SESSION)) {
        ini_set(&#039;session.use_trans_sid&#039;, 0);
        ini_set(&#039;session.name&#039;, Configure::read(&#039;Session.cookie&#039;));
        ini_set(&#039;session.cookie_lifetime&#039;, $this->cookieLifeTime);
        ini_set(&#039;session.cookie_path&#039;, $this->path);
    }
    ini_set(&#039;session.cookie_secure&#039;, 0);

    これは、コアのcake_session.phpの設定を強引に上書きをしています。

    CakePHP2ではcore.phpに以下の記述をすれば、対応できます。

    Configure::write(&#039;Session&#039;, array(
        &#039;defaults&#039; => &#039;php&#039;,
        &#039;ini&#039; => array(
            &#039;session.cookie_secure&#039; => 0
        )
    ));

    特に新しいファイルを作成する必要はなく、手軽です。

    CakePHP2.0ではセッションを共有するしないにかかわらず、SSLがあるページとないページが混同するサイトでは、セキュア属性は外した方が良い。

    これは、まずSSLのあるページ(①)でログインをした後、SSLのないページ(②)にアクセスし、その後、①のページにアクセスをすると、ログアウトしてしまうと言う現象のためです。(これは1.3では発生しません)

    詳しい原因についてはまだ追い切れていないのですが、①にアクセスした後、②にアクセスすると①と異なるセッションキーが振られます。(これは当然の話)

    その後①のページにアクセスすると、②で発行されたセッションキーに上書きされてしまうため、ログアウトをしてしまいます。

    以上の理由から、今のところ、SSLがあるページとないページが混同するサイトでは、セキュア属性は外して置いた方が良いと思います。

    (2011/12/16追記)

    CakePHPチームにチケットを投げてみたところ、上記の書き方でセキュア外せばいいじゃん!って言われました。

    最後に

    まだCakePHP2は駆け出しで、問題が色々と出てますが、触っている感じとしては1.3とそう大差なく使えています。そして早いです。

    きっとこれからどんどん良くなっていくと思うので皆さんどんどんさわりましょう!

    後、本当にfile_mapのキャッシュには要注意です!
    ああ・・・app.phpの750行目を消してfile_mapのキャッシュをなかったことにしたい・・・w

    comments