MVCフレームワーク

MVC+α

MVCの弱点

私がMVCを利用するうち感じた事があります。
とりあえずVは置いといて、MとCだけでは足りないと感じる事があるということです。

「モデル」のところで述べたようにMの設計の仕方は様々ですが、どの設計の仕方にも弱点が生じます。
Railsのようにテーブル単位にモデルを作成するという考え方はPHPでのMVCでは最も分かりやすいのではないかと思いますが、
テーブルに直接関わらないロジックを書く場所がなくなるという事がまず言えます。
そして迷ったあげく、コントローラーにそれを書いちゃったら最悪です。既にMとCの役割の境界が崩れてしまっています。

そしてそれ以上に感じるのが、複数テーブルにまたがるトランザクション処理はどうするのかという事です。
Railsなんかをいじくってみると必ずこの疑問にぶつかる気がします。
でも、調べてみてもそこに触れた話はあまり見つかりません。不思議でしょうがないのですが、みなさんどうしているのでしょうか。

コントローラーとモデルを対にする考え方の場合、モデルはテーブルに縛られないのでそこでトランザクション処理ができ、この問題は解消しますが、
別々のコントローラーで同じテーブルに対して同じような処理が必要な場合、別々なモデルに同じような処理を複数回書かなければならないという別な問題が出てきます。

そこで浮かんでくるのがMとCの間にもう一つあるとうまくいくのではないかという事です。

MVCにとらわれない新しい概念

まずモデルはテーブルオブジェクトと割り切ります。
そしてモデル間のトランザクション管理する役目を果たし、かつテーブルとは直接かかわりのないロジックも担当する、もう一つの概念を作ります。
これを「サービス」と呼ぶことにします。
MVCSといったところです。

このサービスはテーブル単位でもコントローラー単位でもなく、ある特定の事象に対する処理単位で作成することにします。
例えばカートであればCartServiceというクラスを作り、カートに関わるテーブル操作、処理ロジックは全てここに記述します。

データベーステーブルという物は構造上、一つの事象に対して一つのテーブルという事の出来る物ではありません。
リレーションによってそれに近い概念を作りあげるものです。
そう考えれば、オブジェクト指向におけるオブジェクトという概念をテーブルを直接関連付けるのは少々不自然といえます。
しかし言語処理上、そのような形を取るのが整理がつきやすくなります。

そこでその間をつなぐ役目として「サービス」という概念を作りあげるのです。
オブジェクト指向の上での事象単位をサービスとし、その事象に絡む複数のモデルを束ねる役目を担わせるのです。
ついでにテーブルとは関わりのないロジックも担当させてしまえば、MVCの弱点を見事に克服してくれるのではないかと考えます。

CartService.php
<?php

class CartService
{
    // カート情報を取得
    public static function getCartInfo($userId)
    {
        $cart = new Cart();
        $cartProduct = new CartProduct();
        
        $cartInfo = $cart->getUserCart($userId);
        $products = $cartProduct->getProductList($cartInfo['cart_id']);
        $cartInfo['products'] = $products;

        return $cartInfo;
    }

    // 商品追加
    public static function addProduct($cartId, $productId)
    {
        $db = new PDO();
        $db->beginTransaction()
        try
        {
            $cart = new Cart($db);
            $cartProduct = new CartProduct($db);

            // カートに商品を追加
            $cartProduct->add($productId));

            // カート情報の合計金額を更新
            $cart ->updateTotalPrice(($cartId);

            $db->commit();
            return true;

        } catch (Exception $e) {
            $db->rollback();
            throw $e;
        }  
    }

}

?>
CartController.php
<?php

class CartController extends ControllerBase
{
    // カート商品一覧
    public function displayAction()
    {        
        $params = $this->request->getParam();
        // カート情報取得
        $cartInfo = CartService::getCartInfo($params['user_id');
        $this->view->assign('cart_info', $cartInfo);
    }

    // 商品追加
    public function addAction()
    {
        $post = $this->request->getPost();
        // 商品追加処理
        $cartInfo = CartService::addProduct($post['cart_id'], $post['product_id']);
        // カート商品一覧へリダイレクト
        header('Location: http://www.xxx.com/cart/display');
    }

}

?>

コントローラーはひたすら処理の呼び出しに徹します。
考え方としてリクエスト情報やセッションを直接操作するのはコントローラー、それらの情報を元に処理を行うのがサービスまたはモデルと考えます。
ちなみにテーブル単体で処理が完結するような場合はサービスを介さず、直接コントローラーからモデルを操作するのも全然構わないと思います。

べつに「サービス」のような新しい概念でなくとも、モデルの中に2種類作るのもありです。
例えばテーブルオブジェクトはテーブル名をそのまま利用したクラス名とし、今回のサービスのような役割を果たす物には例えば"CartModel"というように、"Model"という接尾語を付ければ、MVCの3つの概念で全てをおさめることも可能になります。

ロジックを小分けにするということ

今回のように、MVCに加えて更に新しい概念のクラスを追加するということは、
やたらにファイルが増えて管理が面倒に思われるかもしれません。

それどころか、世の中のフレームワーク利用者には、コントローラーに直接ロジックをゴリゴリと書くというパターンも少なくないようです。

多分、クラスが分かれれば分かれるほど、いろんなところに目をやらなくちゃいけなくなり、面倒になるというイメージがあるためかと思います。

特にPHP覚え始めの方ほど、一箇所に詰め込むほうがコードが見渡しやすいと思いがちです。
これは逆です。

慣れるほどに気づきますが、コードは小分けにしてあるほうが読みやすく、保守性はあがります。
探しているロジックがどの変に記述されているか、検討が付けやすいためです。
ダラダラと長く書かれていると、それこそ全文検索みたいなもんです。
人間は全文検索よりも、インデックス検索のほうが得意なのです。
しかも役割で分けられているとなおさらです。

小分けにしてある方が、プログラムのの修正に対する影響範囲も小さくなります。
ただしこれは各クラスの役割分担が明確にされていればの話ですが。

そういうこともあって、MVCの概念にとらわれ過ぎず、ここの趣旨のように、必要に応じて新しい概念を付け足すなんてのもありじゃないかと思います。