昨年ぐらいから CakePHP 2 を本格的に触ることになりまして、これまでの Paper ドライバーを卒業と喜んでいたんですが、いきなりつまづいた…というか不満に思ったのが Model の構造でした。CakePHP も 2 となり、もうてっきり Rails の ActiveRecord のようなものになっているかと思いきや、find メソッドが返すものは相変わらず array だったんですね。これはもう思想なのかも?…とはいえ、Paper ドライバーレベルではなかなか思想まで理解できません。同じような不満が出ていないかと検索してみたところ、次の記事が見つかりました。
同じことを思う方はいるもので♪ 早速、上のプラグインを利用させていただいたのですが、どうも CakePHP 2 系では上手く動かないようでしたので、修正を試みたものの、挫折。せっかくだからということで、自分で作ることにしました。これでも自称フレームワークマニアですしねw
その成果物がここで紹介する FkRecordModel です。GitHub に公開中です。
次のような簡単な構成になっています。
- FkRecord エンティティオブジェクト。find メソッドなどの戻り値などになります。
- FkRecordCollection findAll などの戻り値となります。
- FkRecordModel 拡張された Model クラスです。FkRecord 生成の為に find などをオーバーライドしています。
次のような機能があります(特に高機能ではありません)。
- FkRecord::save() で保存
- FkRecord::validates() でバリデーション
- FkRecordModel::build() 新しい FkRecord のビルド
- エンティティフィールドのシンプルなアクセス。Ex. $post->title
- Validation error は FkRecord が保持し、View で参照可能
- HTML のラベルの表示などに利用できるフィールド別名のサポート(VerboseName)
- リレーションを FkRecordCollection で取得。Ex. $post->tags //hasMany のデータの取得
上述の CakeEntity プラグインとの相違点は多々とあると思いますが、とりあえず大きなところで、本プラグインは「CakePHP の通常通り配列を返す」という選択肢を設けていません。find 系メソッドは常に FkRecord または FkRecordCollection を返します。(自分はたぶん使わないからです。ごめんなさい)。また、FkRecordCollection は不要だったり邪魔だと思われる方も我慢してください(イテレータにしておくと色々と便利という信条です)。ちなみに、FkRecordCollection::sort() メソッドには不満があります(削除または改良したいです。どなたか良い案ください)。
以下、簡単な利用例です。
AppModel の定義
まずは以下のように AppModel の super クラスとして本プラグインのクラスを利用します。AppModel の他に、AppRecord も作成してください。FkRecordCollection は全てのモデルで共通に利用されます。
1 2 3 4 5 6 7 8 9 10 11 |
<?php App::uses('FkRecordModel', 'FkRecordModel.Model'); class AppModel extends FkRecordModel { // definition... } class AppRecord extends FkRecord { // definition... } |
具象モデルクラスの定義
モデルクラスでは、それぞれのモデル毎に、Model と Record を定義します。Model の定義は通常通りですが、verboseName などの指定ができます。Record にはエンティティオブジェクトで提供したい機能を実装してください。自らのフィールドへのアクセスは、$this->fieldName または $this[$alias][$fieldName] です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php App::uses('AppModel', 'Model'); class Post extends AppModel { /** * Define verbose name. It is used in the label name, such as, for example. */ var $verboseName = array( 'title' => 'Title', 'description' => 'Description', ); var $hasMany = array( 'tags' => array( 'className' => 'PostTag', 'dependent' => true, ), ); // Other definition... } class PostRecord extends AppRecord { function updateDateBy($format) { return date($format, strtotime($this->modified)); } } |
Controller での利用例
Controller での利用は次のような雰囲気になります。$post->save() のようにして保存します。FkRecord::save() メソッドは、バリデーションエラーがある時には、false を返します。バリデーションエラーの内容はエンティティオブジェクトが保持し、これは View の中で利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php class PostsController extends AppController { $uses = array('Post'); function index() { $posts = $this->Post->find('all'); $this->set(compact('posts')); } function show($id) { $post = $this->Post->findById($id); $this->set(compact('post')); } function create() { $post = $this->Post->build(); $this->set(compact('post')); if ($this->request->is('post')) { $post->setData($this->data); if ($post->save()) { // processing on success. } else { // processing on failed. } } } function edit($id) { $post = $this->Post->findById($id); $this->set(compact('post')); if ($this->request->is('put')) { $post->setData($this->data); if ($post->save()) { // processing on success. } else { // processing on failed. } } } } |
View での利用例(一覧表示)
FkRecordModel に定義された関連は、$post->tags のようにして FkRecordCollection/FkRecord として取得できます。なお、FkRecordCollection は empty($posts) のように空判定できません。FkRecordCollection::isEmpty() メソッドを利用してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php foreach ($posts as $post) : ?> <article id="post-<?php echo $post->getID() ?>"> <h1><?php echo $post->title ?></h1> <p><?php echo $post->description ?></p> <p>Updated at: <?php echo $post->updateDateBy('Y/m/s H:i') ?></p> <?php if (! $post->tags->isEmpty()) : ?> <ul> <?php foreach ($post->tags as $tag) : /* loop for relations */?> <li><?php echo $tag->name ?></li> <?php endforeach; # $post->tags ?> </ul> <?php endif; # !$post->tags->isEmpty() ?> </article> <?php endforeach; # $posts ?> |
View での利用例(編集画面)
フォームを扱うには、FkRecord::bindFormHelper() メソッドで FormHelper をエンティティにバインドし、以後、$post->Form->text($fieldName) といった感じでフォームエレメントを出力できます。FkRecord::getVerboseName($fieldName) でフィールドの別名を取得できます。FkRecord::getError($fieldName) で、そのフィールドにエラーがあれば、その内容を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php $post->bindFormHelper($this->Form) /* Bind to FormHelper */ ?> <?php echo $post->Form->create('Post') /* Start form */ ?> ?> <label> <?php echo $post->getVerboseName('title') ?>: <?php echo $post->Form->text('title') ?> <?php echo $post->getError('title') ?> </label> <label> <?php echo $post->getVerboseName('description') ?>: <?php echo $post->Form->textarea('decription') ?> <?php echo $post->getError('description') ?> </label> <p> <?php echo $post->submit('Send') ?> </p> <?php echo $post->Form->end() ?> |
以上、簡単にですがご紹介まで。
もし奇特な方がいらっしゃいましたら、一度ご利用いただき、是非フィードバックなどを頂けたら嬉しいです! 是非! 是非!