View Cache のキャッシュファイル名を変更する

posted 2010-07-26 | written by mon_sat

久しぶりにCakePHPネタを。

ひとつのAPPで複数のサイトを運営しているAPPがあります。
bootstrap.php内でURL等を見てどのサイトかを判断しているのですが、キャッシュを設定して困りました。
aaa.example.com/index と bbb.example.com/index で生成されるキャッシュファイルが同じになってしまいます。

どちらへアクセスしても先にキャッシュされたファイルを表示してしまうのです。

しかたがないので、以下のようにキャッシュを生成することを試みました。
aaa_index.php
bbb_index.php

キャッシュヘルパーを上書きする

コアのキャッシュヘルパーをコピーする

コアのキャッシュヘルパーを、APP内にコピーします。

cp cake/libs/view/helpers/cache.php app/views/helpers

キャッシュヘルパーを書き換える

キャッシュヘルパーで書き換える箇所は1箇所です。
キャッシュを書き出している __writeFile() の↓に1行追加します。

# app/views/helpers/cache.php
# __writeFile()
$path = $this->here;
if ($this->here == '/') {
  $path = 'home';
}
$path = Configure::read("subdomain") . "/" . $path; // この行を追加
$cache = strtolower(Inflector::slug($path));

subdomain はbootstrap.php にて設定してください。

この状態でURLにアクセスすると、aaa_index.phpのように、サブドメインが付加されてキャッシュファイルが作られます。
しかし、このままではdispatcherがキャッシュファイルを認識してくれません。

Dispatcher等を修正する

dispatcherの該当箇所を書き換える前に、dispatcherをコピーし、そのファイルを読むように変更しなくてはなりません。

CakePHP1.2であれば、この記事内のスライドが参考になります。
しかし、CakePHP1.3 (1.3.0)の場合、dispathcerはキャッシュしてくれないようです。

以下のように対処します。

DispatcherとBootstrapをコピーする

CakePHP1.3から登場のAPP/libsディレクトリに、コアのdispatcher.php とbootstrap.php をコピーします。

cp cake/libs/dispatcher.php app/libs
cp cake/libs/bootstrap.php app/libs

Dispatcherを書き換える

cached() 内に、1行追加します。

# app/libs/dispatcher.php
# cached()
$path = $this->here;
if ($this->here == '/') {
 $path = 'home';
}
$path = Configure::read("subdomain") . "/" . $path; // この行を追加
$path = strtolower(Inflector::slug($path));

次に、当Dispatcherを読み込むように修正します。

app/config/bootstrap.php にて、dispatcherの読み込み先を変更する

bootstrap.phpの最後の方に、下記を加えます。

# app/config/bootstrap.php
App::import('Lib', 'Dispatcher');

このままでは、コアのDispatcherも読みにいってしまうようですので、それを止めます。↓

Bootstrapを書き換える

コアからコピーしたapp/libs/bootstrap.phpを書き換えます。
(app/config内のbootstrap.phpではありません)

# app/libs/bootstrap.php
# 最後の行をコメントアウト
// require CAKE . 'dispatcher.php';

最後に、webroot/index.phpのbootstrap.phpの読み込みを変更します。

webroot/index.phpを書き換える

# app/webroot/index.php
# 次のように変更します
//if (!include(CORE_PATH . 'cake' . DS . 'bootstrap.php')) {
if (!include(APP_PATH . 'libs' . DS . 'bootstrap.php')) {

以上です。

もっとエレガントなやり方がありそうだけど、とりあえず。

CakePHP勉強会@東京(2010年5月)

posted 2010-06-03 | written by mon_sat

CakePHP勉強会に行ってきました。
昨年10月末に行われたCakePHP Matsuri以来のイベント参加。

今回は、札幌・名古屋・福岡でサテライト会場ができ、どこも盛況だったもよう。
勉強会の申し込みもすぐにいっぱいとなり、今回の勉強会もひとつの祭りだったのではないでしょうか。

全体的な所感としては、@cakephperさんのテンポの良い司会と各発表者の発表の上手さがあって、あっという間の4時間でした。
テーマがCakePHP1.3ということで、非常に実践的な内容も盛りだくさんで、「勉強になった」という感じです。

CakePHP以外にも、Rackspaceというクラウドサーバーや、WAFというセキュリティソリューションの存在を知ったりと、CakePHPに関わっている人たちのCakePHPに限らないお話があって良かった。
次の日早速調べてみて、使いたくなったし。

PHP Matsuri by @yandoさん

資料
トップバッターはニューヨークから。
サテライト会場が全国規模で展開され、発表者は世界規模という、まさにグローバル。
(ちなみにNYは深夜1時すぎだそう)

発表された内容も、いきなりのビッグニュース。
なんと、前回のお祭りをスケールアップして、今年も行うらしい!
その名もPHP Matsuri

PHP5.3向けのLithiumがstable間近ということもあってか、冠から"Cake"がとれました。(symfonyとかも含まれていますよ)
前回同様CakePHPのコアデベロッパーに加え、今回はLithiumのコアデベロッパーの講演も開かれる予定とのこと。
これは胸が熱くなりますね!

開催概要をまとめると(現時点で予定していること)

  • 名称
    • PHP Matsuri
  • テーマ
    • もっと交流、もっと刺激的に
  • 開催期間
    • 2010-10-02(Sat) - 2010-10-03(Sun) ※オールナイト!
  • 場所
  • 人数
    • 100名程度
  • 参加費用
    • 2万円前後(食事・軽食・懇親会費用込み) ※早期購入は割引
  • 内容
    • コアデベロッパーによる講演
    • ワークショップ(入門者向け・中級者向け・Lithium入門者向け)
    • コンテスト(プラグイン・ライブラリ、ハッカソン、ドキュメント、パフォーマンス、etc)
  • 主催者からのお願い
    • 協賛企業・団体参加企業募集
    • 参加チケットの早期購入
    • コンテストへの参加
    • 運営スタッフ募集
  • Twitter ID

まず何といっても今回は土日を通してのイベントであるということに驚きました。
プログラムは夜遅くまで組まれていて、夜通し開発(まさにハッカソン!)できる環境とのことです。
興味のある講演を聴きつつ、他の時間で隣のエリアで実践する。
素敵ですね!

しかも場所は晴海。
自宅から至近です。徒歩圏内とまではいかずとも自転車で楽々行けます。車なら数分。
近隣の地理にも明るいので、何かお手伝いできることがあれば是非協力したいな。
と思っていたら運営スタッフ(お祭りなので「青年団」)募集とのこと。
協賛企業にはなれなそうだが、他の3つは協力できますので、よろしくお願いしますー。

ともかく楽しみなイベントの告知から始まった勉強会なのでした。

CakePHP1.3の概要 by @cakephperさん

資料
さて本題のCakePHP1.3のお話。
会場アンケートではまだまだ1.3の開発経験がない方も多く、「何が変わった?」というところに興味が集中しているようです。
変更点がまとまっていて、すぐにでも導入できるということが分かったのではないでしょうか?

私としては、一番はRoutingだと思っていて、Routingの改善が、その他に良い影響を及ぼしている箇所が随所に見られます。
CakePHP1.2ではどうしても力技で解決しなくては行けなかったことも、普通に使えるようになっていることが多いのです。
あとは、plugin周りも整備されている印象です。
1.3からplugins/xxx/webroot以下にcssや画像を置けるようになり、配布・利用がより簡単になっています。

さすがCakePHP1.x系の最終バージョンといえると思います。
CakePHPでの開発経験があり、1.3を使ったことがない方は、次の案件からは1.3をお勧めします。

Ktai Library on CakePHP1.3 by @ecworks_masapさん

資料(PDF on ecworks.jp)
Ktai Library 勉強会でもお世話になってる、作者さんです。
Ktai Library は、現在は、CakePHP1.3上で開発されているため、もし1.2のアプリケーションを1.3に移行するという場合であっても、Ktai Library周辺は変更点はありません。

この辺は実際に双方で開発してみて経験済み。
安心して使えます。
根幹部分はライブラリ化する前から使っているものとのことで、十分に安定していますし。

今回は今後のバージョンアップ予定の機能を、多数、発表していました。
個人的には、早期にplugin化が希望です。
(branchがあれば、協力できることは協力します!)

コアライブラリのエレガントなハック by @hiromi2424さん

資料
今回一番男前な発表をしたのは、CakePHP界での多大な貢献者の@hiromi2424さん。
twitterでの発言や今回の発表を聞いていて感じるのは、CakePHPの中身についての調査をとことんやっているということ。
何事も仕様を理解するというのは重要ですが、表面的なところだけさらって終了することが私は往々にしてあります。コアのコードもちょっと読んで終了とか。
ですが、このかたは、深いところまで理解している。
だからこそ、どうしても必要なこと(ハック)にあたったときに、どのようにやるかということを、「分かりやすく」説明できるのでしょう。

CakePHP触って間もない人は多少振り切られた方もいるかもしれませんが、間違いなく、今回の発表資料は保存版といえます。
今すぐブックマークを!

twitterとcloud serverとcakeで新規サービス by @takamunetaniiさん

資料
つづいて、Ktai Library 勉強会でもご一緒している@takamunetaniiさんの発表。
こんなにためになる事例紹介が過去あったか?というくらい今の私にはためになりました。

タイムリーだったのは、Twitter API / Cloud Server あたり。
ちょうど勉強し始めたところだったので。

あと発表が面白い。惹き付けるなあ。

Rackspace は、Lithium実行環境として最適かも。
最安$10ちょいですって、奥さん。
インスタンスをオフれたり、保存したイメージをコピーできたら最強だなあ。

Twitter API は、GAEで実行しても面白いと思う。
ということでPython勉強中。
(CakePHPユーザーにこそお勧めできる>python)

起業するとのことです。
ぜひ今後もよろしくお願いしますー。

ライトニングトークス

そしてLTの時間。
もう盛りだくさんの内容。LTじゃなくて通常の発表枠でいける内容ばっかり!

最後に@cakephperさんから恒例のIRC集会の案内があって終了。
ちなみに次回は2010年6月11日(金) 15:00-19:00と後日発表されました。
IRCサーバーが変更になっているので注意が必要です。
初めての方もぜひ。気軽に参加できる素敵な会です。(業務中に参加できる方限定ですがw)

懇親会

私の場合、勉強は2の次で、飲むために行っているようなものです。
#cake_beer というタグもあります。

今回は会場の机といすを片付けて、その場で懇親会開始。
ほとんどの人が参加したのではないでしょうか?
ついにはビールが底をつきました(笑)

懇親会中に@sizuhikoさんのテストについての発表あり。
テスト書いておくことで1.2 -> 1.3 への移行も楽ということが分かった。
横着せずにヘルパーとかもテスト書かねば。カバレッジ低いからなあ。。。

毎度思うのが、CakePHPな人たちと飲むのは楽しい。
CakePHPに限らずいろんな話題が出てきてためにもなる。
徐々にCakePHP飲み会や勉強会後の懇親会でご一緒した人が増えてきて、それもまた嬉しい。

サテライトの人たちとも交流できたらいいなあ。
チャンスは他地域勉強会とPHP MatsuriかCakeFest2010か。

その後、懇親会は終了。(会場を貸していただいたトライコーンさんに感謝!主催者の方々、とくに@kaz_29さんおつかれさまでした!)

ぞろぞろと2次会へ。

2次会ではテーブルごとに分かれて交流。
飲み会や勉強会でお世話になってる@cakephperさんや@yashioさんらと飲む。
(もうひとりのかた、忘れてしまった。ごめんなさいい<twitterやってたら連絡ください)
他テーブル含めて雰囲気(うまく変換できた)が良い。

今年はいろんな勉強会に行ってみよう。
興味の幅を広げねば。
そこで仕入れた話をすれば、みんなハッピーになれるかもだし、いつまでも話を聞いてばかりじゃ申し訳ないし。

まとめ

最近いろいろあって疲れる日々もあったが元気出てきた。
今回交流できた方々、次回お会いしましょう。
交流できなかった方々、次回こそ是非!

Ktai Library 勉強会 #1-2

posted 2010-05-10 | written by mon_sat

Ktai Library 勉強会に行ってきました。
といっても、第1回が3月8日(月)で第2回が4月16日(金)でしたので、もうかれこれ1ヶ月近く経とうとしていますが。。。

でも、ちっちゃいことは気にしないで、両日を通して学んだことをまとめつつ、感想などを。(遅ればせながら)

準備編

せっかく作者の@ecworks_masapさんが来るわけですから、できるだけ準備をしておこうというわけで、とりいそぎセットアップを。

CakePHPは1.2を使用しました。
(当日聞いたら1.3でも普通に使えるということで、第2回からは1.3を使用)
Ktai Library は、今後のバージョンアップに備えて、gitで持ってくる。

$ git clone http://github.com/MASA-P/KtaiLibrary.git

(app/vendors/ecw となるように、KtaiLibrary/vendors/ecwをコピーする)

絵文字アイコンが必要なので、それもダウンロード

$ cd app/webroot/img
$ wget http://start.typepad.jp/typecast/download/emoticons.zip
$ tar xfvz emoticons.zip

Ktai Library で必要なファイルを、app内にコピーします。
以下は、シンボリックリンクで代替。

$ cd app/config
$ ln -s /path/to/KtaiLibrary/app/config/ktai_session.php .
$ cd ../../app/controllers/components
$ ln -s /path/to/KtaiLibrary/app/controllers/components/ktai.php .
$ cd ../../../app/views/helpers
$ ln -s /path/to/KtaiLibrary/app/views/helpers/ktai.php .

app_controller.php内で、redirect()を上書きするため、KtaiLibrary/app/controllers/ktai_app_controller.phpの内容を、写す。
ver0.2.2以前の場合で、リバースルーティングに配列を渡している場合は、コレを追加
すべてのcontroller,viewでktaiコンポーネントとktaiヘルパーを使用する場合はapp_controller内の var $componentsとvar $helpersにKtaiを追加する。
また、以下を環境に合わせて追加

# app/app_controller.php
var $ktai = array(
    'use_img_emoji' => true,
    'input_encoding' => "UTF-8",
    'output_encoding' => "UTF-8",
);

NamedParamsのセパレータを変更

# app/config/routes.php
Router::connectNamed(array(), array('argSeparator' => '~'));

FireMobileSimulator

すでにメインブラウザをGoogle Chromeに完全移行したのですが、いかんせんChromeの拡張機能では、現状ではFireMobileSimulatorのようなものを作れません。
というわけで、ケータイからのアクセスをシミュレートするFireMobileSimulatorをFirefoxにアドオンします。
初めて使ったのですが、超絶便利でした。しかもbetaながら、タブごとに別々のuser_agentに切り替えるバージョンまであり、開発スピードが段違い。素晴らしい!

Hello Ktai Library !

ここまでできたら、Ktai Library の教科書Chapter4のリスト04-04、04-05および04-06を参考に、サンプルページを表示します。

以下、Ktai Library 開発用デモサイトソースより一部改訂して引用

# app/controllers/mypages_controller.php
function index() {}

# app/views/layouts/default.ctp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <?php if($ktai->is_iphone()){ ?><meta name="viewport" content="width=260"><?php } ?>
    <title><?php echo $title_for_layout; ?></title>
</head>
<body>
    <?php if(!$ktai->is_ktai()){ ?><div style="width: 240px;"><?php } ?>
    <?php echo $content_for_layout; ?>
    <div align="center">
        <hr width="90%" size="1" color="#333333" noshade>
        (C)2009 <a href="http://shop.example.com/">MyShop</a>
    </div>
    <?php if(!$ktai->is_ktai()){ ?></div><?php } ?>
</body>
</html>

# app/views/mypages/index.ctp
<center><?php $ktai->emoji(0xe753); ?>Hello, ktai Library!<?php $ktai->emoji(0xe753); ?></center>

絵文字付きでHello, Ktai Library!と表示されたら準備完了です。

このブログは記憶を頼りにまとめているので、記述をそのままやっても動かない場合もあると思います。
動かない場合はtwitterでお知らせください。

勉強会 #1

じつは、勉強会の1回目は、第4章を実践することがテーマだったので、準備万端で臨むと、もう終わってたりします(笑)

というわけで私は、今後の開発がしやすいように、いろいろとカスタマイズしていくことにしました。

Themeを使って、KtaiからのアクセスとPCからのアクセスで、表示を分ける

CakePHPのTheme機能を使って、Ktaiからのアクセスの場合は、themed/ktai を使用し、PCからのアクセスの場合は、通常のviewを使用するケースです。

まず、Ktaiからのアクセスかどうかを判別して、必要に応じてテーマを使用するようにします。

Ktaiからのアクセスかどうかは、 Ktaiコンポーネント(かKtaiヘルパー)のis_ktai()を使用します。

# 使用例
if ($this->Ktai->is_ktai()) {
    debug("Ktaiからのアクセスです");
}

Themeを分ける場合は、beforeRender()あたりがよさそうですので、app_controller.php内のbeforeRender()を変更します。

# app/app_controller.php
function beforeRender() {
    if ($this->Ktai->is_ktai()) {
        $this->view = "Theme";
        $this->theme = "ktai";
    }
}

これでThemeの使用ができますので、必要なファイルを作成します。
まずは、必要なディレクトリを作成しましょう。
app/views内に、themed/ktaiディレクトリを作成し、その中に、layoutsやelements、今回の場合はmypagesを作成します。

$ cd app/views
$ mkdir -p themed/ktai/layouts
$ mkdir -p themed/ktai/elements
$ mkdir -p themed/ktai/mypages

そして、レイアウトファイルを移動します。

$ cd app/views
$ mv layouts/default.ctp themed/ktai/layouts

ここで、いったん動作確認。
ケータイによるアクセスとPCからのアクセスで、別々のレイアウトファイルが使われていれば成功です。

mypages/index.ctpも忘れずに移動します。

$ cd app/views
$ mv mypages/index.ctp themed/ktai/mypages

その他

elementsでヘッダーやフッターを作ったりして、初日終了。

感想など

@yashioさんの呼びかけで始まったKtai Library 勉強会@関東ですが、非常に素敵な会でした。
まず何といっても雰囲気がよい。少人数故なのかどうかは分かりませんが、堅苦しくなくて良かったです。
また、講義を聞くスタイルではなくて、実践形式で進めるというのが、振り返ってみて良かったなと。講義を聞いて、その場で分かったつもりになって、実践しないでほったらかす人種のわたしとしては、手を動かす絶好の機会となりました。

なかにはセットアップに苦労していた方もいましたが、そういったことも含めて、実際にやってみることの大事さを再確認することができました。

最後は、みんなで歓談し、CakePHPやそれ以外のことについて情報交換。
『ゼッタイに第2回も参加しよう』と心に誓った第1回でした。

勉強会 #2

そして、第2回。今回は第5章ということで、非常にボリュームがあります。
しかし、その多くはPCによる管理画面の作成ですので、ケータイで作る部分はさほど多くない。
ひととおり読み込んで準備して、、、と思ったが、タイミング良く入っているケータイサイトのお仕事を進めないと進捗が遅れていることに気づく。
内容的にはフォームの作成で、サンプルと一緒の画面遷移なので、第5章を参考にしつつ作成しました。

teltoリンク

Ktai Library には、便利なmailto()というメソッドがある。これはいわゆるmailtoリンクを、キャリアに合わせて作成してくれるもの。

# 使用例 (for CakePHP 1.3)
// Site.mail はメールアドレスが入っています
// 第3引数で件名を第4引数で本文を指定可能です
$this->Ktai->mailto("メール",Configure::read('Site.mail'),"携帯サイト問合せ");

telto()もあるかなとリファレンスを見たが、なさそうなので急ごしらえした。

function telto($telno = "") {
    if (empty($telno)) {
        $telno = Configure::read('Site.tel');
    }
    $telnoWithoutHyphen = str_replace("-","",$telno);
    return "<a href=\"tel:{$telnoWithoutHyphen}\">{$telno}</a>";
}

なんでHtmlヘルパー使ってないんだっけか?相対URLになっちゃうんだっけかな?
おかげで$optionsも指定できないからあんまり意味ないかも。
ハイフンを消す必要があるかどうかも不明だけど、確認するの面倒だからまあ良し。

あと対応できない端末があるかもなあ。

その他

主に見栄えのところを調整した。
Ktai Library には、スタイル調整用の機能があります。

# app/app_controller.php
var $ktai = array(
    'style' => array(
        'h2' => "color:#ffffff;background-color:#0000ff;",
    ),
);

としておき、viewで

# view on CakePHP1.3
<div style="<?php $this->Ktai->style("h2"); ?>">見出し文字列</div>

のように使います。便利ですね。

自ヘルパー内で拡張して、divでラップしても面白いかもしれませんね。

感想など

外は雨模様。帰りにはみぞれになっていた。4月なのに。
そんな悪天候の中、参加者は全員出席だったそう。それだけ注目のKtai Libraryであり、Ktai Library 勉強会ということでしょう。

帰ってから実機で確認してみると、キャリアによって細かい動作が違ってくるものがあるということも分かった。
携帯サイトはノウハウの集積が不可欠だと思っていたが、それでもその大部分を担ってくれるKtai Libraryを使うかどうかで、開発効率は大きく違ってくるでしょう。

そして。
Ktai Library を使っても、なお、知っておかなくてはならないことが多いのが、携帯サイトの開発だと気づく。
だからこそ、Ktai Library の教科書では、題材に『ショッピングサイト』を選んだのではないだろうか。
幅広い機能が必要となるため、それを実践することで、必要なノウハウも(多数)身についていくのですから。

続きは次回。
では。

さくらインターネットでbakeする前に

posted 2010-02-19 | written by mon_sat

自分用にメモです。

さくらインターネットでCakePHPのbake等を利用する前に、bashへのパスを変更する必要があります。
CakePHPのバージョンアップの時に注意が必要です。

 #!/usr/local/bin/bash

以上です。

CakePHPのdatetime validation

posted 2010-02-17 | written by mon_sat

かゆいところに手が届くCakePHPですが、バリデーションルールにdatetimeがありません。
(私が知らないだけ?)

ということで、オリジナルメソッドとして、作成しました。

現在テスト中なので、完全ではありません。

# model

var $validate = array(
	'any_field_name' => array(
		'datetime' => array(
			'rule' => array('datetime' ,'ymd'),
			'message' => '日付の形式が間違っています',
			'allowEmpty' => true,
		),
	),

function datetime($data ,$format) {
	$datetime = current($data);
	$s = strrpos($datetime ," ");
	$date = substr($datetime ,0 ,$s);
	$time = substr($datetime ,$s + 1);
	$Validation =& Validation::getInstance();
	$result = $Validation->date($date ,$format) && $Validation->time($time);
	return $result;
}

(ひとりごと1)突っ込み歓迎(twitterで)
(ひとりごと2)app_model.phpにおくと動かないな。。。

CakePHPのアソシエーション

posted 2009-11-26 | written by mon_sat

CakePHPには、非常に強力なアソシエーションという機能があります。
これは相互に関連する複数のModel(テーブル)を一括して扱うための優れた仕組みです。

便利なアソシエーションですが、マニュアルを読んで理解したつもりでも、実際に活用する段になって、どのように設定すればよいのか迷うことがあります。

CakePHPに不慣れな方がアソシエーションで迷っていたときに、どのように教えてあげると分かりやすいのかという視点で、アソシエーションについてまとめてみました。

ここではHABTM(Has And BelongsTo Many)については触れません。それはまた別の機会で。

よくある勘違い

一般にAとBというふたつのモデルがあったとき、AモデルがBモデルを『ひとつ持っているときはhasOne』で『複数持っているときはhasMany』と認識している場合がありますが、その理解のままだと、ときに混乱してしまいます。

よくあるのが、以下のようなケースです。

  • ブログの記事を扱ったArticleモデルと、その記事の『編集中・投稿済・削除』等のステータスを扱うStatusモデルがあり
  • ArticleモデルとStatusモデルのアソシエーションを検討したとき
  • Article hasOne Status と間違ってしまう

ということがあります。

記事が、ひとつのステータスを保持するわけですから、hasOneだろうと考えると、『あれ?どうやってこれ設定するんだ???』となってしまいがちです。

つまり、上記の『ひとつ持っている』『複数持っている』という考え方は、忘れたほうが良いのです。

アソシエーションの基本

それでは、まず、基本的なところからおさらいしつつ、なぜ上述のような混乱に陥るのかを説明していきたいと思います。

アソシエーションの基本原則其の一:外部キーを持っているときはbelongsTo

まず、belongsToからおさらいしましょう。
belongsToは、あるモデル(B)があるモデル(A)に属している時に、B belongsTo A とアソシエーションを設定します。

『属してるってなんぞや?』というと、簡単に言えば、『hasOneもしくはhasManyされている』ということです。
A hasOne B もしくは、A hasMany Bという関係が成り立っている場合、B belongsTo Aというアソシエーションとなります。

hasOne や hasMany が存在せず、belongsTo だけ設定した場合でも、アプリによっては何の支障もなく動作します。
しかし、ここでは基礎を学ぶという意味で、逆方向のアソシエーションの存在と『対』で理解してもらうことから始めます。

外部キー

相手方のモデルの主キー(id)を『外部キー』といい、belongsToが設定されたモデルには、必ずこのフィールドが存在します。

たとえば、上述のArticleモデルが複数のコメントを扱うCommentモデルとhasManyの関係にある場合、

  • Article hasMany Comment
  • Comment belongsTo Article

ですから、Commentモデルには、article_idというフィールドが必要になります。

逆に言えば、以下の原則が成り立つということです。

アソシエーションの基本原則其の二:hasOneおよびhasManyの相手方のモデルには外部キーが存在する

A hasOne B もしくは A hasMany B というアソシエーションの場合、AモデルにはBモデルの外部キーはありません。
分かりやすいhasManyの例で説明すると、上述の例で、Articleモデルにcomment_idというフィールドを設定することは不可能です。
だってコメントは複数存在できるので、フィールドがひとつでは足りませんから(笑)

hasOne も hasMany と同類

『じゃあ、hasOneならあり得る?』かというと、そういうものではありません。
hasOneの場合、どっちに外部キーを置けばよいか分かりづらいため、外部キーを頼りに考えていくと、より混乱をきたしやすいようです。

そういう場合は、心の中で『hasOne は、hasMany の特殊な一形態』と唱えるようにしましょう。

だって、相手方(関連モデル)が『複数くっついているか、ひとつしかくっつかないか』の差でしか無いわけですから、hasOneなら自モデルに外部キーとなるはずはありません。

もちろんhasOneとhasManyはもっと本質的に違いますが、ここではどのようにアソシエーションすればよいかを考えるために単純化しています

外部キーの位置関係のまとめ

外部キーは、hasOneおよびhasManyのときには『相手方のモデルに存在』し、belongsToしているときは『自モデルに存在』するのです。

この原則を守れば、先頭で提示したマスタテーブルの問題も簡単に答えを導けます。

なお、なぜ上記の原則が正しいのかは、ここでは深く追求しませんのであしからず

マスタテーブルはhasOneではなくhasMany

答えを先に言ってしまいました。
それでは順を追って、なぜそうなるのかを説明していきたいと思います。

例の確認

  • ブログの記事を扱ったArticleモデルと、その記事のステータスを扱うStatusモデルがある
  • ArticleモデルとStatusモデルのアソシエーションを検討する
  • Statusモデルは、editing , published , deleted の3レコードのみからなるマスタテーブル

選択肢はみっつ

hasOneと仮定した場合

  • Article hasOne Status
  • Status belongsTo Article

hasManyと仮定した場合

  • Article hasMany Status
  • Status belongsTo Article

belongsToと仮定した場合

  • Article belongsTo Status
  • Status has??? Article

1: 上記の原則を当てはめて考える

まず、外部キーをどちらに設定するかを考えます。

ArticleかStatusか、どちらになるのかは、あまり考える必要が無いでしょう。
マスタテーブルに外部キーを設定する可能性はありませんから。
つまりStatusには外部キーが存在しないと言うことになります。

ということは。
Article belongsTo Status が確定します。

2: Status has??? Article は、hasOne ? hasMany ?

逆に言えば、Status has??? Article ということになります。

では、hasOneなのかhasManyなのかが問題となりますが、この答えも自明でしょう。
記事は無限に増え続けるわけですから、hasManyでないと、statusが足りません(笑)
Status hasMany Article が確定します。

3: ふたたび上記の原則を当てはめて検証する

結果的にhasManyを用いて、以下のようになることが分かりました。

  • Article belongsTo Status
  • Status hasMany Article

当初イメージした『ArticleがStatusを持っている』という、やってしまいがちな解釈からいくと、逆になりましたね。

では、外部キーがどうなっているかを考えましょう

  • Article
    • id
    • status_id
    • title タイトル
    • body 本文
    • Other その他
  • Status
    • id
    • name

はい。間違いなく、上記の原則に沿っています。

それでも新人くんが迷っていたら

1: hasOneは関係を逆にしてもhasOne

hasOneかhasManyかですが、次のような考え方も成り立ちます。
CakePHPのアソシエーションには『1対多』は存在しても『多対1』はありませんよね。
一方でhasOneは『1対1』ですから、User hasOne Profile が存在する場合。それは、Profile hasOne User でもあるわけです。

つまり『関係性をひっくり返してみて成り立つかどうか』で、A hasOne B か A belongsTo B にするべきかを判断することが可能といえます。

上述のArticleとStatusの例で、Article hasOne Status と(間違って)考えたとしても、逆の関係性=『Status hasOne Article が成り立たないから間違っている』と判断することが可能です。
そんなときはArticle hasMany Status なんて突拍子もないことを考えることなく、Article belongsTo Status ( = Status has??? Article)と考えることができますね。

2: hasMany の場合は、Counter Cache を考える

Article hasMany Comment というアソシエーションがあるとき、Article モデルに comment_count というフィールドを設置し、Commentモデルの(belongsTo)アソシエーション時にcounterCache => trueとすると、commentsテーブルにコメントが追加されたり削除されたりするたびに、自動的にArticle.comment_count にコメント数が入ります。

Articleモデル単体を呼び出すだけで、コメント数が分かるようになり、非常に便利です。

Commentモデルを一緒に引っ張ってきた場合でも、すべてのコメントを取得することなくコメント数が分かります

上述の例に戻ると、たとえば『各ステータス別の記事数が知りたい』という場合、以下のようにするでしょう。

# Status Model
# * id
# * name
# * article_count
$statuses = $this->Status->find('list', array('fields' => array('Status.name', 'Status.article_count'));
debug( $statuses );
// example
// editing => 3
// published => 35
// deleted => 7

Status hasMany Article が正解ということは、Counter Cacheが可能であることからも分かりますね。

まとめ

アソシエーションの基本原則

  • 外部キーを持っているときはbelongsTo
  • hasOneおよびhasManyの相手方のモデルには外部キーが存在する

迷ったら

  • マスタテーブルはhasMany
  • 外部キーから考える
  • hasOneで良いのか迷ったら、関係性を逆にしてhasOneが成り立たなかったら、belongsTo
  • hasManyに違和感があったら、Counter Cache が設定できないかを考え、設定できればhasManyで問題ない

以上です。

アソシエーションは油断すると中級者でも混乱します。
CakePHPに不慣れな新人さんが、アソシエーションで迷っていたときの参考に是非。

参考

関連: モデルを結びつける :: モデル :: CakePHPによる開発 :: マニュアル :: 1.2 Collection :: The Cookbook
【CakePHP】アソシエーションで迷ったらこう考えよう | ECWorks Blog

CakePHPでRSS2.0 feed

posted 2009-11-20 | written by mon_sat

CakePHPでRSSフィードをはくのは、ものすごくカンタンです。
今回、当ブログで身をもって体験しましたので、ぜひ。

とはいえ、すでに他のサイトでRSSについては、記事になっています。
(わたしも今回参照しながら対応させていただきました。m(_ _)m)
ぜひ、以下のサイトを参考に設定してみてください。

基本的な流れは、上記のサイトの方が詳しいので、そちらを見ていただくとしまして、今回は、Web&MUSICブログ QUALLさんのところで対応していたCDATAについての別解です。

まずは、RSS配信の流れから

ほぼ、上述のサイトそのままですが。

config/routes.phpを編集する

# config/routes.php
// 追記
Router::parseExtensions();

controllerでRequestHandlerコンポーネントを読み込む

articles_controller に記事があるとします。

# controllers/articles_controller.php or app_controller.php
// 例
var $components = array('RequestHandler',);

viewにRSS用のviewを作成

レイアウトファイルに標準のものを使用する場合は、viewファイルだけを作ります。
ここで、view内にfunctionを書くのに違和感ある場合は、helperに記入すると良いでしょう。
以下は、そんなケースの例です。
ここではMyHelper (my.php) というのがあるとします。

# views/articles/rss/index.ctp
// 記事一覧が $this->data に入っている場合
echo $rss->items($this->data, array($my, 'rss_transform'));

# views/helpers/my.php
<?php
class MyHelper extends AppHelper {
function rss_transform($item)
{
	return array(
			'title' => $item['articles']['title'],
			'link' => array('action' => 'view', $item['articles']['id']),
			'description' => array(
				'value' => $item['articles']['body'],
				'cdata' => true,
				'convertEntities' => false,
			),
			'pubDate' => ['articles']['posted_date'],
		);
}

以上でフィードがはかれます。
デフォルトURLは、 上記の例なら、 /articles/index.rss です。

CDATA対応

上記の description のように配列で記述すると、HTMLタグがそのまま入ります。
HTMLタグをエスケープしたくない場合は、convertEntities を false に、中身をCDATAで囲う場合はcdata を true に、それぞれ設定してください。

というわけで、以上です。
このブログもRSS配信をするために、数時間で対応できました。(現在フィードのテスト中)
上述したブログのおかけで、実際の配信部分は30分程度で完了し、残りはRoutingに苦労したのと、CDATAに対応するために試行錯誤しただけ。
CakePHPさまさまです。

CakePHPのバージョンを取得する方法

posted 2009-11-19 | written by mon_sat

CakePHPのイメージ画像CakePHPでプラグインを作っていると、現在稼働中のCakePHPのバージョンが1.2なのか1.3なのか知る必要がでてくるかもしれません。

そんなとき、どうやってCakePHPのバージョンを取得するのだろうと思って、IRCで聞いたら、教えてもらいました。

CakePHPのバージョン取得

debug( Configure::version() );
# ex : 1.2.3.8166

Configureクラスを見てみると、どうやら、coreのconfig/config.phpに値がある様子。
で、ソースを見てみると、、、

<?php
return $config['Cake.version'] = '1.2.3.8166';
?>

コメント除くとこれだけでした。

CakeMatsuriは有意義で楽しかった

posted 2009-11-18 | written by mon_sat

ブログを書くまでが勉強会であり、ブログを書くまでがCakeMatsuri(祭り)です。

ようやくブログを書き上げた。いまだ、祭り気分が抜けていなかったから、というわけではありませんが。
お祭りもようやく終了です。

私が参加したのは2日目のカンファレンス。
事例紹介等の通常のセッションの他にも、CakePHPのコアデベロッパーの話を直接聞けるということもあり、盛りだくさんの内容。

セッションの様子は、他の方のブログに任せまして、CakeMatsuriで一番得られたものについて書きたいと思います。

私が参加した理由のひとつは、CakePHPの勉強以上に、CakePHPを使っている方々との交流にありました。
せっかくユーザー数が増えているCakePHPです。使っている方とひとりでも多くお話しするに越したことはありません。さらに、コアデベロッパーともお話しできるチャンス。これを逃す手はありません。(まあ英語できないんですけどね。そこは気持ちで押し切ろうと)

さて、カンファレンス当日。
早めに家を出たつもりも、着いてみたら開始5分前。席はまばらでしたが、ちょうど良い時間でした。
cakephperさんに受付してもらい、CakeMatsuri特製手ぬぐいをもらいます。
2種類あって悩みましたが、より祭りっぽい方をチョイス。これはうちに帰って家族にプレゼント。(デザインが素敵だと喜ばれた)

席に着くとほどなくして、隣に外国の方が!日本の人と話ながら座りました。
さすがCakeMatsuriです。インターナショナルですな。
でも、ばりばり日本語が堪能でした。
昼食時に3人で乾杯しお弁当をいただきながらいろいろお話しできました。日本語で。
聞くと、イギリス生まれで7~8年前から日本で働いているとのこと。
着くなりMacBookを開け、おもむろにWindowsを立ち上げました(笑)
何と、USBに開発ツールを始めとしたアプリをすべて詰め込み、家(win)でも会社(Mac)でも同じ環境で作業できるんだそうです。ソノハッソウハナカッタワ。

わたしも今ではその人に教わったportableappsで、USB開発環境を作ってしまいました。Macは持ってないけど取引先のPCでもネットカフェでも開発ができるっていうのは、便利すなあ。
何せUSBさえ持っていけばgitも使えるんですから、ポータブル環境最高です。

さらに、会社でもMacBookにキーボードとディスプレイを外付けしてUSBに入れたアプリで作業していると、笑いながら話してくれました。うん、Macの意味ないですね(笑)
でも見ててMac欲しくなった。

ちなみに会場に来ている方のノートPCはほとんどMacBook。私は今のところノートPCを持たないでやっていけてるのですが、必要になったらこりゃ初Macですね。開発しやすそうだし。

ちなみにちなみに私が持って行ったのはPomera。テキストエディタだけです(恥)
もちろんひとりぼっち。

この陽気なイギリスの方とは、この日いちにち最後まで一緒でした。
そしてこの方と談笑しながら席に着いたのが、帰ってきてからこれ書いた人だと分かって大層びっくりしたヨシダスタジオの中の人。
大体同じくらいのCakePHP歴で、しかも、Croogoの話を聞いた後は、CakePHP製のCMSというお互いの興味がかぶっていることも判明。
職場も私の出身高校の近くということで何かと縁のある方。

さっきサイト見たらTwitter始めたらしいからさっそくフォローした。

カンファレンスの最後のプログラムは懇親会。
ここで、他にも多数の方とお話しできた。
なかには私と同姓同名(漢字一字違い)という方も。よくある名字とよくある名前の組み合わせでも、なかなか同姓同名には当たらないものです。
もしかしたら人生で初かも。

懇親会ではおもに、コアデベロッパーにアタック。
ここで、最近英語に触れてなかったことに激しく後悔。
簡単な表現すらでてこない。orz

気持ちでは押し切れないということが分かりました。

で、何とか話をしたのは。
まず、Grahamさん(@Predominant)とは。
CakePHP1.3やCakePHP2.0ではNameSpaceがサポートされるのかという質問をしたところ、両方ともPHP5.2をサポートするから使わないというお返事。
そうだよね。そうすると結局名付け問題には当分気を遣うことになりそうです。

あとは逆にCakePHPについての不満は?と聞かれてしまう。
実は、正直言って、今のところ不満はないんです。
で、「ない」と応えると、「It's Perfect???(さすがに完璧ってことはないよね?)」と言われてしまった。
「いやあ、パフォーマンスもキャッシュ使ったりで何とかやっているので問題ないんだ、今のところは」って言おうとして、3歳児レベルの片言の英語で必死に伝えようとするものの、伝わるわけはありませんので、最終的には「Yes. Perfect!」といって笑ってごまかした。

でも、Grahamさんは、そんな私のつたない英語を必死に聞いてくれて、ああ、心底良い人だと思いましたよ。わたしは。

イケメンJoelさん(@jperras)とは。
「今までKey Valueストアを避けてきたけど、これからは使います」と、言いたくて、後半だけ伝えた
「ぜひ使ってみてよ」と笑顔で言われた。
Joelさんは話をするとき、まっすぐ目を見て話す。真剣さが伝わってくる。見習いたい。

他にもいっぱいしゃべったが覚えていないのは、昼からビールを飲み続けたからかも知れない。
そして、2次会にもお邪魔した。
ここでは日本の食事や食材の話が多かった。

いわし明太子やらゴーヤチャンプルやら烏賊の一夜干しやら、続々と「食べたこと無いよね」という料理が注文される中、Grahamさんのベストは「たこわさ」だった。
日本食には全般的に満足してくれたみたいだ。しかもヘルシーすぎて日本から帰ったら7kgもやせていたらしい。
なんで日本人(とくに私)は、日本食で太れるのか。

振り返ってみると、2次会で見たコアデベロッパーの素の姿がとても印象的。
いたって普通だ。いや普通じゃない。いたって良い人たちだ。
デベロッパーが、人間的にも素晴らしいから、CakePHPのプロジェクトは成功しているんだと、改めて思った。
いつか海外のお祭りにも参加してみたい。

この日の解散は深夜0時に近かった。
2週間以上経っても、一瞬たりとも色あせない。とても有意義な一日だった。

運営に携わったすべての方々へ
本当に楽しめたお祭りでした。直接御礼の言葉を言えませんでしたが、どうもありがとうございました。

CakePHPのCache(Viewキャッシュ)について考える

posted 2009-08-07 | written by mon_sat

今までさほどシビアにキャッシュについて考えてこなかったのですが、ここらでキャッシュについてまとめてみます。

まずView系のキャッシュのうち代表的なものを。

  1. element ( & requestAction ) キャッシュ
  2. Viewキャッシュ
  3. Html Cache Helper

3は、Bakeryに掲載されているMatt氏作成のヘルパーです。初回アクセス時にHTMLファイルを生成するため、以後PHPすら使われません。
多少の制限事項がありますので、それについては、後述。

下に行くほど(1→2→3)キャッシュの威力が大きくなります。
以下順番に見ていきましょう。

1.element ( & requestAction ) キャッシュ

Viewをelement単位でキャッシュしていく方法です。
ヘッダーやフッター、メニューバーやHTMLのheadなど、使い勝手は良いでしょう。
記述の方法は次の通り。

# view
$this->element('element_name' ,array('cache'=>array('time'=>"+2 hours"));
$this->element('element_name' ,array('cache'=>array('time'=>"+2 hours" ,'key'=>$id));

通常は前者で良いのですが、同じelementを別々にキャッシュしたいというときは、後者のようにkeyを渡します。
データが大きいときは'key'=>$id.'_'.md5($data)とでもすれば良いでしょう。

$idは無くてもかまいません。特定のキャッシュファイルを削除するときに分かりやすくしているだけです。

そして、そのelementファイルの中では、requestActionを使用して、データを取得すると良いでしょう。
requestActionは、任意のControllerの任意のactionを個別に呼びだす仕組みです。
右側のカラムにある「○○リスト」等を作成するために重宝します。

# view(element)
echo $this->requestAction('/controller_name/action_name/params' ,array('return'));

第二引数にreturnという値を含めるとレンダリングしたViewを(ただしlayoutは使用せず)返してくれます。
もしデータだけがほしい場合は、actionの最後に return $this->data; のようにしておいて、element側で取得したデータを整形して表示させましょう。

requestActionのパフォーマンスはあまり良くないようですので、elementのキャッシュを使用して効率化してください。

elementのキャッシュファイルを削除する

最後に、requestActionで取得しているデータが更新された場合に、elementのキャッシュファイルを削除する方法です。
ModelのafterSave()とafterDelete()を使用して削除するのが良いようです。

# Model
function afterSave() {
  @unlink(CACHE.'views'.DS.'element__action_name');
}
function afterDelete() {
  @unlink(CACHE.'views'.DS.'element__action_name');
}

この場合は、当該Modelの全elementのキャッシュが削除されます。

2.Viewキャッシュ

CakePHPのキャッシュは主にこのViewキャッシュで実現します。
このViewキャッシュの最大の特徴は、キャッシュファイルが存在する場合は、Controllerを通らずに、キャッシュされたページを表示する点にあります。

Controller , Component , Model , Behavior , View , Helper 等々のファイルを一切読み込みませんので、非常に高速にページが表示されます。

使用方法は、以下のようにします。

  • core.php や bootstrap.php 等で Configure::Cache.check を true にする // Configure::write('Cache.check ', true);
  • Controller で CacheHelper を読み込む // var $helpers = array('Cache');
  • Controller で cacheAction でキャッシュする時間を指定する // var $cacheAction = 3600; // 秒

$cacheAction には配列を渡して、細かく制御することが可能です。
通常はアクション名(と$id等)を使用します。詳しくはマニュアルをご参照のこと。

URLルーティングを行なっているときはURLになります。

次のいずれかの設定をした場合にはViewキャッシュは働きません。
キャッシュを(一時的に)使用しない場合には、ここを変更すると一発です。

  • Configure::Cache.disable を true にする // Configure::write('Cache.disable', true);
  • Configure::debug を 1 以上にする // Configure::write('debug', 2);

 

3.Html Cache Helper

これはViewをレンダリングした後にHTMLファイルを生成しておき、次回のアクセスからはそのHTMLファイルが直接読みだされるという仕組みのキャッシュです。
生成されるファイルがHTML(正確には非PHP)なので、動的に変化する要素の記述はできませんが、静的ページやRSSなどの書き出しには非常に優れています。

ヘルパーのコードは、HTML Cache - GitHub より取得し、APP/plugins/html_cache に配置してください。(downloadリンクよりzip形式でダウンロードすることも可能)

# directories
plugins/
        html_cache/
                   views/helpers/html_cache.php

使い方は簡単です。Controllerで$helperの最後にヘルパーを記述し、 webroot/.htaccess を編集するだけ。具体的には。

# Controller (他のHelperよりも後に記述)
var $helper = array('XXX','YYY','ZZZ','HtmlCache.HtmlCache');
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} ^GET$
    RewriteCond %{DOCUMENT_ROOT}/cache/$1/index.html -f
    RewriteRule ^(.*)$ /cache/$1/index.html [L]
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>

すると webroot/cache以下にURLをもとにファイルが生成されます。(cacheディレクトリはパーミッション707等で作成)
CakePHPは標準の設定で、CSS等ファイルが実在する場合は、ルーティングを行いません。そのため生成されたHTMLファイルが直接WEBサーバーより読み込まれます。

注意事項としては、
・/controller_name 等のURL(action名のないURL)やルートに対してはキャッシュが働かない
→/controller_name/indexのようなURLにはキャッシュが働きます
(仕様変更により可能になりました)
・キャッシュファイルの生存期間(expires)は設定できない
→bakeryの記述を参考にcron等で一定期間ごとに自動的に削除するようにします
・パーミッションを適切に設定する
→ファイルの書き出しはwebサーバー等の権限で行われます。webroot/cache以下に書き込み権限がないといけません。また、webroot/cacheはあらかじめディレクトリを作成しておく等の必要があります。

用途と環境に応じて選択しましょう

以上です。
利用する状況に応じてキャッシュの種類を使い分け、快適なサービスを提供していきましょう。

第1回CakePHPオフ会@関東

posted 2009-08-06 | written by mon_sat

昨日(2009/08/05)、前回のIRC集会終了後に話題が上がった「勉強会ではなく懇親会」(つまりいわゆるオフ会)が開催されました。

過去勉強会とは縁がなかったわたしも、今回は参加申込みをし、いそいそと曙橋の会場へ。
10数人という非常に適切な規模による、素敵なオフ会がスタートしました。

こういった場所に初参加のわたしは、もちろんみな初対面。CakePHP界では名の知れた方々もちらほら。
でも、IRC等ではちょっとだけ繋がりがあったりするので、遠慮することを遠慮させてもらいました。勝手に(笑)

プレミアムビールで乾杯し、ピザや差し入れのお菓子をつまみつつ、LTを聞いてあれやこれやとCakePHPについての話題を語る。
非常に幸せな空間がそこにありました。

会場はビルの屋上のテラスのようなところ。屋根を解放すると、心地よい風が吹き抜ける。
ビールが進むこと、進むこと。ぐいっぐいっ

ドイツで行なわれたCakePHPフェスタの貴重な話も聞けて、大満足な一夜でした。

counterCache (HABTMでも)

posted 2009-07-03 | written by mon_sat

カウンターキャッシュとは、hasManyしている関連Modelがあるとき、そのModelの件数を元Modelに保持しておく機能です。
http://book.cakephp.org/ja/view/81/belongsTo

たとえばArticle hasMany Comment というコメントを複数ぶらさげている記事があったとき、コメントの件数をArticle Modelに持たせることができます。
これによりわざわざコメントModelを取得することなく、コメントの件数を(記事一覧ページに)表示させるといったことが可能です。
データベースの構造からすると冗長なデータの持ち方ともいえますが、フィールドをひとつ増やすことでDBへのアクセスを減らすことが見込めますので、ぜひ積極的に活用したい機能です。

使い方は簡単です。

HABTMのcounterCacheは後述します。

まず、上記の例でArticleにあたるModelにフィールドを追加します。

  • model_name_count    integer

上記の例では comment_count です。

つぎに、関連先のModelのアソシエーションを以下のように変更します。

# models/comment.php
var $belongsTo = array(
    'Article' => array(
        'counterCache' => true,
        'counterScope' => '',
    ),
);

これだけです。
これだけで、コメントがadd , edit ,delete されるたびに、紐付いたコメントの件数がArticleに書き込まれます。

実際にデバッグモードで発行されるSQL文を確認すると、insert等のあとにselect count(*) して、Articleをupdateしているのがわかります。

HABTMでcounterCacheを使う

さて、非常に簡単なcounterCacheですが、残念ながらHABTMでは使えません。
Article hasAndBelongsToMany Tag となっているとき、ArticleにはTagの件数を、TagにはArticleの件数を持っておきたいというのが人情です。

タグ(tag)付けされた、複数の記事(article)がある状況

そこで登場するのが、bakeryに投稿されたこのビヘイビア(CounterCacheHabtmBehavior)です。
このビヘイビアのすばらしいところは、それぞれのModelに xxx_count というフィールドを用意し、$actsAsで指定すれば、それだけで良いということです。

# model
// 両方のモデルに記述します
// カウントはxxx_countというフィールドが存在しているときのみ実行されます
var $actsAs = array('CounterCacheHabtm');

なお、片方のModelのみに xxx_count を用意した場合も、何の設定も必要なく、動作いたします。
(xxx_countが存在しない場合はカウントされません)

CakePHP IRC集会 (2009年07月)

posted 2009-07-02 | written by mon_sat

昨日(2009/07/01)は、第3回CakePHP IRC集会が開催された。
今回は「平日の昼間開催」というおもしろい試みでした。
参加人数は約40人。みんな仕事中なのに何やってんだか(笑)

いつもどおりのゆるめの雰囲気で終始し、途中わたしの空気を読めない質問でCakePHP界の大御所たちの貴重な時間を使いつつ、閉会。
閉会後も残ったメンバーの一部が盛り上がり、次回の開催やオフ会の話題で夜まで話は尽きませんでした。
※オフ会は8月上旬に東京で開催予定だそうです。

今回もMASA-Pさんより話がありましたが、返す返すも「CakePHP界隈の雰囲気は良い」のです。
これは貴重なことですね。

さて、最後になりますが、ちょうど開催がCakePHP1.2ガイドブックの発売直後だったということもあり、著者を前にして「買った」「買います」の嵐。
なかには「会社の上司を説得するための必殺トーク教えて」というオーダーも。(しかもそれを著者自ら答えるというサービスぶり)

実際CakePHPガイドブックがなければCakePHPはここまで盛り上がったとは思えません。それくらい1.1版の前著はよくできていました。
その前著を50ページも増ページした今回も間違いなく「買い」です。

CakePHPはRapid Developmentに最適なツールではありますが、慣れるまで(いや慣れてからもしばしば)開発中にはまってしまうことがあります。
生産性が非常に高まるはずのCakePHPも、使用初期はその思想等に慣れるまでどうしても遠回りしてしまうことがあるのです。
そんなとき、マニュアルだけでなく、このような良書を手元に置いておくということは開発初期の生産性を保つための最善の手段です。
「多大の時間の損失を防ぐことができるでしょう」

また、前著をすでに持っている人や、すでに中級者レベルに達している人も、この本を持っておいたほうが良いと思います。
というのは、この本は、CakePHP1.0という初期からCakePHPに関わっているこの世界の重鎮による著作だからです。
ここにはCakePHPを利用するうえで非常に有効な「優れたコード」が満載です。
自分のCakePHPの使い方に「もっと良い方法がないか」と上を目指すためのヒントが見つかることでしょう。
そう考えると、この3,360円という価格は投資額として安価です。
なぜなら必ずやその何倍ものベネフィットをもたらすわけですから。

それではIRC集会に参加したみなさん。また次回。
cakephperさん、どうもお疲れさまでした。

Tree Behavior & Tree Helper

posted 2009-07-01 | written by mon_sat

CakePHPのCore BehaviorであるTree Behavior は、簡単にツリー構造のデータを扱うことができる便利なビヘイビアです。
http://book.cakephp.org/ja/complete/91/Tree

さらに、bakeryで公開されている準公式HelperであるTree Helperと併せて利用すると、その簡単さに誰もが驚くことでしょう。

得意なデータはたとえば次のようなものがあります。

  • ディレクトリ
  • カテゴリ(親子形式のあるもの)
  • ユーザーグループ
  • その他

基本的に何らかのデータをhasManyするModelが適していると思われます。

例として、複数のArticleを持つCategory Model のテーブルをみてみましょう。

  • id    integer
  • name    string
  • article_count    integer    # counterCache用 ※任意
  • parent_id    integer    # Tree Behavior用
  • lft    integer    # Tree Behavior用
  • rght    integer    # Tree Behavior用
  • その他必要なフィールド

関連付けは以下の通りです。

# Category Model
var $hasMany = array('Article');
var $actsAs = array('Tree');

# Article Model
var $belongsTo = array('Category' => array(
    'counterCache' => true, # counterCache用 ※任意
));

# Categories Controller
var $helpers = array('Tree');

これだけで準備はできました。
なお、Tree Helper は、Bakeryよりコピーしてきてください。
他のappと共用しているpluginsディレクトリがあれば、そこに入れておくとよいでしょう。

基本的な使い方

その1:ArticlesControllerで、addやeditをするときのセレクトボックス用のデータを作成する

# ArticlesController
$categories = $this->Article->Category->generatetreelist(null ,null ,null ,' ');
# views/articles/edit.ctp
echo $form->input('category_id' ,aa('label',"カテゴリ"));

参照:http://book.cakephp.org/ja/view/517/generatetreelist

その2:カテゴリの一覧を取得する

# ArticlesController
$conditions = array(
    'Category.article_count >' => 0,
); // Articleが一件以上登録されているもののみ取得する場合
$this->data = $this->Category->find('all' ,compact('conditions') );

これは通常通りに取得可能です。

その3:カテゴリパス用のデータを取得する

# CategoriesController
$parents = $this->Category->getpath( $category_id );
$this->set('parents' ,$parents);
# view
foreach ($parents as $parent) {
    $html->addCrumbs(
        $parent['Category']['name'] ,
        '/categories/index/' . $parent['Category']['id']
    );
}
$html->getCrumbs('>' ,'TOP');

viewのコードはHelperにおいておき、layoutファイルから読みだすとスマートです。

参照:http://book.cakephp.org/ja/view/235/getpath

その4:カテゴリの順序を変える

カテゴリの並びを変更するにはふたつのやり方があります。
ひとつは、親カテゴリをつけかえることです。
たとえば以下のようなツリーを想定します。

  • CakePHP
    • View
      • element
      • ヘルパー
    • Controller
      • コンポーネント
    • Model
  • レイアウト
  • CSS
  • HTML

「レイアウト」のparent_idに「View」のidを指定すれば、レイアウトがViewの子供(のカテゴリ)になるので、ヘルパーの次に表示されるようになるでしょう。

# controller
$this->Category->id = 8; // 「レイアウト」の ID
$newParentId = $this->Category->field('id', array('name' => 'View'));
$this->Category->save(array('parent_id' => $newParentId));

では、ヘルパーをelementより先に表示したい場合はどうするのかというのが二つ目の方法です。
「同じ階層の中で順序を変更するとき」はmoveUpおよびmoveDownメソッドを使用します。

マニュアルでは階層を上下させることができるような記述がありますが、あくまで同じ階層のなかで順位を変更するメソッドです。
上記の例でelementをmoveUpしてもすでに同階層の先頭に位置しているため、moveUpは失敗します。

# controller
// ひとつ下に順位を下げる
$this->Category->moveDown($this->Category->id, 1);
# controller
// ひとつ上に順位を上げる
$this->Category->moveUp($this->Category->id, 1);

参照:http://book.cakephp.org/ja/view/229/Advanced-Usage
マニュアルに詳しい使用例が載っているので参考にしてください。

その5:初期データを設定する

Tree Behaviorを使うときに真っ先に苦労するのが、初期データの登録です。

もともと何らかのデータがある場合、それをインポートするために、単純にphpMyAdminなどでinsertしようとしても、lftやrghtの値を自分でつけるわけにはいかずに右往左往してしまいます。

で、そんなときは、lft・rhtをnullのままにしておき、id,name,parent_idのみを(何らかの方法で)インポートしてください。
あとは、以下のメソッドを実行するだけで、すべてのlft,rghtが割り振られます。

# controller
$this->Category->reorder();

admin_reorderというactionにしておくとよいかもしれません。

また、recoverというメソッドもあり、何らかの不用意な操作でTree構造が壊れたときは、これで修復が試みられるそうです。

私は、まだ実際に使ったことはありません。

その6:データの削除

addやeditは通常通りsave()で行いますが、delete()してしまうと、Tree構造が壊れてしまいます。
delete()の代わりにremoveFromTree()が用意されていますので、そちらを利用しましょう。

Tips

Modelであらかじめ $order = array('lft ASC'); としておくと、常に順序よくデータを取得できます。


requestActionと通常のactionを共用することは可能か

posted 2009-06-30 | written by mon_sat

requestActionはControllerやView内において、特定のactionを呼びだすときに使います。

たとえば、Viewの一部品であるelementのなかで使用し、「最新のニュースを数件表示する」といった機能を実装できます。
elementにしておけば複数のページから呼びだすことも、キャッシュを効かすことも可能ですので、非常に重宝します。

requestActionの仕様で、URLは「通常のactionとしてアクセス可能」なため、使い方には注意が必要です。

通常のactionとして実行してほしくない場合は、actionの先頭で(requestActionで呼ばれてない場合には)404ページを出力するようにしましょう

CakePHPでは、Viewをレンダリングするのは以下のようなケースです。

  • $this->autoRenderがtrue(既定値)で、actionが終了するか$this->render()が呼ばれる、もしくはreturnが返される
  • $this->autoRenderがfalseで、$this->render()が呼ばれる

反対に次のケースでは、returnで返された値が直に出力されます。

  • $this->autoRenderがfalseで、returnが返される(かactionが終了する)

なお、requestActionによってactionが呼ばれた場合は、あらかじめautoRenderもautoLayoutもfalseとなり、何も指定していなければreturnによって返された値が返ります
そのため明示的にViewで呼びだしても、layoutされたViewの中に、さらにlayoutされたViewが入るということはありません

Viewをレンダリングして返してほしい場合は、以下のようにreturnを指定して呼びだしてください。

# view
// 第2引数にreturnを含めるとviewをレンダリングした結果を返す
// layoutは含まないviewの結果のみ
echo $this->requestAction('/controller/action/id' ,array('return') );

このケースでもlayoutはされませんのでご安心を。

requestActionと通常のactionを共用する

さてこれをふまえて、いよいよ本題です。
結論から言うと「共用は可能」です。
同じデータをレンダリングするかどうかの違いであれば、まったく意識することなく共用できますし、以下のどちらかの手順をふめば、requestActionで呼ばれたかどうかは判定可能でしょう。

  • actionの先頭でautoLayoutとautoRenderの値をチェックする
  • $this->params['requested']を確認する

前者は上述のとおりです。
後者は、requestActionの場合にセットされるようですので、次のように書くことができます。

# controller
function index() {
  $posts = $this->Post->find('all');
  if (!empty($this->params['requested'])) return $posts;
  $categories = $this->Post->Category->find('all');
}

requestActionのときは、余計なデータを読み込むことがなくなります。

追記:Viewキャッシュを使用しているときには注意が必要だった

便利なViewキャッシュですが、どうやら、requestActionで呼ばれた場合でも、すでにページ全体のキャッシュが存在する場合、そのキャッシュを表示するようです。

そのため、elementのなかでrequestActionを呼んだ場合、同じURLのキャッシュが存在するときは、elementのなかにキャッシュされたページが丸々差し込まれるという、非常に不格好なことになります。

さらにさらに、ページ丸々ということは当然にhtmlやheadが入っていますので、そもそもhtmlとしてあり得ない状態になってしまうようです。

Viewキャッシュを使用したページと同じURLをrequestActionで呼びだすことはできないと考えるしかなさそうです。
なぜなら、action内で「requestActionかどうかを判定し・・・」とやろうにも、キャッシュを表示するときはcontrollerを通りません。requestActionがキャッシュを読まない処理を施してくれない以上どうしようもなさそうです。

<< previous
|
next >>

プロフィール

@mon_sat

CakePHPをよく利用しています。

理解の浅かった半年前と、何も知らなかった一年前の自分への教科書として書いています。
当たり前のことも平易に。

RSS2.0

カテゴリ別エントリ一覧

タグ別エントリ一覧

アーカイブ