2009/11/01

cakephpはフルスタックフレームワークで進む

将来のcakephpについてCakeMatsuri懇親会でGrahamとJoelに質問してみた。
正直に言えば、知ってる英単語とサービスを並べたらどうにかがんばって理解して答えてくれた。

質問
最近pythonでもperlでもrubyでもフルスタックなフレームワークが小さなプラガブルなフレームワークに変わっている。ruby on railsさえも!
フルスタックのフレームワークcakephpはこれからもフルスタックのフレームワークなの?それとも?

答え
Graham
cakephpはこれからもフルスタックのフレームワークだよ

Joel
cakephpはこれからもフルスタックのフレームワークだよ
全部入りを求める人たち・アプリケーションとそうでないという人たち・アプリケーションは別のターゲットにいる
一番いいと思う選択を提供したい
(ORMやテンプレートエンジン選べるようにならないかな?ってきいた)
cakephp2からmodel層がActiveRecordパターンからDataMapperパターンに変わるから、DataMapperの先が今以上にフレキシブルに書けるようにはなるよ
Merbのプラガブルなのいいよね
(Doctrineの話をしてくれてよくわからなくて聞きなおして3回ぐらいかみくだいて説明してくれたけどわからなかったのでわかったことにした)

感想と余談
懇親会の話は懇親会だけにとどめておくものだという正論は忘れた。
将来のcakephpについて講演してたgrahamに聞くのが本命だったのだけど、先にgrahamに聞いてうまくいかなかった部分をブラッシュアップしてJoelに聞けたので、結果的にJoelのほうがいっぱい話を聞けた。

そのあと自分が聞きたこと聞けて満足してたら
Joel「今日のトピックのなかで何が一番エキサイティングだった?」
て逆に質問された
ぼく「将来のcakephpの話が一番だった」
ぼく「あ、次はkey-valueの話」
とってつけたみたいに付け加えちゃったので
ぼく「クラウドやスケーラブルなプログラミングにも興味あるよ!」
って自分から話振った上にあわわになるなどどうしようもなかったりもした。
共用サーバはもうありえないよな、というところで一致した。
あんまり中身のあることしゃべれなかったけどいっぱいいっぱいだったので仕方ない。
文字に起こしてみたら質問の答えも満足に得られてないことが判明したけどいっぱいいっぱいだったので仕方ない。

一生懸命聞いてくれる相手に意図を伝えられるぐらいには英語使えるようになろうと思った。
CakeMatsuriの中でぼくにとってはこの会話が一番エキサイティングだったよ、ありがとうGraham、ありがとうJoel。
I thank Graham and Joel. Talking with you is my most exciting topic in CakeMatsuri.

2009/04/30

フレームワークの話を聞いてきた

events.php.gr.jp - 第42回PHP勉強会@関東 http://events.php.gr.jp/events/show/75 に参加してきました。

CMSとフレームワークの話
CMSの中にかんたんなCRUDのアプリを入れたい、逆にフレームワークの中にCMSを組み込みたい、という要望はよくある。特に利用者の学習・習熟を考えるといちいちリセットしていられないというのはわかる。検討はしても最終的に組み込んだことはまだないので、実際に使っている(使おうとしている)話は参考になった。

複雑なSQL使ったときもモデルの便利機能使いたい話(propel)
レールから外れるときに「レールから外れるとフレームワーク使えねー」にいくのではなくどうにかしてレールに戻す方法考えれば楽なので、あとはその戻す方法だという話。
具体的には、DBのテーブルと1対1では紐づかないモデルの話。自分でレコードセット取ってきて、レコードセットを解釈してモデルの一般的なオブジェクトに落とし込むところに、自分で突っ込んでやればよい。そういうのはメソッド追っかけていけば直感で分かるとのこと。
参照だけなんだからもっとやりようあるんじゃね?とか同じモデルの中においた方が見通しよくね?とかはたぶん話の本質でないので措く。

じゃあどこにまとめるかって言うのは考える余地があると思う。syuukeisとかいうモデルは作りたくないもんなあ。

サービス志向の話(mobasif)
携帯向け軽量フレームワークの話。サービスをすばやくリリースして回収するところを志向してすべてが作られている。開発の機動力重視。アプリの層が薄いこと(=軽いこと)による処理速度重視。

ディスパッチャにユーザ認証が組み込み。
ユーザ認証は、端末固有番号を用いたいわゆる「かんたんログイン」がメイン?
いきなり書くときはべたっとSQLを書くときもある、再利用するときにはじめて切り出す。
フレームワークにも手を入れる。サービスごとのmobasifがあり、それは公開しているものともまた違う。スケルトンとしてのmobasif。

互いのサービスが疎になっていて(なっているのかな?)、インターフェイスさえ変わらなければ別にいいのかな、とも思う。

全体的に思ったこと1 テスト
フレームワークは自身のプロジェクトで十分テストが行われているので、フレームワーク自体を疑うテストコードは書かない。に染まりつつある自分としては、それを書かざるをえないのは直感的にいやだ。でも、きっとそこは見通しのよさでカバーするんだろう。というか本体に直接手を入れようと、継承してきて自分で手を入れる仕組みが出来ていようと、結局把握してないとダメなのは変わらないのか。

全体的に思ったこと2 情報が来てないのかそれとも感じていないのか
自分の話。アウトプットとインプットの比率がいびつで、アウトプット不足を個人的にはどうにかしたいのだけれど、そもそもインプットも足りてないのを実感。過多なぐらい情報はあるんだから集め方と飲み込み方が悪いんだなあと思う。

全体的に思ったこと3 ライブラリ
pear, pear2, openpear, github, coderepos, …場はいっぱいあるんだけどどうライブラリを利用したものかずっと困り中。どのライブラリがトレンドでどれがdeprecatedなのか追いつけていない。もっと低次元で言えば、検索で引っかかったブログのエントリ中のコードをコピペするのはさすがにもうやめたい。

===

Keep
会場運営ありがとうございました。
thechaw(チョー)…cakephp界のgithubを教えてもらった。

Problem
電源はあるけど自分の席まで届かなかった。次は延長コードとタップ的なものを持参する。

Try
懇親会で質問できたけど、勉強会でも質問する。
勉強会では名札してたけど、懇親会では外してしまった。つけとく。

2008/03/07

CakePHP Users in Japanの勝手に全文配信RSSを組み立ててみた

Yahoo Pipesフォーラム - CakePHP Users in Japanの勝手に全文配信RSSを組み立ててみました。
Pipes: CakePHP Users in Japan (Show All Unofficially)
http://pipes.yahoo.com/pipes/pipe.info?_id=5ad195a09b02662e1fdb1574a35e83d4

作って一人で使っているのはもったいない、でも作ったのですが作ってないわけですから(微妙なニュアンス)、おおっぴらに広めるのは直感的にまずい気もします。CakePHP Users in Japanの中の人が明確にNOと言ったら止めます。


実は「組み立ててみました」というのはかなり語弊があって、途中まで作ったところでニッチもサッチも行かなくなって「ここまで作ったんだけどわかんないよ!助けて!」ってダメもとで公式掲示板に嘘英語で質問投げたら、charsetを巧みに操るUKの超人が現れてほぼ書き直してくれました。すげえ。あとUKの人の元ネタのヘッダ変換サービス提供してくれている中国の人にも感謝。顛末のURLも載せておきます。たどると私の書いた拙い方のPipeも見られます。

Yahoo! Message Boards - Developer Help - In foreach loop, How can I use valiables? http://discuss.pipes.yahoo.com/Message_Boards_for_Pipes/threadview?m=te&bn=pip-DeveloperHelp&tid=3699&mid=-1&tof=2&rt=2&frt=2&off=1#-1

もうちょっと抽象化すればもっと使えるのかな。私にはこれで必要十分だからまあいいや。 ただ、甘やかされすぎなのもまずいのでそのうち使い方のエントリも書く予定。

2008/03/01

Remoraを眺めてみた2

Remoraを眺めてみた感想メモその2。

實松アウトプット: Remoraを眺めてみた1
http://sane.justblog.jp/blog/2008/02/remora1-e01b.html

Revision 10695: /addons/trunk/site
http://svn.mozilla.org/addons/trunk/site/

全部要る時だけbindFully

$hasMany_full,$hasAndBelongsToMany_full,$belongsTo_full
を書いておいてフル必要なときにbindFully()でバインドするっていうのはいいね。

bindModelかSQLか

条件つけたり取り出す順番だったりってのは前に書いたようにbindModelで出来る。
でもRemoraはモデルに直にSQLで書いちゃってる。まあわかりにくいもんなあ。SQLで書いちゃったほうがミスも少ないし速い(速い人は速いしそういう人が多いはず)。ただ私の場合SQL書くにもドキュメント見ながらだし、arrayで書くのとあんまり変わらない。bindModelならデータベースの差異を吸収できる(はず)っていうのはあるけど。ひょっとすると速度的な問題もあるのかな。remoraはどこみてもMySQL専で書かれているし。

bindModelの上手な使い方

addons_controllerのversionsメソッド一部抜粋

$bindusers = array('hasAndBelongsToMany' => array('User' => $this->Addon->hasAndBelongsToMany['User']));
$this->Addon->unbindFully();
$this->Addon->bindModel($bindusers);
$addon = $this->Addon->findById($id, null, null, null, null, 0);

なるほどなるほどー。一番勉強になったのはここ。このbindModelの書き方はかっこいい。そうかこれならすっきりだ。

なんだかんだで結構fatなコントローラ

基本的にSQLのクエリのべた書きが必要なもの以外はコントローラで済ませちゃってる。いくつかコントローラにSQLべた書きしているところもある。かなり効率的に分かりやすく書かれているけど、なんだかんだで結構fatなコントローラだ。getActiveUserByMail($mail)とか作ってちょっとずつ条件の違うメソッドでモデルが埋め尽くされるのとどっちがましなんだろう。これは一見thinに見えるけど結局いちいちモデル確認しなきゃいけないんだよね。あ、でも全部丸ごとモデルに押し込んじゃうとアソシエーションを設定するためにはあらかじめアソシエーションが無いと直には読めないのか。でもやっぱりコントローラ経由すれば読めるのか。やっぱりappimportしちゃえばいいのか。どっちがマシかはまだよくわからない。

まだ続くかも。そのうち。

訂正:scaffoldにBasic認証をかける

エントリの訂正です。

實松アウトプット: scaffoldにBasic認証をかける http://sane.justblog.jp/blog/2008/02/scaffoldbasic-d.html

-
var $protectedActions = array('index', 'add', 'edit', 'delete');
+
var $protectedActions = array('index', 'add', 'edit', 'delete', 'view');

に訂正します。登録データが全部見えちゃう!というか見えてた!

pmaimai Social Network Service by PHP

pmaimaiはCakePHPで作られている作りかけのSNSです。

pmaimai - Google Code
http://code.google.com/p/pmaimai/

プロフィール、日記、メッセージ、マイフレンドの機能があります。
ライセンスはMITです。
動作環境はPHP5です。
ドキュメントは0です。


形になった段階で公開する予定だったのですが、それを待っていると一生公開出来ない気がしたので出来損ないの段階で出しておきます。逆に、それっぽい形になったところでいったん終了ということにします。OpenPNEあるし。

「私はこれぐらいのことが出来るよ!」というプレゼンのつもりで基本的なものを作っていたのですが、結果的に「私にはこれぐらいしか出来ないよ!」というプレゼンになってしまっている皮肉です。

findByFooBar的なものを使っているため動作環境はPHP5です。findByFoo_bar的なものに書き換えればPHP4でも動くのかな。
管理側の機能は現状scaffoldで手動でやります。

動作サンプルを置いておきます。CakePHP1.2.x.x_24.01.2008, xrea+, PHP5, SQLiteです。
pmaimai
http://pmaimai.zapto.org/

サンプルなのでdroptableもrollbackもガシガシやっちゃうのであしからず。

続きはプロフィール・日記のアクセス権と管理側の機能実装をやる予定です。

2008/02/26

Remoraを眺めてみた1

Remoraを「読んでみた」ではなく「眺めてみた」です。感想メモその1。
Revision 10695: /addons/trunk/site
http://svn.mozilla.org/addons/trunk/site/

MVC間のデータ移動はざっくり

コントローラではモデルから受け取ったデータをざっくりビューに押し込んでいる。
サニタイズをapp_controllerでビューにセットする直前にやってる。$this->publish()ってやつ。それはビューでやりたい気も。

ビューはやっぱりモデル名やカラム名で溢れる

ビューが一見あんまりモデル名やカラム名で溢れてないって書いたけれど、前言撤回。
たとえばapp/views/addons/display.thtmlなんかそんなのでいっぱい。foreachの中で$addon['Version'][0]['File'][0]['id']こんなのとか。てことは渡されてるデータは$addons[0]['Version'][0]['File'][0]['id']みたいなのか。うげげ。

ビューの共通ロジックもコンポーネントに

コントローラごとに違う処理の場合コントローラが持っているけれど、コントローラまたいで共通のロジックの場合、コンポーネントにしてあってビューからロジックを呼び出している。
app/views/addons/recommended.thtmlだと
$prevPath = $this->controller->Image->urlForAddonPreview($addon['Addon']['id'], 1);
こんな感じ。
こういうのってたいていヘルパー使うと思うんだけどなんでなんだろう。
Imageコンポーネントの中身見てみるとモデルにアクセスしてるメソッドがあった。findByIdとか。ああだからこの辺の機能まとめてコンポーネント(コントローラ)なんだ。納得した。
あとコンポーネントのstartupで$this->controller =& $controller;ってしておけばコンポーネントからモデルにアクセスするのも簡単なのね。でもappimport(Model, Hoge)でモデル入れといてnew Hoge()した方が分かりやすいしアクセスしやすい気がしないでもない。このへんはあらかじめ規約で決めとくのかな。そっかこれならコンポーネントでコントローラのメソッドが使えるんだ。redirectとかflashとか。

真似してやってみたけれどviewからだと$this->controller->Componentでアクセスできない。何か設定が足りないかな。ビューがコントローラのオブジェクト持ってないからか。というわけでFAQを参考にコンポーネントのスタートアップでコンポーネント自身をビューに渡して、ビューからコンポーネントに$componentでアクセスできるやり方にしてみた。

どうでもいいけど眺めてたデータは五日前のRevision 10565なのに今はもうRevision 10695になってる。更新してみたら30個以上ログがあった。

続きはそのうち。

2008/02/23

重複チェックのバリデート3

重複チェックのバリデートを若干使いやすくしました。条件をつけてメールアドレスの重複チェック等に使えます。CakePHP1.2.x.x_24.01.2008で確認してます。

//重複チェックのバリデート
function checkDuplicate($data, $addconds = array())
{
	$cond = array(key($data) => '= '.current($data));
	if(($id = $this->getID()) !== false){
		$cond[$this->primaryKey] = '!= '.$id;
	}
	foreach($addconds as $key => $value){
		$cond[$key] = $value;
	}
	return ($this->findCount($cond, -1) === 0);
}

使い方はバリデーションの中で
'rule' => array('checkDuplicate', array('status' => '= active'))
例えばこんな感じに連想配列を入れてやると、statusがactiveなメールアドレスの中で重複チェックができます。findの連想配列に渡しているので付け足せる条件は連想配列のみです。

直したところ

  • 条件を付け足せるようにしたこと
  • findCountのrecursive = -1にしたことでモデル名が要らなくなったこと
  • primaryKeyをid決め打ちじゃなくしたこと

實松アウトプット: バリデーションでメールアドレスの重複登録チェック
http://sane.justblog.jp/blog/2008/02/post-9f5d.html

實松アウトプット: バリデーションでメールアドレスの重複登録チェック2
http://sane.justblog.jp/blog/2008/02/2-810c.html

2008/02/21

焼きたての感想

第2回CakePHP勉強会に参加してきました。とても刺激を受けました。感想、疑問、個人的な反省という三段構成で行きます。

感想

addons.mozilla.orgの事例

addons.mozilla.orgがcakephpで作られているというのは聞いたことがありましたが、オープンソースでやっているというのは知りませんでした。
Update:Remora - MozillaWiki
http://wiki.mozilla.org/Update:Remora

Revision 10566: /addons/trunk/site
http://svn.mozilla.org/addons/trunk/site/

ブラウザでも見られますし、subversionでひっぱってきてもいいです。/app以下をチェックアウトして持ってきたのでおいおい読んでみます。

見覚えのあるaddonsのサイト(Remora)がlocalhostで動いて、しかも下の方に今度はローカルで見覚えのあるSQLのデバッグログが出てきたプレゼンには感動しました。あとmemcache速えー。memcacheつえー。

トピックイットの事例

デバイスフリー志向でPC版やモバイル版だけでなくipod touch版やwii版を用意しているということ、PCと携帯のアクセスが半々ぐらいということが頭に残りました。振り分けはIPやUA等でやってるのかな。ビューだけ(もしくは文字コードだけ)分けるのか完全に分離しちゃうのかどんなファイルの置き方をしているんだろう。
あと以前、自分の作ったプログラムのDBからデータを返すRESTなAPIもどき(GETのパラメータに応じて毎回クエリを作ってXMLで返す)は作ったことがあるのですが、結局自分しか使わなくてあまり負荷がどうのという話にはなりませんでした。やっぱりAPIの定番の作り方について勉強しようっと。ここもキーポイントはキャッシュなのかな。
なんだかトピックイットともCakePHPとも関連が薄い感想ですみません。

懇親会

findで引っ張ってくるデータでunbindするのがめんどくさいって話を振りました。

例えばuserモデルがmailaddressモデルとnicknameモデルとpostモデルとcommentモデル持っているときに、プロフィールを編集したいときにはuserとmailaddressとnicknameだけあればよくてpostやcommentは要りません。で、まずはrecursive、次にunbindModelで取り外すわけですが、関連がある状態で不要なものをunbindModelするという形だと、テーブルが増えたときや構成変えたときに変更漏れが出てしまいます。さらに、使い始めのときはbakeが勝手にアソシエーション書き出してくれるからいいですが、テーブル増やしたり、カラム名変えたりのときは倍倍でめんどくさいです。かといって、関連まっさらにしてから必要なものを組み立てる、というのはarray()が多すぎてこれもまためんどくさいです。というような話です。

これについてアドバイスをもらいました。
Modelのアソシエーションはあらかじめ全部書いておく。それでModelは全部基本的にrecursive = -1にしておいて、必要なときにrecursiveの数字上げたりbindModelしたりすればいい、条件はあらかじめメンバ変数にでもいれておけばいいとのことです。

これでもarray()地獄なのに変わりはありませんが、一度決めておけば変更漏れも少なくなるのでこれで行こうと思います。

基本のアソシエーションだったり、必要なアソシエーションだったりは将来的にはYAML等で外に出したいところだなあ。validationをYAMLにしてたのどこかで読んだ覚えがあるので、それを参考にしてのちのち要検討。

疑問

Remoraのプレゼンの資料では、ModelからfindやfindAllで返って来たデータを、かなりあっさりコントローラーからビューにsetしてるように見えました。そうするとビューではモデル名とDBのカラム名であふれかえるような気がします。でも今ぱらぱらとRemoraのコードを眺めていますが、それほど溢れかえってる感じは無いです。かといってヘルパーに丸投げしている感じもしないですし。ここはもうちょっと読んでみます。

今自分が書いてるコードだと、モデル名やカラム名をそれぞれで隠蔽しようとしてモデルやコントローラーでいじくり回した結果、よくわからないデータをビューに渡すことになってしまっています。結局二度手間三度手間になっているのでビューでいじる方がわかりやすいのかも。

反省

懇親会で積極的にいけなかった。次はもっと話しかけよう。話した方の数が少ないのはまあいいとしても、情報提供的なことが出来なかったのがすごく失敗。情報をもらってばかりで提供することが出来なかった。つくりかけのあれかつくりかけのこれのどっちか完成させておけば一ネタになったはず。もったいないことした。

せめてフィードバックだけでもということで感想等をきちんと書いてみました。そして、貴重な勉強会ありがとうございました。

2008/02/19

scaffoldにBasic認証をかける

scaffoldにBasic認証をかけるやり方です。scaffoldを表に出すなよっていう是非論は横におきます。あと、アクションにベーシック認証をかけているだけなのでscaffoldに限らず使えます。CakePHP1.2.x.x_24.01.2008で確認してます。

CakePHP 管理者サイトでBasic認証を簡単に行うコンポーネント | Shin x blog
http://www.1x1.jp/blog/2008/01/cakephp_adminauthcomponent.html

best practices for admin part of website? - Cake PHP | Google グループ
http://groups.google.co.jp/group/cake-php/msg/4d1200e920b9fe0e

上のコンポーネントに下の投稿の発想を組み合わせて使います。

具体的には、認証コンポーネントのfunction startupの中を書き換えます。
before:

if (!preg_match("/^" . Configure::read('Routing.admin')  . "_/i", $controller->action)) {
	return;
}

after:
if (!in_array($controller->action, $controller->protectedActions)){
	return;
}

次に、認証を掛けたいコントローラ側に
var $components = array('ScaffoldAuth');
var $protectedActions = array('index', 'add', 'edit', 'delete');
という感じに認証コンポーネントと認証を掛けたいアクションを記述します。今回の場合scaffoldなのでindex,add,edit,deleteを書きます。

これでvar $protectedActionsに書いたアクションにはbasic認証がかかります。

今まで私はbakeでadmin_hogeを作ってそこに認証をかけていたのですが、構成を変えるときにMVC全部手動で書き換える必要がありました。これがめんどくさかったのでadmin用にscaffold使えないかな、という所からの発想です。私の場合、key-valueで設定組み合わせが書いてあるconfigsテーブルの編集に使ってます。

もちろん管理画面を作るに越したことはありません。が、俺俺サービスをユーザー側だけ出来た段階で早く公開したいとき等には使えそうです。ただし、パスワードがかかってるとはいえ、簡単に類推できる場所なので脆弱です。必要最小限に。

2008/02/17

アソシエーションにあるけど今要らないデータはunbindModel

アソシエーションはあるけれどfindで別にツラツラデータが欲しくない場合、まず考えるのはrecursiveで階層指定することです。それだとうまく指定できない場合にはunbindModelを使います。CakePHP1.2.x.x_24.01.2008で確認してます。

//アソシエーションにあるけど今要らないデータはunbind
$this->unbindModel(array('hasMany' => array('Mail', 'Pic', 'Post')), false);

こんな感じに外したいモデルを指定します。 逆に付け加えたい場合にはbindModelを使います。具体例としては、関連キーで紐づいて引っ張られてくるデータの条件を一時的に付け加えたい場合等です。

//引っ張ってくるニックネームのstatus条件
$this->bindModel(array('hasMany' => array('Nickname' => array('conditions' => array('status' => '= active')))), false);

こんな感じです。もともとのbelongtoやhasmanyに勝手にマージしてくれるので、追加したい条件だけ書けばOKです。

UserモデルがMail, Pic, Post, Profile, Nickname各モデルをhasmanyで持ってたとして、上記二つの指定をしたあとfindを使うと
$this->find(array('User.id' => '= '.$id,));

  • Userモデルの中からidが$idのデータを取ってくる。
  • Mail, Pic, Post各モデルのデータは見に行かないからUserモデルのidに紐づいていても取ってこない。
  • Profileモデルはuserモデルのidに紐づいているデータをごっそりもってくる。
  • Nicknameモデルの中でuserモデルのidに紐づいているデータの中からさらにstatusがactiveなデータをとってくる。

ということが出来ます。

詳しい使い方はAPI for CakePHPやソースで確認してください。

プロフィール編集のコントローラーではUserモデルとUserモデルからそれに紐づいたProfileモデルとNicknameモデルだけ必要なのに、Userモデルに紐付けられてるPostモデルやPicモデルまでごっそり引っ張ってこられても邪魔です。この場合recursiveだけで深度をうまく設定することは出来ません。富豪的にごっそり呼び出して蓄えこんでもかまいませんが、それはあんまりだってことで自分なりにやり方を考えました。

はじめは、関連キーも変数なんだからいじっちゃえばいいのでは、ということでunset()を使うことを考えました。もちろんこれでも思い通りに動作します。findの前にunset($this->hasMany['Post']);なんてやればアソシエーションがごっそり消せます。でもこれだと一時的に変更ではなく同じインスタンスの中だとその変更はずっとになってしまいます。もちろん自分でそうしたからですが。

何かいいやり方は無いかなとModelのAPI見ていると、そのまんま同じことをやっているメソッドがありました。unbindModelです。そりゃあるよね普通。一時的な変更にするかどうかも引数で設定できて一安心。それに加えてちょうど困っていた、紐づいて引っ張り出されてくるデータに条件を加えたい、という件まで逆のbindModelで解決しました。これで、必要に応じて必要なデータだけをとってくることが出来るようになりました。

上の例では外すのはモデル、付け加えるのは条件、としていますが当然それ以外もOKなはずです。でも例以外は試してません。

もう一点、本当は必要なアソシエーションだけbindModelでホワイトリスト式に動的に条件すべてをくっつけてやる方がよさそうな気もします。が、配列地獄で書くのがめんどくさいのでunbindModelでモデル名書くブラックリスト式にしてます。DBのテーブルが果てしなく増えてきたときにはホワイトリスト式を考えます。

2008/02/16

メールはビューで作れば楽

CakePHPからメールを送る続き。メール本文はビューで作っちゃえば楽ちん。

//メール本文を作る
$this->set('id', $id);
$this->set('mail', $mailAddress);
$this->set('token', $token);
ob_start();
$this->render(null, '/email/text/default', '/email/user_register');
$body = ob_get_clean();

$thisはコントローラーです。

app/views/layouts/email/text/default.ctp
にてテキストメールの共通テンプレートを設定します。署名等。
app/views/email/user_register.ctp
にてメールの本文を書きます。もちろんsetした変数が使えます。

あとは前述のJPHPGMailer でsetBody($body)してやればオッケーです。

CakephpからGmailで日本語メール送信

CakePHPからGmailで日本語メールを送信するやり方です。

:: PHP Mailer ::
http://phpmailer.codeworxtech.com/

PHPで日本語メールを送る - 応用編 (添付ファイル、HTMLメール) - EC studio 技術ブログ
http://techblog.ecstudio.jp/tech-tips/mail-japanese-advance.html

のPHPMailerとJPHPMailerを使います。 メール送信用のアカウントはそう頻繁に送信毎に変えるものでも無いので、ラップするクラスにまとめた方が楽です。

まあ別にGMailに限ったものじゃなくてSMTP全般に使えますが、自分が使うのがGmailなので こんな名前にしてます。 やってることは設定読み込んで自分の環境に合わせてるだけなのであしからず。

/vendors/phpmailer/jphpgmailer.php:

<?php
vendor('phpmailer/jphpmailer');
class JPHPGMailer extends JPHPMailer{
	function __construct($account = 'gmailaccount', $encoding = 'UTF-8', $language = 'ja')
	{
		$this->set('in_enc', $encoding);
		$this->SetLanguage($language);
		
		$this->IsSMTP();
		$this->set('SMTPAuth', true);
		
		//Gmailの設定読み込んでセット
		Configure::load($account);
		$this->set('Host', Configure::read('Gmail.host'));
		$this->set('Port', Configure::read('Gmail.port'));
		$this->set('Username', Configure::read('Gmail.username'));
		$this->set('Password', Configure::read('Gmail.password'));
	}
}

app/config/gmailaccount.php

<?php
$config['Gmail']['username'] = 'example@gmail.com';
$config['Gmail']['password'] = 'password';
$config['Gmail']['host'] = 'ssl://smtp.gmail.com';
$config['Gmail']['port'] = 465;

私の場合、/vendors/phpmailer/以下にclass.phpmailer.php, class.smtp.php, jphpmailer.phpを置いてます。
インスタンス生成がnew JPHPGMailer() になるだけで、あとの使い方はJPHPMailerと同じです。

2008/02/12

バリデーションでメールアドレスの重複登録チェック2

更新時にも使えるように重複登録チェックをバージョンアップしました。

http://sane.justblog.jp/blog/2008/02/post-9f5d.html#comment-8397038
新規登録はこれでOKだと思いますが、更新の時は、エラーになってしまうのでは?
投稿: toshiyuki_saito | 2008/02/11 22:48

確かにその通りです。

 

//テーブル名のゲッタ
function getTableName() 
{
	return $this->name;
}

//重複チェックのバリデート
function checkDuplicate($data) 
{
	$cond = array(key($data) => '= '.current($data));
	if(($id = $this->getID()) !== false){
		$cond[$this->getTableName().'.id'] = '!= '.$id;
	}
	return ($this->findCount($cond) === 0) ? true : false; 
}

これで更新OKなはず。

はじめ $cond['id'] = '!= '.$id; にしたら

Warning (2): sqlite_query() [function.sqlite-query]: ambiguous column name: id [CORE\cake\libs\model\datasources\dbo\dbo_sqlite.php, line 115]
Query: SELECT COUNT(*) AS "count" FROM "mails" AS "Mail" LEFT JOIN "users" AS "User" ON ("Mail"."user_id" = "User"."id") WHERE "mail" = 'hogehoge@example.com' AND "id" != '6'
Warning (512): SQL Error: 1: SQL logic error or missing database [CORE\cake\libs\model\datasources\dbo_source.php, line 439]

idってどのidなのかわかんないよ!って怒られたのでテーブル名を入れました。

2008/02/11

バリデーションでメールアドレスの重複登録チェック

結論から言うと、メンバ変数+αで簡単に重複チェックできるCakePHP1.2系は偉いです。

メールアドレスの重複登録を防ごうとしたけれど、やり方がわかりませんでした。なんとなくbeforeSave()かなあと思って取り掛かりましたが、そこから先に進めず。どうやらparent::validates()がキーワードらしいのでいろいろググりました。

CakePHP - validate | Shin x blog
http://www.1x1.jp/blog/2006/08/cakephp_validate.html

CakePHP モデルの validation の拡張 | Sun Limited Mt.
http://www.syuhari.jp/blog/archives/139

参考にしたのはこの辺。validatesの返り値やエラーメッセージの入れ方をAPI for CakePHP とにらめっこしながら、頭をひねってモデルの中を書きました。

function validates($data = array()) 
{
	if(!parent::validates($data)){return false;}
	
	$cond = array('mail' => '= '.$this->data['Mail']['mail']);
	if($this->findCount($cond) === 0){
		return true;
	}else{
		$this->invalidate('mail', 'そのメールアドレスは登録されています');
		return false;
	}
}

期待通りに動くので、もちろんこれでもOKです。ただ、ん?これ普通にバリデーションで出来ね?と思ったので書き直してみました。

var $validate = array(
	'mail' => array(
		'duplicate' => array(
			'rule' => 'checkDuplicate',
			'message' => 'そのメールアドレスは登録されています',
		)
	), 
); 
function checkDuplicate($mail) 
{
	$cond = array('mail' => '= '.current($mail));
	return ($this->findCount($cond) === 0) ? true : false;
}

うわーん。こっちなら調べる必要なかった上に一目瞭然。上は数時間、下は3分。同じ轍踏む人が減るように書き残しておきます。validates()の書き方覚えたのもいつか役に立つといいなあ。 ついでにもうちょっとだけ汎用性高いものも書き残しておきます。

function checkDuplicate($data) 
{
	$cond = array(key($data) => '= '.current($data));
	return ($this->findCount($cond) === 0) ? true : false;
}

2008/02/09

migration v3.6がSQLite(MDB2 v2.5.0a2)では期待通りに動かない

CakePHPでマイグレーション使うのに便利そうな
cakephp-migrations - Google Code
http://code.google.com/p/cakephp-migrations/

のmigration v3.6ですが、SQLiteだと期待通りに動きません。結論から言えばPEAR MDB2のバグだそうです。

期待通り動かない例は以下。

001_create_users_table.yml
UP:
 create_table:
   users:
     mail: string
     pass: string
DOWN:
 drop_table: users

これでmigrateすると期待通りに動きます。show create table statementで表示すると

CREATE  TABLE users (id INTEGER NOT NULL PRIMARY KEY, mail VARCHAR(255) DEFAULT NULL, pass VARCHAR(255) DEFAULT NULL, created DATETIME DEFAULT NULL NOT NULL, modified DATETIME DEFAULT NULL NOT NULL)

しかし、

002_add_cols_to_users.yml
UP:
 add_field:
   users:
     is_active: boolean
DOWN:
 drop_field:
   users: is_active

これでmigrateすると期待していた

CREATE  TABLE users (id INTEGER NOT NULL PRIMARY KEY, mail VARCHAR(255) DEFAULT NULL, pass VARCHAR(255) DEFAULT NULL, created DATETIME DEFAULT NULL NOT NULL, modified DATETIME DEFAULT NULL NOT NULL, is_active BOOLEAN
DEFAULT NULL)

ではなく

CREATE  TABLE users (id INTEGER NOT NULL, mail VARCHAR(255) DEFAULT NULL, pass VARCHAR(255) DEFAULT NULL, created DATETIME DEFAULT '1970-01-01 00:00:00' NOT NULL, modified DATETIME DEFAULT '1970-01-01 00:00:00' NOT NULL, is_active BOOLEAN DEFAULT NULL)

となってしまい、idのPRIMARY KEYが消えてしまいます。

困ったので例によって嘘英語でバグを報告してみました。するとJoelMossから返事があって、migration側はちゃんとデータを投げているのにMDB2側にバグがあるそうです。
Issue 1 - cakephp-migrations - Google Code
http://code.google.com/p/cakephp-migrations/issues/detail?id=1

migrateのソースを見ても私の能力不足で何をしているのかよくわかりません。いわんやMDB2をやなので、これ以上は無理です。PEARにどうバグ報告していいのかも分かりませんし。
現時点のMDB2 最新alpha v2.5.0a2 でダメなので、SQLiteでmigration使いたい人はMDB2がバグフィックスされるまでしょんぼり待ちましょう。

2008/02/07

debug 2以上で表示されるSQLクエリの数を増やす

CakePHPのdebug 2以上で表示されるSQLクエリは200件までです。これを増やすには

/cake/libs/model/datasources/datasource.php
before:
var $_queriesLogMax = 200;
after:
var $_queriesLogMax = 600;
ぐらいに増やせばOKです。増やすとメモリ食うぜ!って警告が書いてあったので必要そうな分だけにします。

大量に登録する場合、デフォルトの200件だと本数が足りなくなります。クエリ自体は通ってるので単純に表示の問題のようです。ただ、増やそうと思ったけれど設定が見つかりません。それっぽいところを追いかけてみてようやく設定を見つけました。

はじめは/cake/の中身をいじりたくなかったので、
/cake/libs/model/datasources/datasource.php
を丸々
/app/model/datasources/datasource.php
にコピーしてきて
var $_queriesLogMax = 600;
としてみましたが、変化無しでした。appの方は読み込まないみたいです。 仕方なく上記の通り/cake/をいじりました。

それにしてもqueriesLogMaxで検索してgoogleでもyahooでも世界中で0件っていうのはどういうことなんだろ。一応期待通りに動作はしているものの触っちゃいけないところなのかもしれません。世界中でゼロってことは無いでしょさすがに。セッタを見逃してる予感。

_queriesLogMaxで検索したらちょこちょこ出て来ました。よかったよかった。

2008/02/05

SQLiteでTransaction behaviorを使う

Transaction behavior | The Bakery, Everything CakePHP : Articles http://bakery.cakephp.org/articles/view/transaction-behavior

を便利に使っていたのですがSQLiteだとこんな↓エラーが出ました

Warning (2): sqlite_query() [function.sqlite-query]: near "SET": syntax error [CORE\cake\libs\model\datasources\dbo\dbo_sqlite.php, line 115]

Query: SET autocommit=0

Warning (512): SQL Error: 1: SQL logic error or missing database [CORE\cake\libs\model\datasources\dbo_source.php, line 439]

transaction behavior がおかしいのれす。あらためてtransaction behavior をザーッと見てみたところ、これMySQLで決めうちなのね…

とりあえずautocommit関連設定を全部falseにする。 さらにメンバメソッドのbeginの中を

before:
$model->query('SET autocommit=0');
after:
if(strpos(ConnectionManager::getDataSource($model->useDbConfig)->description, 'SQLite') === false){$model->query('SET autocommit=0');}

に変更。SQLiteはBEGINされると勝手にautocommitがOFFになるっぽいのでこれでよさそう。 自分のつないでいるDBがどの種類かってなんかもっと分かりやすいのがありそうな気がするけど見つけられず。暫定的にこれで。

beginとcallbackとcommitだけでいいからCakePHP用各DBのbehavior誰か書いてください。誰も書かなかったらそのうち調べて書こう。

2008/02/02

CakePHPでmyappを作り始めるまでの準備その2(bakeでざっくり)

實松アウトプット: CakePHPでmyappを作り始めるまでの準備その1(TortoiseSVNとbake)
http://sane.justblog.jp/blog/2008/02/cakephpmyapp1to.html

の続き行きます。

  1. myapp/configの中のbootstrap.php.default, core.php.default, routes.php.default, database.php.default をコピーしてファイル名から.defaultを削ります。.default無しの方はSVNコミットの際に無視リストに入れて追加しないようにします。
  2. (SQLiteを使うなら)myapp/myapp.sqliteをつくり、これもSVNコミットの際に無視リストに入れます。
  3. myapp/config/core.phpのSecurity.saltの値を書き換えます。
  4. myapp/config/database.phpを編集します。'encoding' => 'utf8'を書きます。これはdatabase.php.defaultにも書き加えておきます。また、'database' =>のところがwebrootから見たパスになるのでSQLiteを使う場合は注意です。フルパスで書いておけば気にしなくていいです。ちなみにこの4はbakeでも出来ますがリネームしたり何なりのついでに編集してしまった方が早いです。
  5. DBにテーブルを作ります。MySQLならphpMySQLAdmin、SQLiteならTkSQLiteが便利です。
  6. c:/path/to/cakephpからコマンドプロンプトを起動します。bakeを使ってテーブルの数だけモデルを作ります。
    c:/path/to/cakephp>cake/console/cake bake -app myapp
    アソシエーションだけ対話式でさっさと作ってしまいます。
  7. bakeを使ってモデルの数だけコントローラを作ります。scaffoldはとりあえず無しで。admin用のindex, edit, add, deleteも作ります。
  8. コントローラに合わせてビューもbakeでざっくり自動生成します。
  9. 作ったコントローラからadmin関連以外のメンバメソッドは消してしまいます。 同様にビューからもadminでないファイルを消してしまいます。これでadmin経由で全部のデータがwebから編集できます。
    このやりかたの利点はモデルのバリデートが活きることです。phpMySQLAdmin等でアクセスした場合には当然全部編集出来るので、そこは使い分けです。

以上です。あとは好きなだけ作りたいように作ればオッケーです。ただしこのやりかただとカラムを変更した場合、admin用のビューをまた作り直す必要があります。まだ試していませんが、admin関連の時だけscaffoldをonにした方が楽かもしれません。

2008/02/01

CakePHPでmyappを作り始めるまでの準備その1(TortoiseSVNとbake)

それぞれやり方があると思いますが、自分なりのやり方を備忘録がてら書いてみます。書いている時点でのCakePHPのバージョンは1.2.x.xのrev6418です。WindowsXPSP1です。TortoiseSVNをつかっています。

  1. これからmyappを作ります。
  2. c:/path/to/repositoryにmyappディレクトリを作ります。そしてc:/path/to/repository/myappにTortoiseSVNでリポジトリを作成します。(私の場合アプリごとにリポジトリを分けています)
  3. c:/path/to/cakephpからコマンドプロンプトを起動します。bakeを使います。

    c:/path/to/cakephp>cake\console\cake bake -app myapp

    App : myapp
    Path: c:/path/to/cakephp/myapp
    ---------------------------------------------------------------
    Bake Project Skel Directory: c:/path/to/cakephp/cake/console/libs/templates/skel
    Will be copied to: c:/path/to/cakephp/myapp

    確認にyesを入力するとmyappにファイルがコピーされました。
  4. myapp/configの中のbootstrap.php, core.php, routes.phpをそれぞれbootstrap.php.default, core.php.default, routes.php.default にリネームします。
  5. myappをfile:///c:/path/to/repository/myapp/trunkにTortoiseSVNでインポートします。
  6. myappの中身をいったんまっさらに消して、SVNチェックアウトします。リポジトリはfile:///c:/path/to/repository/myapp/trunkです。この作業は馬鹿っぽいのでおそらくいったん消さずに済むちゃんとしたやり方があるはずです。

ひとまずここまで。 次はDBにテーブルを作って、bakeで一気にアソシエーション等を済ませます。

CakePHPでハマったことその5(トップページとセッションID)未解決

セッションIDが付加されている状態でトップページにアクセスするとそんなコントローラ無いって怒られました。

【PHP】フレームワーク CakePHP 2ホール目
http://pc11.2ch.net/test/read.cgi/php/1194976228/321

321 nobodyさん sage 2008/01/28(月) 01:30:21 ID:???
app/config/routes.phpで
Router::connect('/', array('controller' => 'pages', 'action' => 'display'));
て設定してる時にクッキー消して(use_trans_sid onで)
http://myapp/?CAKEPHP=e233bd9c1facda8084d8ba2f2226eb60 でアクセスすると
Error: CAKEPHPe233bd9c1facda8084d8ba2f2226eb60Controller could not be found.
そんなコントローラーねえよって怒られちゃう
Router::connectの書き方誰か教えてくらさい

このあと
Routing Question how to use Router::connect('/') with session_id? - Cake PHP | Google グループ
http://groups.google.co.jp/group/cake-php/browse_thread/thread/677a044cbe3bf889#

にマルチポスト。

英語が拙すぎる上にセッションIDについて間違ってたのはご愛嬌。CakePHPコアのデベロッパーの人が「それ最新のブランチなら修正されてるよ」って教えてくれました。「最新のブランチ入れるのは怖い」なんて開発者に言えずアップデートしました(つもりでした)。でも問題は解決しなかったので、セッションIDを保持したままトップページに飛ばさないよう変更しました。

後ろ向きですがこれで自分のサイトでは解決、ハマりポイントとしては未解決です。

余談ですが、その後その4でハマったSQLiteのエラーが出ているので、アップデートしたつもりがちゃんとアップデートできて無かったということが判明します。ただ、その後もう一度確認しなおしたところちゃんと(?)同じエラーが出たので一安心(?)でした。

CakePHPでハマったことその4(SQLiteでのupdate)

MySQLの場合期待通りに動いていたコードがSQLiteに変えたらエラーを出すようになってしまいました。

Warning (2): sqlite_query() [function.sqlite-query]: near "AS": syntax error [CORE\cake\libs\model\datasources\dbo\dbo_sqlite.php, line 115]

表示されたエラーで検索したところ

コスミー報告書[社外秘] - CakePHPではまったこと15(sqliteを使っているときのupdate)
http://red-treasure.com/report/?p=160

これがドンピシャ。1.2.0.6311をnightlyにアップデートすれば直るそうだ。ちょうど別件でも要アップデートだったので(結局別件の方は解決しなかったのですが)現時点の1.2系latest branchであるrev6418に突撃。
/branches/1.2.x.x - CakePHP : The Rapid Development Framework for PHP - Trac
https://trac.cakephp.org/browser/branches/1.2.x.x?rev=6418

SQLiteでのupdateのエラーが解決しました。

CakePHPでハマったことその3(バリデーション)

CakePHPでハマったことその3もバリデーションです。 メンバメソッドを作ることで自分なりのバリデーションを作ることが出来ます。 その際メンバメソッドに渡されるデータが生のdataではなくarray('カラム名' => data)なところでハマりました。

具体的には、

var $validate = array(
       'name' => array(
               VALID_NOT_EMPTY,
               'length' =>array(
                       'rule' =>array('between', 2, 50),
                       'message' => '名前は2-50文字までです',
               ),
               'kaigyo' => array(
                       'rule' => array('kaigyoLessThanN', 0),
                       'message' => '名前は改行できません',
               ),
       ),
);
//改行n以下のバリデート
function kaigyoLessThanN($data, $numKaigyo)
{
       $bool = false;

       $pattern = "`\r\n|\n|\r`u";
       $count = preg_match_all($pattern, $data, $array);
       if($count === false){
       }elseif($count <= $numKaigyo){
               $bool = true;
       }
       return $bool;
}

のバリデートが期待通りに動きませんでした。連想配列が渡されるので以下のように修正することで期待通り動くようになりました。改行の数を数えるので4行以内にしたい場合にはパラメータで3を渡せばOKです。

//改行n以下のバリデート
function kaigyoLessThanN($data, $numKaigyo)
{
       $bool = false;

       $pattern = "`\r\n|\n|\r`u";
       $count = preg_match_all($pattern, current($data), $array);
       if($count === false){
       }elseif($count <= $numKaigyo){
               $bool = true;
       }
       return $bool;
}

CakePHPでハマったことその2(バリデーション)

var $validate = array(
        'user_id' => VALID_NOT_EMPTY,
        'url' => array(
                'rule' => 'url',
                'message' => 'url ja nai yo'
        ),
        'count' => VALID_NUMBER
);

ならOKで

var $validate = array(
        'user_id' => array(
        VALID_NOT_EMPTY,
               'message' => 'user id pleeeeeeze',
        ),
        'url' => array(
               'rule' => 'url',
               'message' => 'url ja nai yo',
        ),
        'count' => array(
               VALID_NUMBER,
               'message' => 'suuji ja nai yo',
        )
);

だと
Warning (2): preg_match() [function.preg-match]: Delimiter must not be alphanumeric or backslash [hoge\cake\libs\model\model.php, line 2113]
のエラーが出ます。messageの使い方が間違ってるのかな。でも上の例でURLじゃ無かった場合url ja nai yoが出てるから、何か勘違いかも。

解決しました。

var $validate = array(
        'user_id' => array(
                'rule' => VALID_NOT_EMPTY,
                'message' => 'user id pleeeeeeze',
        ),
        'url' => array(
                'rule' => 'url',
                'message' => 'url ja nai yo',
        ),
        'count' => array(
                'rule' => VALID_NUMBER,
                'message' => 'suuji ja nai yo',
        )
);

これでオッケーーイ

CakePHPでハマったことその1(実はMySQLでした)

基本どおりにブログチュートリアルどおりに進めたが
SQL Error: 1366: Incorrect integer value: '' for column 'id' at row 1
というエラーが出て登録が出来ない。

検索してみると
SQL error when adding item using the scaffolding - Cake PHP | Google グループ
http://groups.google.com/group/cake-php/browse_thread/thread/a0b1a35c1909d5db/

ということでMySQLの設定が厳しすぎたからだという。''をnullと解釈してくれないようだ。 ''をnullと解釈することの方が間違っている気はしないでも無いけど、Cakeの中身がよくわからないのでここはCakeに合わせておく。

今度はSTRICT_TRANS_TABLESでぐぐるとopenPNEらへんの質問掲示板のログがヒット。my.iniの
sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
をコメントアウトしておっけい。

2008/01/31

ネット大喜利 ODON - Ogiri Dynamic Operation Network

實松アウトプット: 作るもの Ogiri Dynamic Operation Network http://sane.justblog.jp/blog/2007/09/ogiri_dynamic_o_666f.html
で書いたネット大喜利のシステムが動くようになりました。

ODON - Ogiri Dynamic Operation Network
http://odon.zapto.org/

まだ実用には程遠いですがCakePHPで作った一つ目のアプリということでリリース。

ボケましーんと比較した特長

  • 名前欄にトリップ実装
  • 自分の投稿したボケ、投票したボケを見て修正できる(現在はクッキーあり限定)
  • お題とお題の間のインターバルを管理人が設定可能
  • 不正なフォームからの投稿を拒否
  • 過去ログが読みやすい

実用するには要改善な点

  • IPアドレスで多重投稿規制しているのでDocomoは投稿投票し放題
  • 文字コードがUTF-8なので読めない携帯が多い
  • 残り時間はカウントダウンしたい
  • 無造作にSQLを投げまくってるので必要なものを必要なだけとってくるようにする
  • 現状毎回すべて動的にDBから生成しているのでキャッシュを有効活用したい
  • 過去ログ検索機能
  • 結果をxmlやjson等で出力
  • クッキーを受け入れない場合のセッションIDの扱いについて理解不足

これから数回にわたって間違って理解していたことや自分なりのCakephpやBakeの使い方を書いてみます。1.2系です。

2007/10/07

アソシエーション確認のコントローラとビュー

テストデータを作ったあとに、コントローラとビューを作りました。関連テーブルからちゃんと引っ張れているかどうかの確認です。

controllers/subjects_controller.php

<?php
loadModel("Subject");
class SubjectsController extends AppController{
       public $uses = null;
       function view($id = null) {
               $dao = new Subject();
               $this->set('subject', $dao->findById($id));
       }
}

views/subjects/view.thtml

<?php print_r($subject);?>

ボケ、コメント、投票にも同様に暫定のコントローラとビューを作りました。

http://path/to/subjects/view/2
等のURLを叩くと、関連付けたテーブルからきちんとデータを引っ張れていました。よしよし。

ただ、お題とボケ、ボケと投票、お題とコメントは全て1対多で紐付けてありますが、お題と投票が1対多になるのは二段階での紐付けなのでデフォルトだと引っ張れてきていません。逆もまた然りでこのままだとお題テーブルしか必要ないときでも一段階掘り進んじゃうのかもしれません。ここはあとで要見直しです。

また、イマイチわかっていなかったところ、コントローラーからビューにデータを受け渡す際どうするのか、についてのマニュアルの記述
> set() で1度設定すると、ビューの中で変数が使用できるようになります。コントローラで set('color', 'blue') を実行すると、ビューの中で、 $color 変数が使用できるようになります。
この記述がようやくピンと来ました。コントローラでセットした変数をペタペタ貼るなり展開するなりのイメージを持てばいいのね、ビューで。

○モデルにアソシエーションだけ入れた ×モデルにリレーションだけ入れた

hasManyやbelongsToはモデル間のリレーションではなくてモデル間のアソシエーションでした。

實松アウトプット: モデルにリレーションだけ入れた
http://sane.justblog.jp/blog/2007/10/post_db57.html

2007/10/03

モデルにリレーションだけ入れた

これでいいはず、といってもテストデータもコントローラもビューもないのでデータをちゃんと引っ張り出せているかどうかわかりません。そもそもphp的にエラーがあるかどうかすらわかりません。

動かしてみてもし一発で動けば問題はないものの、そんなことあるわけありません。むしろ一発OKを想定してはいけないはずです。切り分け出来ないから。

というわけで次の作業はテストデータ作成にします。

models/subject.php

<?php
class Subject extends AppModel {
       public $name = 'Subject';
       //answer(ボケ),Comment(コメント)をhasMany
       public $hasMany = array('Answer' =>
                               array('className'  => 'Answer',
                                     'foreignKey' => 'subject_id',
                                     ),
                               'Comment' =>
                               array('className'  => 'Comment',
                                     'foreignKey' => 'subject_id',
                                     ),
                               );
}

models/answer.php

<?php
class Answer extends AppModel {
       public $name = 'answer';
       //vote(投票)をhasMany
       public $hasMany = array('Vote' =>
                               array('className'  => 'Vote',
                                     'foreignKey' => 'answer_id',
                                     ),
                               );
       //subject(お題)にbelongsTo
       public $belongsTo = array('Subject' =>
                                 array('className'  => 'Subject',
                                       'foreignKey' => 'subject_id',)
                                 );

}

models/vote.php

<?php
class Vote extends AppModel {
       public $name = 'Vote';
       //answer(お題)にbelongsTo
       public $belongsTo = array('Answer' =>
                                 array('className'  => 'Answer',
                                       'foreignKey' => 'answer_id',)
                                 );

}

models/comment.php

<?php
class Comment extends AppModel {
       public $name = 'Comment';
       //subject(お題)にbelongsTo
       public $belongsTo = array('Subject' =>
                                 array('className'  => 'Subject',
                                       'foreignKey' => 'subject_id',)
                                 );
}

お題以外のテーブルのSQL(ボケ、投票、コメント)

CREATE TABLE 'answers' (
   id INTEGER PRIMARY KEY UNIQUE NOT NULL,
   name TEXT NOT NULL,
   answer TEXT NOT NULL,
   host TEXT NOT NULL,
   mobilenum TEXT DEFAULT '',
   created TEXT NOT NULL,
   modified TEXT NOT NULL,
   subject_id INTEGER NOT NULL
)
CREATE TABLE 'votes' (
   id INTEGER PRIMARY KEY UNIQUE NOT NULL,
   answer_id INTEGER NOT NULL,
   host TEXT NOT NULL,
   mobilenum TEXT DEFAULT '',
   created TEXT NOT NULL,
   modified TEXT DEFAULT ''
)
CREATE TABLE 'comments' (
   id INTEGER PRIMARY KEY UNIQUE NOT NULL,
   subject_id INTEGER NOT NULL,
   comment TEXT NOT NULL,
   host TEXT NOT NULL,
   mobilenum TEXT DEFAULT '',
   created TEXT NOT NULL,
   modified TEXT DEFAULT ''
)

テストデータ作らないと何がなんだか。

2007/09/24

RewriteBaseをきちんと設定

/webroot の.htaccessがブログチュートリアルのままでおかしかったのでapacheのrootからのパスにきちんと変えました。具体的には /path/to/apacheroot/odon/.htaccess のRewriteBase を

RewriteBase /odon

にしました。このエントリ、順序的にはMVCのエントリの前に入ります。

参考
【PHP】フレームワーク CakePHP
http://pc11.2ch.net/test/read.cgi/php/1163850802/21
> 21 nobodyさん sage 2006/11/27(月) 09:17:16 ID:???
> http://localhost/cake_blog_tutorial/にCakePHP入れてチュートリアルやってたんだけど、無効なURL〜みたいなエラーが発生。
> mod_rewriteが悪いのかな?と思いつつ調べてみたら.htaccessのRewriteBaseを設定しないといけないと書いてあった。
>
> /.htaccess
> RewriteBase /cake_blog_tutorial
>
> /app/.htaccess
> RewriteBase /cake_blog_tutorial/app
>
> /app/webroot/.htaccess
> RewriteBase /cake_blog_tutorial/app/webroot
>
> これで動作した。同じ事で初っ端から躓く人が居るかもしれないからここに書き残しておく。
> (躓いたおかげでチュートリアルに1時間半かかったw)
>
> 参考:orihasam's log
> ttp://d.hatena.ne.jp/orihasam/20060809

2007/09/23

作るもの Ogiri Dynamic Operation Network

今作っているものはweb大喜利のシステム Ogiri Dynamic Operation Network です。早い話が
ボケましーん
http://bok-1.presen.to/labo/index.html
のパチものです。

  • この配布スクリプトにいくつか不満があること
  • ライセンスが改造OK再配布NGであること
  • なによりCakePHPを使ってみたいこと

この理由から、手を入れるのではなく1から作り始めることにしました。

システムの流れとしては お題→回答→投票+コメント→結果 を繰り返しで表示します。DBテーブル的にはお題、ボケ、投票、コメント、規制の5つがあればいいはずです。

システムの基本部分は簡単に出来そうです、がそこを作るのにもモタモタしている次第です。そしておそらく山場は携帯対応(SJIS←→UTF-8)になりそうです。

ログインシステムと携帯対応はwebアプリ作る上で避けて通れません。というか今まで自分が携帯からほとんどweb利用しないこともあって携帯対応は避けてきたんですが、そろそろ覚えておいたほうが便利そうなのでやってみます。

以下断片的にお題subject、ボケanswer、投票vote、コメントcomment、で表記が揺れますがそこは適宜読み替えてください。

MVCのひな型

models/subject.php

<?php
class Subject extends AppModel {
}

controllers/subjects_controller.php

<?php
loadModel("Subject");
class SubjectsController extends AppController{
       public $uses = null;

       function index() {
               $dao = new Subject();
               $this->set('subjects', $dao->findAll());
               }
}

views/subjects/index.thtml

<h1>投稿</h1>
<table>
   <tr>
       <th>Id
       <th>名前</th>
       <th>お題</th>
   </tr>

  <!-- ここで、 $subjects 配列のループをまわして、投稿情報を printing out します。 -->

   <?php foreach ($subjects as $subject): ?>
   <tr>
       <td><?php echo $subject['Subject']['id']; ?></td>
       <td>
           <?php echo $html->link($subject['Subject']['name'],
"/subjects/view/".$subject['Subject']['id']); ?>
       </td>
       <td><?php echo $subject['Subject']['subject']; ?></td>
   </tr>
   <?php endforeach; ?>

</table>

MVCを一気に。ただ、subjectにコントローラやビューって必要なのかな?お題一覧のテーブルを出してそこを表示したり変更したりするような機能は要らない気がするんだなあ。何に何が必要なのかがイマイチよくわかってません。余計なものを作ってしまった分には後で消すのは簡単なのでとりあえずそういうことで。

この辺を参考にしました。
Blog-side CakePHP わかりずらい3点
http://blog.takeda-soft.jp/blog/show/190
Blog-side CakePHP わかりずらい+2点
http://blog.takeda-soft.jp/blog/show/192

2007/09/22

セットアップとDBの準備

xreaにワラワラwebアプリ置くことを考えると

  1. CakePHP のコアライブラリ - /cake
  2. アプリケーションコード(コントローラ、モデル、レイアウト、ビューなど。) -/app
  3. アプリケーションの webroot ファイル(画像、javascript, CSS など) -/app/webroot

1はすべて共通でpublic_html外
2はアプリごとにpublic_html外
3はアプリごとに(ドメインごとに)public_html内

にそれぞれ置く必要があります。

3.4.?CakePHP のセットアップ
http://cakephp.jp/doc/ch03s04.html
の日本語マニュアルにそのまんまの記述があるので指示通りに設定しました。

次はDBの暫定テーブルです。

CREATE TABLE 'subjects' (
   id INTEGER PRIMARY KEY UNIQUE NOT NULL,
   name TEXT NOT NULL,
   subject TEXT NOT NULL,
   status TEXT NOT NULL,
   host TEXT NOT NULL,
   mobilenum TEXT DEFAULT '',
   answersec INTEGER NOT NULL,
   votesec INTEGER NOT NULL,
   created TEXT NOT NULL,
   modified TEXT DEFAULT ''
)

今のところDBはこんな感じにしておきます。

次にデータベース接続の設定config/databaseです。

class DATABASE_CONFIG {
       var $default = array(
               'driver' => 'sqlite',
               'persistent' => false,
               'host' => '',
               'login' => '',
               'password' => '',
               'database' => '../../path/to/database.db',
               'prefix' => ''
       );
}

webroot/indexの中で設定した定数をDBのパスに使おうとしてダメだったので、とりあえず相対パスで指定しました。

ブログチュートリアル設定の名残(config/routes.phpがそのまま)だからpostsコントローラがないぞ!って怒られますが、これは次以降のエントリで。

追記 2007.09.23

データベースのパスをフルパスに変えました。

class DATABASE_CONFIG {
	public $default = array(
		'driver' => 'sqlite',
		'persistent' => false,
		'host' => '',
		'login' => '',
		'password' => '',
		'database' => 'c:/path/to/database.db',
		'prefix' => ''
	);
}

cakeちょっとずついじる宣言

CakePHPのチュートリアルを動かしてみてもいまいちわかったようなわかってないような状態。さらには、いろんな人が掲示板なりblogなりで書いているtips的なところまで自分が追いついていないので、みんなが何を喋っているのかわからない。

かといって基本的なシステムを備えたものを習作として作る、というのは性に合わない。やっぱり例によって自分が欲しい適当なものを作っていきなり実戦投入してみる、という方向性で。

  • 毎日ちょっとずつでもいいから手を動かす
  • 箱(デザイン)から決めるとモチベーションが上がる
  • ちょっとずつ書くことで諦めにくくする

このあたりの先人の知恵を参考にしていこう。

2007/09/16

Cakeブログチュートリアルで早速詰まる

CakePHP 1.2.0.5427alphaでブログチュートリアルをそのまま実行してみる。

Notice: Method input() is deprecated in HtmlHelper: see FormHelper::input or FormHelper::text in /path/to/cake/libs/view/helpers/html.php on line 608

とかいうエラーがいくつか出る。 いくつかのメソッドはdeprecatedな上、submitなんてメソッド無いよ、ってことらしい。 わからないなりにgoogleりながら修正。
Nabble - CakePHP - (HtmlHelper::submit) Deprecated: Use FormHelper::submit instead | switch off?
http://www.nabble.com/(HtmlHelper::submit)-Deprecated:-Use-FormHelper::submit-instead-%7C-switch-off--t3584569.html

controllerに追加

public $helpers = array('Html', 'Form',);

add.thtmlを

<h1>投稿の追加</h1>
<form method="post" action="<?php echo $form->url('/posts/add'); ?>">
<p>
タイトル:
<?php echo $form->input('Post.title', array('size' => '40')); ?>
<?php echo $form->error('Post.title', 'タイトルは必ず入力してください。'); ?>
</p>
<p>
本文:
<?php echo $form->textarea('Post.body', array('rows' => '10')); ?>
<?php echo $form->error('Post.body', '本文は必ず入力してください。'); ?>
</p>
<p>
<?php echo $form->submit('保存'); ?>
</p>
</form>

に書き換えることでエラー無しに作動。

OOP理解するには使いたいフレームワークを利用しながらソース読むのが一番速いよ、とのアドバイスを実行しようとしていた矢先、出だしで詰まると取っ掛かりすらさっぱりわからなくてちょっと困りました。 MVCやらコントローラやらヘルパやら少しずつ理解していくつもり。

最近のトラックバック

information

  • author : sanemat
    表記はsanemat あるいは SANE もしくはsanematsu, sane
    sanemat's portfolio
    (murahashi kenichi)
  • サイト内検索
ブログ powered by TypePad