Help us understand the problem. What is going on with this article?

肥大化するコントローラを避ける (CakePHP 編)

More than 3 years have passed since last update.

アイデア元
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/Config/bootstrap.php
App::build( array(
    'Model' => array( APP . 'Model/Logic/' ),
) );

モデル

対象のコントローラー名に Logic を付加したモデルを作成し、コントローラーと一対になるようにする。
(例: UserController がある場合は app/Model/Logic/LogicUser.phpLogicUser クラスを作成。)

ロジックモデル内にはコントローラーの対応するアクションの処理内容を action_ プレフィックスをつけたメソッド内に記述し、コントローラーから呼び出すようにする。

app/Model/Logic/LogicUser.php
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 をパラメータに渡すことを忘れないようにする。

app/Controller/UserController.php
class CommoditiesController extends AppController {

    public $uses = array( 'LogicUser', // ... );

    public function add() {
        return $this->LogicUser->action_add( $this );
    }
...

基底クラスの継承、およびビヘイビア

モデルで共通するメソッドを実装したい場合には、基底クラスを作成して各ロジッククラスで継承するか、ビヘイビアとして別で作成して基底クラスおよび当該クラスで $actsAs から読み込むようにする。
※ ビヘイビアは振る舞いを実装する箇所なので、この辺りは気に入らなければ基底クラスのみを使うようにした方がよい。ただその場合は基底クラスが膨れ過ぎないように注意。

app/Controller/UserController.php
App::uses( 'Model', 'Model' );

class AppModel extends Model {
    public $actsAs = array(
        'DateUtil', 'ArrayUtil', 'SessionUtil', 'MailUtil', // ...
    );
...

課題や気になる点

  • モデル内でコントローラーの処理を行うのは自然ではないのでコンポーネントを利用した方がよいのかもしれない。ただビジネスロジックはモデルに近い位置にあるし、ロジックモデルとした方がしっくりくる。
  • 複数のコントローラーから呼び出すためのロジックモデルはまだ想定されておらず、命名規則もない。参考URLのアイデアに沿うならばサービスモデル ServiceHogehogeModel を作成することになるか。

まとめ

まだ完成した形ではないですが、この方式によりコントローラーはだいぶすっきりして見通しがよいコードになりました。
もっとこうした方がいいとかこれはマズいとかあれば、指摘頂けると幸いです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away