Home > CakePHP > CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目)

CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目)

この記事の所要時間: 612

CakePHPのModelに悩む人が多いようなので、自分なりの付き合い方(考え方)をご紹介します。

CakePHP Advent Calendar 2010の3日目です。

前日の k1LoW さんのエントリ、参考になりますね。GETのフォームをdata[]でやるとURLがすんごいことになるので、ウチでは別途対応できるライブラリを作ったりしてます。

さて、3日目の今日は書きたいネタは幾つかあったのですが、「君の当たり前に僕らは感嘆させられるんだ」の精神に従って、自分なりのModelの使い方、考え方を書いてみます。

1. 適正なインターフェイスを用意して処理をカプセル化

まず基本的な考え方。

Model(に限らずですが)では、処理単位でインターフェイス(メソッド)を用意して、実装はカプセル化しておきます。

こうすることによって、Modelを利用する側(Controllerや他のModel等)はそのインターフェイス越しに処理を呼び出せば、求める処理が実行され、その結果を得ることできます。

呼び出し側では、求める処理さえが実行されれば良いわけで、その処理自体がどのような実装になっているのかは知る必要がありません。極端な話、求める要件さえ満たせば、処理はどのように行われていても良いわけです。

この処理のカプセル化というのはオブジェクト指向では一般的な考え方ですが、あらためてしっかりとイメージしておくとフレームワークと上手く付き合う方法が見えてくるのかなと思います。

もちろん、これはベースの考え方であって、実際は当然実装も気にしますし、それに応じた呼び出し方も考えます。ただ根底ではこの考え方を意識しています。

2. SQLを直接書いて良いか?

CakePHPが用意しているインターフェイスで簡単に全てのクエリが表現できればそれに超したことは無いのですが、実際のところそうもいかないこともあります。

発行したいSQLはイメージできているのに、それをどうCake風に書けば良いかを悩む、という場面に遭遇した人も多いのではないでしょうか。

もちろんフレームワーク流の書き方を習得する方法も1つなのですが、場合によっては、その処理をメソッド内に閉じ込めておけば、SQLを書いてしまっても良いと思います。

イメージとしては以下。このメソッドを呼び出す側からはregisterという処理を実行してくれれば良いわけで、中でsaveメソッドを呼ぶのか、SQL直書きなのかは関係ありません。

  public function register() {
     $this->query('複雑なSQL書く');
  }

もちろん、これも程度の問題で、基本はModelのメソッドをそのまま使います。ただ複雑なSQL、パフォーマンスが要求されるような箇所では、SQL直書きも許容するということです。

3. belongsTo以外のアソシエーションは使わない

単純なbelongsTo以外のアソシエーションは原則使いません。

複数テーブルのJOINが必要な場合は、DBにViewTable(CREATE VIEW)を作成して、それに対応するModelを作ります。

例えばでは、usersテーブルとuser_classesテーブルをJOINしたViewTableを作ります。

CREATE VIEW v_users AS
SELECT
  u.*,
  uc.name AS user_class_name
FROM
  users AS u
JOIN
  user_classes AS uc ON (u.user_class_id=uc.id)
;

そして、v_usersを利用するModelを作ります。

class VUser extends AppModel {
  public $useTable = 'v_users';
}

VUserモデルでfind()等を実行する場合は、あくまでv_usersという一つのViewTableに対する操作となるので、とても単純です。

さらにViewTableは自分でSQLを書いて作るので、意図したとおりのSQLが発行できます。もしクエリをチューニングするときはSQLレベルで調整できます。

複雑なアソシエーションを覚える、制御する必要が無いので、個人的にはこの方法が気にいっています。

4. アクション毎にModelを作る

Modelには多くの責務があるので、アプリケーションが大きくなってくると1つのModelに処理が増えてきて肥大化する傾向があります。

特にバリデーションなどは画面によって内容が異なる場合もあるので、1つのModelにあらゆる場面での処理を書いていくと複雑になり、メンテナンス性が落ちます。

そこで、処理を分割する、1つのModelの責任を小さくするためにControllerのアクション毎にModelを作成しています。

例えば、UserController#index()ならActionUserIndexというModelを、UserController#edit()ならActionUserEditというModelを作成します。

画面毎の処理はそのModelに書き、共通で利用するような処理はUserモデルに書くというように使い分けています。

もちろん中にはUserモデルをfindするだけで済むような画面もあるので、そういう場合は作らないときもありますが、基本的には作成するようにしています。

これにより画面毎の処理を局所化できるので、特にチームでの開発では効率が上がりました。

5. 例外を積極的に使う

わりと敬遠されがちな例外ですが、積極的に使ってます。例外を使えば、処理の考え方がシンプルになります。

例えば、Modelで何かエラーがあったら例外を投げるようにしています。投げる例外は場面に応じたものを選択しますが、とりあえずエラーなら例外を投げるというルールにします。

また、呼び出し側では、try句の中には正常系だけ書いて、異常系はcatchに書きます。try句の中には正常系処理しか無いので、本来実行すべき処理を追いやすくなります。

もちろん例外を使わず戻り値の規約を作れば同じようなことはできますが、try/catch句のようにPHPの構文で実装の意図を明確にすることができます。

try {
  正常系処理
} catch (NotFoundException $e) {
  異常系処理1
} catch (AppException $e) {
  異常系処理2
}

バランスも大事

フレームワークを使う上で、どこまでフレームワークの仕様に合わせるか、どこからは独自の方法で実装するかというのは悩むポイントですね。

このエントリで紹介した内容はCakePHPの基本的な使い方からは少し外れるものもあります。

ただ、フレームワークを使う目的は、フレームワークを使うことではなく、フレームワークを使って何かを作ることだと思っているので、バランスを取りながらうまく付き合っていきたいですね。

CakePHP Advent Calendar 2010 4日目は、remoreさんです。どんなエントリになるか楽しみですね!

Pocket

follow us in feedly

コメント (Close):3

monmon 11-01-14 (金) 5:06

質問です!

「4. アクション毎にModelを作る」の

・ActionUserIndexやActionUserEditとUserの関係
・UserControllerがそれらをどう呼び出すか

がイメージ付かなかったので教えてもらいたいのです。

まず、一つ目についてなのですが、
ActionUserIndexやActionUserEditはUserクラスを継承したクラスなのでしょうか?
(「バリデーションをわける」とあるので、関係してるのかな?と思いました。
Userに共通の内容を書いておき、ActionUserIndexに
必要なバリデーションを上書きするするイメージをしました。)

二つ目についてですが、
UserContrllerの$usesはUserだけを呼び出すように
$uses = array(‘User’);
な感じになっていて、
UserController#index()の中では
ClassRegistry::init(‘ActionUserIndex’)
のように呼び出しているんでしょうか?
それぞれのアクションでどのようにActionUserIndexモデルを呼び出すのだろう?と思いました。

shinbara 11-01-16 (日) 0:52

はい!

> ・ActionUserIndexやActionUserEditとUserの関係
必ずしもUserを継承する必要は無いです。
ActionUserIndexの$useTableで users テーブルを設定すれば事足りることも多いです。
Userモデルのメソッドを利用したければ参照で利用する方法もあります。
「Userを継承する」と決め付けると複数モデル(テーブル)を操作するアクションで困ることになるので、
こだわらない方が良いですね。

> ・UserControllerがそれらをどう呼び出すか
ご指摘のとおり以下の方法で使っています。

> UserController#index()の中ではClassRegistry::init(‘ActionUserIndex’)

monmon 11-01-16 (日) 12:57

ありがとうございます!
> 「Userを継承する」と決め付けると複数モデル(テーブル)を操作するアクションで困ることになるので、
> こだわらない方が良いですね。
なるほど。色んなテーブルを使うことを考えてませんでした。
試してみます。ありがとうございます。ありがとうございます。

トラックバック:3

このエントリーのトラックバックURL
http://www.1x1.jp/blog/2010/12/thinking_abount_cakephp_mode.html/trackback
Listed below are links to weblogs that reference
CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目) from Shin x blog
pingback from CakePHP2をこれから学習していく人が必ず読むべき記事13選 - tagamidaiki.com 12-09-14 (金) 14:41

[…] – sooey.com CakePHP開発者が知るべき10のこと – 1byte.jp CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目) – Shin x blog CakePHPで細かすぎて伝わらないtipsネタBEST3 – Knockin’on TechLog […]

pingback from CakePHPをはじめて使ってみたことのまとめ | rui live note 13-02-21 (木) 7:59

[…] CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目) – Shin x blog […]

pingback from CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目) | Shin x blog ¶ yeees.in 14-07-19 (土) 1:32

[…] http://www.1×1.jp/blog/2010/12/thinking_abount_cakephp_mode.html […]

Home > CakePHP > CakePHP Modelとの付き合い方(CakePHP Advent Calendar 2010 3日目)

検索
フィード
メタ情報

Return to page top