03
2009

CakePHP 1.2.5 でハマったところ

CATEGORYPHP
PHPで小規模なWebアプリを作ることになった。
「彼氏が素のPHPでコード書いてた。別れたい…」ともいうし、CakePHPを使ってみることにした(おぃ

2週間ほど使っていて、何点かハマった箇所があったので参考までにメモしておく。
バージョンは安定版最新の1.2.5という奴・・・アプリのトップページ開くと「Release Notes for CakePHP 1.2.4.8284」とか出るけど、これ1.2.5でいいんだよね?(汗

なお、PHPで本格的に開発するのはこれが初めてで、また勘違いや見落としなどもあるかもしれないが、ご容赦願いたい(^^;
他にもいろいろ悩んだはずだが、Cookbook見ればちゃんと判るところに書いてあるよ、というのは除外している。
意外と書いてあるんだけど、でもやっぱり書いてないという印象。
というか使用例が書いてあっても仕組みの説明がない?

$this->data とモデル

formでFormHelperからpostしたときの中身が入っている $this->data だが、これは

$this->Users->save($this->data)

のようにそのままモデルに渡すことができる。
かつ、findの結果とも同じフォーマットなので、逆にFormHelperに初期値を渡したいときは、

$this->data = $this->Users->findById($id)

とかで値を入れておけばよい。


FormHelperでのinputタグの出し方

FormHelperで入力欄を作りたいときは、基本的に $form->input() を使用する。
これでやれば、ラベルからバリデートエラー時のエラーメッセージから何から何まで全てセットでできる。

selectとかを作りたいときも、オプションで 'type' => 'select' を使用する。
$form->select() があるからといきなりそちらを使ってしまうと、バリデートエラーが表示されなかったりして悩む羽目になる。

自力で同じ事をする場合は、$form->text(), $form->label(), $form->isFieldError(), $form->error() を組み合わせる必要がある。
わざわざ自分でやるのは、デザイン的に使わざる得ない場合とかだけでいいと思う。実際そういうこともあった。


minlength, maxLength, between のバリデータは日本語非対応

上記のバリデータに日本語を渡すと普通にバイト数でカウントされる。
その方が都合がよい人もいるだろうから仕様なのかもしれないが、DBもUTF-8で文字数でチェックしてほしい時とかには困る。

下記で公開されているように自分で app_model.php にオーバーライドすることで対処可能。

WIDGET-INFO - CakePHP バリデーションの日本語問題


alphaNumeric のバリデータは英数判定ではない

Cookbookにも「半角のアルファベットか数字のみ許可されます。」とあるんだけど誤り。
このメソッドは、画面に表示できる文字か、という判定を行う模様。

どうも、昔のバージョンでは(結果的に?)英数だけだったのが、1.2.3RC3?を境に今のような判定条件に変わったらしい。
変わったということは意図的にやっているはずなので、そういう仕様なのだろう。

miau's blog? - CakePHPによる実践Webアプリケーション開発

向こうの人にとっては画面に表示する文字=アルファベットか数字じゃね?ということなのかもしれないが、正直これは紛らわしい。


複合主キーは使えない

無理やり使えなくは無いけど、少なくともフレームワークのいろんな機能(ぱっと思いつくのでは、重複チェックだのdel()だの)は使えなくなる。 11/4 削除。無理やりでも困難でした(--;
DB設計の勉強していると、普通に複合主キーを作るシチュエーションが出てくるんだけど・・・。

何かしらのID列を追加して対処する。正規化してきれいにしたところに列を追加するのは気持ち悪いが、駄目なのもは仕方ない。


FormHelperで複数モデルの指定

一番ハマったところ。ちょっとややこしいので、例を挙げつつ説明。

例えば、以下のような3つのテーブルがあったとする。

テーブル構成例1

それぞれユーザーと経歴と経歴詳細で、1対nの関連を持つ。career_detailsはcareersから繰り返し項目を分離するための、career_idとnoで一意にさせるような補助的なテーブル。ただし上の問題があるので、noではなく一意なid列を主キーとした。

で、ここまではよい。これを別々に使う分には特に問題は無い。また、参照する分にも特に問題は無い。
ところが、これを下記のように一画面で入力させようとしたところで、壁にぶつかった。

フォームサンプル1

結論から言うと、FormHelperに対して1対nで3階層以上のモデルを指定することはできない。

FormHelperのinput、というかその中で呼んでいるHelper#setEntityのAPIでは、引数 $entity について下記のように記述されている。


A field name, like "ModelName.fieldName" or "ModelName.ID.fieldName"

いろいろと省略できたりでややこしいのだが、上のテーブル例の場合で、下記のような結果を得られた。


$fieldNameinputタグのname属性例補足
mail_addressdata[User][mail_address]フィールド名のみ、モデル名Userを省略
User.mail_addressdata[User][mail_address]モデル名とフィールド名
Career.${i}.titledata[Career][0][title]モデル名とインデックス、フィールド名
Career.${i}.CareerDetails.${j}data[Career][0][CareerDetails][0]モデル名とインデックス、さらにモデル名とインデックス
Career.${i}.CareerDetails.${j}.skilldata[User]モデル名とインデックス、さらにモデル名とインデックスとフィールド名
${i}.CareerDetails.${j}.skilldata[User]インデックスとモデル名、さらにインデックスでフィールド名
${i}.CareerDetails.${j}data[User][CareerDetails][0]インデックスとモデル名、さらにインデックス
${i}.mail_addressdata[User][0][mail_address]インデックスとフィールド名

※ 指定できたが Career.${i}.CareerDetails.${j} が正常に動作するのかは不明

setEntity()のソースも省略やらの判定が多く追いづらいのだが、最終的に4階層までしか考慮していない模様?
しかも、4階層指定できる、ということではなく、いろいろ省略したものを足した上で4階層のようだ・・・orz

パラメータの取得自体はできるようで、自分でinputタグのnameを下記のように作ってやれば、$this->dataにfind等で取得するのと同じ構成で格納することはできる。

<input type="text" name="<?php echo "data[Career][${i}][CareerDetails][${j}][skill]" ?>">

ただ、結局バリデートもsave()もエラー出力も自前でやるしかないので、メリットは無いと思われる。

どうしてもFormHelperを使って今回のようなパラメータを渡したいときは・・・

Career.${i}.CareerDetails${j}skill

のように区切り文字のピリオドを消して属性に見せた上で、コントローラ側で自分でパラメータを解析する手しか思い浮かばない・・・。後はテーブル構成を変える。

たぶん、こういう構成はフレームワークとして想定していないというか、そういう複雑な設計にするなとかそういうことだと思われる。小規模向けらしいし致し方ないか。


配列のバリデート結果保存先

引き続き上のモデルを例に。上のように Career.${i}.title と書くと配列で入ってくるわけだが、下記のように書くだけではFormHelperにバリデート結果が表示されない。

foreach ($this->data['Career'] as $career) {
$this->User->Career->create($career);
$this->User->Career->validates();
}

ループに関する情報がvalidates()に渡ってないので、そりゃ出るわけ無いって感じではあるがw
で、saveAll($this->data) でまとめてやった場合はちゃんとバリデート結果が表示されるので、saveAllのソースを見てみたところ、下記のように書けば表示できることが判った。

$validationErrors = array();
foreach ($this->data['Career'] as $key => $career) {
$this->User->Career->create($this->data['Career'][$i]);
if (!$this->User->Career->validates()) {
$validationErrors[$key] = $this->User->Career->validationErrors;
}
}
$this->User->Career->validationErrors = $validationErrors;

その他

Cookbookはたまにリンク切れ・・・というかリンク誤りがある。↓とか。

4.1.2 1個のフィールドに1個のルールを定義する
http://book.cakephp.org/ja/view/127/
5.2.3 認証における問題のトラブルシューティング
http://book.cakephp.org/ja/view/565/

たいていはリンクが間違っているだけなので、英語ページでURLを見てページの番号(上でいう127とか565とか)を調べ→日本語版でその番号のページを手打ちする、で回避できる。
(本当は指摘して直してもらうべきだろうが、よくわからなかったので。)

後、引数に何を渡せばよいか、CookbookにもAPIにもはっきり書いてないことが多々ある。
困ったらソースを読むかググって先人に期待するしかない(汗



全体的に使ってみたイメージとしては、フレームワークで想定しているとおりのつくりなら簡単、ちょっと外れたことをやろうとすると時間がかかるという感じか?
FormHelperで複数モデルのところとか、俺が知らないだけでもっとスマートな方法があるとかだとありがたいのだが・・・?


2009/11/4 追記。

複合主キーが使えない話、思いっきりCookbookの最初のほうに書いてあったわ、、、そういえば見たような?まあいいや。
ちなみに、意図的にサポートされていないとのことで、将来的に使えるようになる見込みは低いようです。

CakePHP のおいしい食べ方 - 複合主キーをめぐる議論
スポンサーサイト

Tag: CakePHP

0 Comments

Leave a comment