モデルの作り込み

モデルの基礎クラス作成

コントローラーと同じく、モデルも共通処理は基礎クラスを作成し、これを継承する形にしましょう。

モデルの処理として、データベースアクセスという部分は必ずといっていいほど発生すると思われます。
データベースアクセスはどのような手段で行うかですが、ぱっと思いつくのは次の3つくらいでしょうか。

  • PHP標準DB関数
  • PEAR::DB
  • PDO

好き好きですが、ここではPDOを利用することにします。

PDOはデータベースアクセス用のクラスです。
コンストラクタでDBへの接続情報を指定してDB接続を確立します。

こんな感じです。

<?php

$dsn = 'mysql:host=localhost;dbname=sample;port=3306;';
$dbuser = "hoge";
$password = "xxxx";
$pdo = new PDO($dsn, $dbuser, $password);

?>

こんな感じでデータベースへつながるわけですが、
(※PDOの詳細についてはPHPのマニュアルサイトなどを見てください。)

このPDOのインスタンス生成部分は共通処理と考える事が出来るので、
モデルの基礎クラスのコンストラクタでこの処理を行い、PDOのインスタンスをフィールドに持たせてしまいます。

ModelBase.php
<?php

class ModelBase
{
    private static $connInfo;
    protected $db;
    protected $name;

    public function __construct()
    {
        $this->initDb();
    }

    public function initDb()
    {
        $dsn = sprintf(
            'mysql:host=%s;dbname=%s;port=3306;',
            self::$connInfo['host'],
            self::$connInfo['dbname']
        );
        $this->db = new PDO($dsn, self::$connInfo['dbuser'], self::$connInfo['password']);
    }

    public static function setConnectionInfo($connInfo)
    {
        self::$connInfo = $connInfo;
    }
}

?>

接続情報はプログラムに直書きしてしまうと汎用性がなくなるので、外側から設定できるように設定メソッドを作りました。

コンストラクタで接続情報を渡すような作りにしてしまうと、モデルのインスタンス生成のたびにいちいち接続情報を指定しなければならず面倒です。
なのでコンストラクタが呼び出される時点では既に接続情報が設定されている状態になってくれるとありがたいところです。

これを実現するために設定メソッドはstaticにしています。
このメソッドをどうするかというと、

index.php
<?php

require_once '/home/www/public_html/mvc/ModelBase.php';
require_once '/home/www/public_html/mvc/Dispatcher.php';

// DB接続情報設定
$connInfo = array(
    'host'     => 'localhost',
    'dbname'   => 'sample',
    'dbuser'   => 'hoge',
    'password' => 'xxxx'
);
ModelBase::setConnectionInfo($connInfo );

$dispatcher = new Dispatcher();
$dispatcher->setSystemRoot('/home/www/public_html/tuuhan');
$dispatcher->dispatch();

?>

という感じで、起点スクリプトなどで初期設定として設定してしまいます。
基本的にDB接続情報はそのサイト中で一つになることがほとんどのはずなので、初期設定として設定してしまい、
以後はこの設定情報を元に接続を行います。

こうすれば、ModelBaseを継承したモデルクラスのインスタンス生成のたびに接続情報を指定する必要がなくなります。


それではこのModelBaseを継承したモデルクラスを作ってみましょう。

CartProduct.php
<?php

class CartProduct extends ModelBase
{
    private $name = 'cart_product';    

    // 商品リスト取得
    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;
    }

                ・
                ・
                ・
}

?>

こんな感じで、DB接続に関する手続き処理なしでいきなりSQL実行に入れます。

便利機能を詰め込む

更に、モデルクラスとしてよく利用しそうな機能をメソッドとして作っておけば便利です。
例えば、

ModelBase.php
<?php

class ModelBase
{
    protected $connInfo;
    protected $db;
    protected $name;

                ・
                ・
                ・
    // クエリ結果を取得
    public function query($sql, array $params = array())
    {
        $stmt = $this->db->prepare($sql);
        if ($params != null) {
            foreach ($params as $key => $val) {
                $stmt->bindValue(':' . $key, $val);
            }
        }
        $stmt->execute();
        $rows = $stmt->fetchAll();

        return $rows;
    }

    // INSERTを実行
    public function insert($data)
    {
        $fields = array();
        $values = array();
        foreach ($data as $key => $val) {
            $fields[] = $key;
            $values[] = ':' . $key;
        }
        $sql = sprintf(
            "INSERT INTO %s (%s) VALUES (%s)", 
            $this->name,
            implode(',', $fields),
            implode(',', $values)
        );
        $stmt = $this->db->prepare($sql);
        foreach ($data as $key => $val) {
            $stmt->bindValue(':' . $key, $val);
        }
        $res  = $stmt->execute();

        return $res;        
    }

    // DELETEを実行
    public function delete($where, $params = null)
    {
        $sql = sprintf("DELETE FROM %s", $this->name);
        if ($where != "") {
            $sql .= " WHERE " . $where;
        }
        $stmt = $this->db->prepare($sql);
        if ($params != null) {
            foreach ($params as $key => $val) {
                $stmt->bindValue(':' . $key, $val);
            }
        }
        $res = $stmt->execute();
        
        return $res;
    }
}

?>

という感じでSQLのクエリ結果を返してくれるメソッドを作ると便利です。
さらにINSERT文を実行してくれるメソッドやDELETE文を実行してくれるメソッドなんかも作ってみました。
これを利用すれば、モデルクラスはこんな感じでスッキリします。

CartProduct.php
<?php

class CartProduct extends ModelBase
{
    protected $name = 'cart_product';    

    // 商品リスト取得
    public function getList($cartId)
    {
        $sql = sprintf('SELECT * FROM %s where cart_id = :cart_id', $this->name);
        $params = array('cart_id' => $cartId);
        $rows = $this->query($sql, $params);
        return $rows;
    }
    
    // 商品追加
    public function add($data)
    {
        $res = $this->insert($data);
        return $res;
    }

                ・
                ・
                ・
}

?>

テーブル名との関連付け

モデルをテーブルオブジェクトとして扱う場合ですが、
クラス名とテーブル名を関連付けてしまうと、テーブル名をフィールドで設定するなんて必要もなくなってきます。

完全にイコールにする場合は楽ですが、
テーブル名の命名規則とクラス名の命名規則が異なる事は多いと思います。
例えばテーブル名は全て小文字で単語の区切りには"_"を挟むという命名規則、
クラス名は単語の先頭のみ大文字、というような場合、そのままでは関連付けできません。

なので例えばModelBaseに以下のようなメソッドを作り、コンストラクタで呼ぶようにします。

ModelBase.php
<?php

class ModelBase
{
    protected $connInfo;
    protected $db;
    protected $name;

    public function __construct()
    {
        $this->initDb();

        // 継承先で$nameが設定されていない場合はクラス名からテーブル名を生成
        if ($this->name == null) {
            $this->setDefaultTableName();
        }
    }

                ・
                ・
                ・

    public function setDefaultTableName()
    {
        $className = get_class($this);
        $len = strlen($className);
        $tableName = '';
        for ($i = 0; $i < $len; $i++) {
            $char = substr($className, $i, 1);
            $lower = strtolower($char);
            if ($i > 0 && $char != $lower) {
                $tableName .= '_';
            }
            $tableName .= $lower;
        }
        $this->name  = $tableName;
    }
}

?>

クラス名をもとに、文字列操作によってテーブル名を決定するというものです。
継承先のモデルクラスのフィールドで未指定の場合のみ設定するようにしているので、継承先で任意のテーブル名を指定することも可能になっています。

このようにして、できる事はどんどんと基礎クラスで自動的にやるようにしていき、
機能実装の負担を減らしていくとよいと思います。