モデル

モデルの概念

オブジェクト指向を真面目に設計する場合、アプリケーションに登場する事象をプログラミングで扱うオブジェクトとして定義していきます。このことをモデリングと呼びますが、MVCにおけるモデルとはその名の通り、まさにこのモデリングの産物と考える事が出来ます。
つまりアプリケーションの根本の仕様を形作るのがモデルであるため、アプリケーションのメインロジックは全てここに書くべきと言うことになります。

まずはそれを守っていればモデルの作りは自由だと思います。

モデルは当然クラスとして表現します。どういう単位でクラスを作成するかが設計の分かれ目です。

物理モデル(データモデル)

ここで言う物理とはアプリケーションに必要なデータストアを表しますが、モデルの重用な役割の一つとしてデータの入出力があります。そのため、モデルクラスの設計の一つの形として、RailsのActiveRecordような、モデルクラス=データベーステーブル(レコード)という考え方は有力候補の一つです。この場合、テーブル名と同一か、またはある一定の規則に従ってテーブル名と関連のあるクラス名でクラスを作ります。
つまり、そのアプリ上で利用するテーブルの数だけモデルクラスも存在することになります。

例えば通販サイト構築を例として、カート情報を保存するカートテーブルをクラス化してみます。

Cart.php
<?php

class CartHeader
{
    private $db;
    private $name = 'cart_header';    

    public function __construct($user, $pass)
    {
        $this->db = new PDO($user, $pass);
    }

    // カート基本情報取得
    public function getUserCart($userId)
    {
        $sql = sprintf('SELECT * FROM %s where user_id = :user_id' , $this->name);
        $stmt = $this->db->query($sql);
        $stmt->bindValue(':user_id', $userId);
        $rows = $stmt->fetchAll();
        return $rows[0];
    }

    // 新規カート作成
    public function create($userId)
    {
        $sql = sprintf('INSERT INTO %s ・・・・・・・・', $this->name);
        $res = $this->db->query($sql);
        return $res;
    }

}

?>

カートの基本情報を格納するcart_headerというテーブルに対するモデルクラスです。
このテーブルのデータ操作に直接関わる処理はこのクラスにまとめるわけです。

カートには当然、商品を入れるわけなので、カートの基本情報を格納するテーブルとは別に、カート内の商品リスト情報を格納するテーブルが必要になるはずです。

CartDetail.php
<?php

class CartDetail
{
    private $db;
    private $name = 'cart_detail';    

    public function __construct($user, $pass)
    {
        $this->db = new PDO($user, $pass);
    }

    // 商品リスト取得
    public function getList($cartId)
    {
        $sql = sprintf('SELECT * FROM %s where cart_id = :cart_id', $this->name);
        $stmt = $this->db->query($sql);
        $stmt->bindValue(':cart_id', $cartId);
        $rows = $stmt->fetchAll();
        return $rows;
    }
    
    // 商品追加
    public function add($data)
    {
        $sql = sprintf('INSERT INTO %s ・・・・・・・・', $this->name);
        $res = $this->db->query($sql);
        return $res;
    }
    
    // 商品削除
    public function remove($productId)
    {
        $sql = sprintf('DELETE FROM %s WHERE ・・・・・・・・', $this->name);
        $res = $db->query($sql);
        return $res;
    }
}

?>

今度はcart_detailテーブルの処理用のCartDetailというモデルクラスを作りました。

コントローラーでこれらのモデルを利用してみます。

CartController.php
<?php

class CartController
{
    public function displayAction()
    {
        $req= new Request();
        $params = $req->getParam();
        $userId = $params['user_id'];

        // カート基本情報を取得
        $cart = new CartHeader();
        $cartInfo = $cart->getUserCart($userId);
        
        // 商品一覧を取得
        $cartDetail = new CartDetail();        
        $products = $cartDetail->getProductList($cartInfo['cart_id']);
                ・
                ・
                ・
    }

}

?>

このように、一連の処理の中で複数のテーブルに対して処理が発生する場合、テーブルごとのモデルクラスを用意し、それぞれの処理を呼び出す形になります。テーブルをオブジェクトとらえて、テーブル単位の処理に綺麗に分類できるため、ロジックの整理がしやすいと思います。

ただしデメリットもあります。
テーブルとはあまり関わりない部分のロジックははどこに書くかという事で迷うのです。
そうなった時にコントローラーにだらだらとロジックを書いてしまうという過ちを起こしやすくなります。

またアプリ全体の中で、ごく一部でしか使われないようなテーブルで、処理内容もほとんど無いような場合にも一つのクラスとする必要があり、無駄にクラスが増えすぎる可能性があるということも言えます。

論理モデル(エンティティモデル)

データベースという形のあるものを意識して物理モデルと呼ぶのに対し、アプリケーションに登場する論理的な事象に着目してクラス化するというアプローチもあります。

上の例ではテーブル単位にモデルを作成しました。
リレーショナルデータベースの性質上、カートをテーブルで表現するには2つのテーブルが必要になります。これはリレーショナルデータベースという外部のソフトウェアの仕様に引っ張られたものであり、本来カートはカートです。

これをシステム設計上はエンティティと呼んだりしますが、クラスをこの論理的なエンティティ単位に作成します。カートに関するテーブルは2つであるため、この2つのテーブルに関する処理はカートクラス内に実装するという考え方です。本来のオブジェクト指向的な考え方に基づくとこのような設計が自然かも知れません。

<?php

class Cart
{
    private $db;
    private $headerTable = 'cart_header';
    private $detailTable = 'cart_detail';

    public function __construct($user, $pass)
    {
        $this->db = new PDO($user, $pass);
    }

    // 新規カート作成
    public function create($userId)
    {
        $sql = sprintf('INSERT INTO %s ・・・・・・・・', $this->headerTable);
        $res = $this->db->query($sql);
        return $res;
    }

    // 商品リスト取得
    public function getList($cartId)
    {
        $sql = sprintf('SELECT * FROM %s where cart_id = :cart_id', $this->detailTable);
        $stmt = $this->db->query($sql);
        $stmt->bindValue(':cart_id', $cartId);
        $rows = $stmt->fetchAll();
        return $rows;
    }
    
    // 商品追加
    public function add($data)
    {
        $sql = sprintf('INSERT INTO %s ・・・・・・・・', $this->detailTable);
        $res = $this->db->query($sql);
        return $res;
    }
    
}

?>