アイデア元
Web アプリの MVC 設計まとめ - もやし日記
概要
上記のリンク先の目次がそのまま解決したい課題となっています。以下に抜粋します。
- 肥大化するコントローラを避ける
- ビジネスロジックをどこに書けば良いのか
- コントローラとモデルの間にもう一つの層があるとうまくいく?
これを CakePHP で解決する方法を検討します。
解決案 - Logic モデルを作成する
※ 2015-05-12 追記
Logic コンポーネントの方がよいのでは、という議論もコメント欄でしていますので、合わせて参照下さい。
※ 2016-07-21 追記
下記記事にある 「Service層」のことをやりたい、という話でした。
Webアプリケーションの構成に関する予備知識
http://qiita.com/okeyaki/items/37eb4b66bd8ef62c1fe8
検討といいつつ自分が実践している例なのですが、上記で言う「もう一つの層」となる、Logic モデルを作成します。これによりコントローラーはルーティングや初期設定+αの処理に徹し、実際のビジネスロジックは Logic モデルに実装することで各層の粒度(ソースコード、各メソッドの行数)を均すことができます。
app
|-Controller
| |- UserController.php
| |- ...
|
|-Model
| |-Logic <- これを作成する
| | |-LogicUser.php
| |- ...
|
|-View
|-User
| |-add.ctp
| |- ...
|- ...
実例
ブートストラップ
Logic フォルダを Model 直下に配置し、bootstrap.php
で自動ロードの対象に含める。
App::build( array(
'Model' => array( APP . 'Model/Logic/' ),
) );
モデル
対象のコントローラー名に Logic
を付加したモデルを作成し、コントローラーと一対になるようにする。
(例: UserController
がある場合は app/Model/Logic/LogicUser.php
に LogicUser
クラスを作成。)
ロジックモデル内にはコントローラーの対応するアクションの処理内容を action_
プレフィックスをつけたメソッド内に記述し、コントローラーから呼び出すようにする。
class LogicUser extends Model {
/**
* テーブル名(未使用時は false を指定する)
* @var String
*/
public $useTable = false;
public function action_add( Controller $controller ) {
// コントローラーのアクションの処理内容を記述する
// コントローラー内で $this で利用しているメソッド、プロパティは
// $controller で呼び出すようにする。
// 例) $this->request->data() → $controller->request->data()
}
...
コントローラー
コントローラーの $uses
に対象となるロジックモデルを読み込む。
各アクションではロジックモデルの対応するアクションを読み出す。
※ ロジックモデル内でコントローラーの処理が呼び出せるように、コントローラのインスタンス $this
をパラメータに渡すことを忘れないようにする。
class CommoditiesController extends AppController {
public $uses = array( 'LogicUser', // ... );
public function add() {
return $this->LogicUser->action_add( $this );
}
...
基底クラスの継承、およびビヘイビア
モデルで共通するメソッドを実装したい場合には、基底クラスを作成して各ロジッククラスで継承するか、ビヘイビアとして別で作成して基底クラスおよび当該クラスで $actsAs
から読み込むようにする。
※ ビヘイビアは振る舞いを実装する箇所なので、この辺りは気に入らなければ基底クラスのみを使うようにした方がよい。ただその場合は基底クラスが膨れ過ぎないように注意。
App::uses( 'Model', 'Model' );
class AppModel extends Model {
public $actsAs = array(
'DateUtil', 'ArrayUtil', 'SessionUtil', 'MailUtil', // ...
);
...
課題や気になる点
- モデル内でコントローラーの処理を行うのは自然ではないのでコンポーネントを利用した方がよいのかもしれない。ただビジネスロジックはモデルに近い位置にあるし、ロジックモデルとした方がしっくりくる。
- 複数のコントローラーから呼び出すためのロジックモデルはまだ想定されておらず、命名規則もない。参考URLのアイデアに沿うならばサービスモデル
ServiceHogehogeModel
を作成することになるか。
まとめ
まだ完成した形ではないですが、この方式によりコントローラーはだいぶすっきりして見通しがよいコードになりました。
もっとこうした方がいいとかこれはマズいとかあれば、指摘頂けると幸いです。