001.中間層PHPの構造と記述方法

 この項では中間層PHPであるcommon/contents_db.phpについて説明します。
 中間層PHPとは具体的にはSELECT文を発行するクラスです。INSERT、UPDATE、DELETE中間層PHPは一般的には個々には記述しません。これら更新系のSQLについては後述します。
 これから説明するようなクラスは、実際にPHPでサイトを作成する上で、各自が記述する必要があります。このドキュメントではサンプルとして都道府県テーブル用のクラスが記述されています。
 この項では、都道府県テーブル用のクラスを説明することで、記述方法の説明とします。

コンテンツDBクラス

 common/contents_db.phpをエディタで開くと以下のようなクラスがあります。
//--------------------------------------------------------------------------------------
/// 都道府県クラス
//--------------------------------------------------------------------------------------
class cprefecture extends crecord {
    //--------------------------------------------------------------------------------------
    /*!
    @brief  コンストラクタ
    */
    //--------------------------------------------------------------------------------------
    public function __construct() {
        //親クラスのコンストラクタを呼ぶ
        parent::__construct();
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief  すべての個数を得る
    @param[in]  $debug  デバッグ出力をするかどうか
    @return 個数
    */
    //--------------------------------------------------------------------------------------
    public function get_all_count($debug){
        //親クラスのselect()メンバ関数を呼ぶ
        $this->select(
            $debug,                 //デバッグ文字を出力するかどうか
            "count(*)",             //取得するカラム
            "prefecture",           //取得するテーブル
            "1"                 //条件
        );
        if($row = $this->fetch_assoc()){
            //取得した個数を返す
            return $row['count(*)'];
        }
        else{
            return 0;
        }
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定された範囲の配列を得る
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $from   抽出開始行
    @param[in]  $limit  抽出数
    @return 配列(2次元配列になる)
    */
    //--------------------------------------------------------------------------------------
    public function get_all($debug,$from,$limit){
        $arr = array();
        //親クラスのselect()メンバ関数を呼ぶ
        $this->select(
            $debug,         //デバッグ表示するかどうか
            "*",            //取得するカラム
            "prefecture",   //取得するテーブル
            "1",            //条件
            "prefecture_id asc",    //並び替え
            "limit " . $from . "," . $limit     //抽出開始行と抽出数
        );
        //順次取り出す
        while($row = $this->fetch_assoc()){
            $arr[] = $row;
        }
        //取得した配列を返す
        return $arr;
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定された範囲の配列を得る(プレースホルダつき版)
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $from   抽出開始行
    @param[in]  $limit  抽出数
    @return 配列(2次元配列になる)
    */
    //--------------------------------------------------------------------------------------
    public function get_all_prep($debug,$from,$limit){
        $arr = array();
        $query = <<< END_BLOCK
select
*
from
prefecture
where
1
order by
prefecture_id asc
limit ?, ?
END_BLOCK;
        $prep_arr = array($from,$limit);
        //親クラスのselect_query()メンバ関数を呼ぶ
        $this->select_query(
            $debug,         //デバッグ表示するかどうか
            $query,         //プレースホルダつきSQL
            $prep_arr       //データの配列
        );
        //順次取り出す
        while($row = $this->fetch_assoc()){
            $arr[] = $row;
        }
        //取得した配列を返す
        return $arr;
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定されたIDの配列を得る
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $id     ID
    @return 配列(1次元配列になる)空の場合はfalse
    */
    //--------------------------------------------------------------------------------------
    public function get_tgt($debug,$id){
        if(!is_int($id)
        ||  $id < 1){
            //falseを返す
            return false;
        }
        //親クラスのselect()メンバ関数を呼ぶ
        $this->select(
            $debug,         //デバッグ表示するかどうか
            "*",            //取得するカラム
            "prefecture",   //取得するテーブル
            "prefecture_id=" . $id  //条件
        );
        return $this->fetch_assoc();
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定されたIDの配列を得る(プレースホルダつき版)
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $id     ID
    @return 配列(1次元配列になる)空の場合はfalse
    */
    //--------------------------------------------------------------------------------------
    public function get_tgt_prep($debug,$id){
        if(!is_int($id)
        ||  $id < 1){
            //falseを返す
            return false;
        }
        $query = <<< END_BLOCK
select
*
from
prefecture
where
prefecture_id = ?
END_BLOCK;
        $prep_arr = array($id);
        //親クラスのselect_query()メンバ関数を呼ぶ
        $this->select_query(
            $debug,         //デバッグ表示するかどうか
            $query,         //プレースホルダつきSQL
            $prep_arr       //データの配列
        );
        return $this->fetch_assoc();
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief  デストラクタ
    */
    //--------------------------------------------------------------------------------------
    public function __destruct(){
        //親クラスのデストラクタを呼ぶ
        parent::__destruct();
    }
}
 このクラスは、都道府県テーブルSELECT文用のクラスです。
 都道府県テーブルprefectureという名前です。prefectureをSELECTするクラスなのでcprefectureクラスとしています。
 このドキュメントでは、このようなクラスをコンテンツDBクラスと称します。コンテンツDBクラスは、必ずcrecordクラスを継承して作成します。
 prefectureテーブルのようなテーブルをマスタテーブルといいます。日常的に変更が行われることが少ないテーブルです。マスタテーブルは通常は管理画面などで追加や更新ができるようになっています。
 単純なマスタテーブル用のSQL文は、総レコードの数を得る指定したレコードから何行かを取り出す指定したレコードを取り出すの3つのアクセス方法があれば足りる場合が多いと思います。

SELECT文を発行する

 cprefectureクラスはこららのSQLに対応するメンバ関数が記述されています。
 まず、総レコードの数を得るget_all_count()関数です。以下に抜き出します。
    //--------------------------------------------------------------------------------------
    /*!
    @brief  すべての個数を得る
    @param[in]  $debug  デバッグ出力をするかどうか
    @return 個数
    */
    //--------------------------------------------------------------------------------------
    public function get_all_count($debug){
        //親クラスのselect()メンバ関数を呼ぶ
        $this->select(
            $debug,                 //デバッグ文字を出力するかどうか
            "count(*)",             //取得するカラム
            "prefecture",           //取得するテーブル
            "1"                 //条件
        );
        if($row = $this->fetch_assoc()){
            //取得した個数を返す
            return $row['count(*)'];
        }
        else{
            return 0;
        }
    }
 まず、この関数の引数は$debugです。これは発行されたSQL文を表示するかどうかのフラグで、これをtrueにするとSQL文をechoします。
 続いて$this->select(...);と、親クラスのselect()関数を呼び出します。この関数はcommon/pdointerface.phpに記述があり、以下のような内容になっています。
    //--------------------------------------------------------------------------------------
    /*!
    @brief  select文の実行
    @param[in]  $debug クエリを出力するかどうか
    @param[in]  $columns 取得するカラム
    @param[in]  $table 取得するテーブル
    @param[in]  $where 条件文(省略可)
    @param[in]  $orderby ならび順(省略可)
    @param[in]  $limit 抽出範囲(省略可)
    @return 成功すればtrue
    */
    //--------------------------------------------------------------------------------------
    public function select($debug,$columns,$table,$where = '1',$orderby = '',$limit = ''){
        global $DB_PDO;
        $this->free_res();
        if($orderby != ""){
            $orderby = "order by " . $orderby;
        }
        $this->retarr['sql'] =<<< END_BLOCK
select
{$columns} 
from
{$table}
where
{$where}
{$orderby}
{$limit}
END_BLOCK;
        //親クラスのcrecord_core_pres関数を呼ぶ(値は渡さない)
        $ret =  $this->crecord_core_pres(array());
        if($debug){
            echo '<br />[sql debug]<br />';
            $this->res->debugDumpParams();
            echo '<br />[/sql debug]<br />';
        }
        return $ret;
    }
 赤くなっているところがSELECT文を組み立てているところです。
 SELECT文は、一般的には
select
抽出するカラム
from
テーブル
where
条件式
[ソート順]
[limit句]
 となります。これを、関数内で組み立てていることになります。
 ただ一つだけ問題なのが
select
*
from
prefecture
 のような場合です。whereは省略可能です。
 しかし、このSQLは
select
*
from
prefecture
where
1
 と記述することも可能です。1というのはという意味がありすべてということになります。
 ですからcprefecture::get_all_count()関数が作り出すSQL文は
select
count(*)
from
prefecture
where
1
 となります。count(*)は、SQLの総数を返す内部コマンドです。
 またselect関数が呼び出す$this->crecord_core_pres(array());array()プリペアードステートメント使わない呼び出し方となります。プリペアードステートメントを使う場合は、のちに説明するselect_query関数を使ってください。

SELECT文を発行する

 このように$this->select(...);を実行することによりSQL文が発行されます。正常にリソースが取得できると、
        if($row = $this->fetch_assoc()){
            //取得した個数を返す
            return $row['count(*)'];
        }
        else{
            return 0;
        }
 と$this->fetch_assoc()関数呼び出しで1行分の配列データを取得します。この関数は、内部的にはPDOStatement::fetch_assoc()関数を呼び出します。 この関数は取得できない場合FALSEを返します。
 このように、cprefecture::get_all_count()関数の戻り値は行数になります。

一覧表示用のメンバ関数

 一覧表示に使用されるSELECT文はcprefecture::get_all()関数で実装します。
 SELECT文発行のメカニズムは上記と変わりありません。1つ注意点は、この関数は何行目から何行分を取り出すという設計になっています。これは、ページ分けするのに必要な処理です。
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定された範囲の配列を得る
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $from   抽出開始行
    @param[in]  $limit  抽出数
    @return 配列(2次元配列になる)
    */
    //--------------------------------------------------------------------------------------
    public function get_all($debug,$from,$limit){
        $arr = array();
        //親クラスのselect()メンバ関数を呼ぶ
        $this->select(
            $debug,         //デバッグ表示するかどうか
            "*",            //取得するカラム
            "prefecture",   //取得するテーブル
            "1",            //条件
            "prefecture_id asc",    //並び替え
            "limit " . $from . "," . $limit     //抽出開始行と抽出数
        );
        //順次取り出す
        while($row = $this->fetch_assoc()){
            $arr[] = $row;
        }
        //取得した配列を返す
        return $arr;
    }
 このようなパラメータになっています。$from$limitを受け取ります。この値を設定するのは一覧ページであるprefecture_list.phpで行いますので、ページ分けについては、一覧ページの説明で行います。
 ここではcprefecture::get_all()関数$from$limitを受け取るように作成しておく、ということだけ確認しておいてください。
 なおcprefecture::get_all()関数は2次元配列を返します。cprefecture::get_all_count()関数の形とは違います。受け取ったほうはその2次元配列をもとにHTMLに書式化してechoする形になります。
        //順次取り出す
        while($row = $this->fetch_assoc()){
            $arr[] = $row;
        }
 の部分で2次元配列を作成しています。

プリペアードステートメントの使用

 PHPBasePDOのプリペアードステートメントも使えます。使用するにはget_all_prep関数にように記述するのがいいでしょう。
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定された範囲の配列を得る(プレースホルダつき版)
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $from   抽出開始行
    @param[in]  $limit  抽出数
    @return 配列(2次元配列になる)
    */
    //--------------------------------------------------------------------------------------
    public function get_all_prep($debug,$from,$limit){
        $arr = array();
        $query = <<< END_BLOCK
select
*
from
prefecture
where
1
order by
prefecture_id asc
limit ?, ?
END_BLOCK;
        $prep_arr = array($from,$limit);
        //親クラスのselect_query()メンバ関数を呼ぶ
        $this->select_query(
            $debug,         //デバッグ表示するかどうか
            $query,         //プレースホルダつきSQL
            $prep_arr       //データの配列
        );
        //順次取り出す
        while($row = $this->fetch_assoc()){
            $arr[] = $row;
        }
        //取得した配列を返す
        return $arr;
    }
 ここではSELECT文をすべて記述しています。赤くなってるところがプレースホルダの指定です。?を使う形で記述します。そして
        $prep_arr = array($from,$limit);
 と値の配列を作り出し、
        $this->select_query(
            $debug,         //デバッグ表示するかどうか
            $query,         //プレースホルダつきSQL
            $prep_arr       //データの配列
        );
 と、親クラスのselect_query関数を呼び出します。select_query関数SQL文をすべて記述して呼び出します。必要であればその中に?を使うプレースホルダも記述し、値の配列を渡します。プレースホルダを使わない場合は
    //--------------------------------------------------------------------------------------
    /*!
    @brief  指定された範囲の配列を得る(プレースホルダつき版)
    @param[in]  $debug  デバッグ出力をするかどうか
    @param[in]  $from   抽出開始行
    @param[in]  $limit  抽出数
    @return 配列(2次元配列になる)
    */
    //--------------------------------------------------------------------------------------
    public function get_all_prep($debug,$from,$limit){
        $arr = array();
        $query = <<< END_BLOCK
select
*
from
prefecture
where
1
order by
prefecture_id asc
limit {$from}, {$limit}
END_BLOCK;
        //親クラスのselect_query()メンバ関数を呼ぶ
        $this->select_query(
            $debug,         //デバッグ表示するかどうか
            $query          //プレースホルダつきSQL
        );
        //順次取り出す
        while($row = $this->fetch_assoc()){
            $arr[] = $row;
        }
        //取得した配列を返す
        return $arr;
    }
 のように記述しても大丈夫です。

詳細表示用のメンバ関数

 詳細表示用のメンバ関数は詳細ページ(prefecture_detail.php)で使用しするcprefecture::get_tgt()関数になります。この関数はパラメータにプライマリキー(prefecture_id)を渡します。SELECT文を発行する前に、パラメータの有効性(数字でなおかつ1以上かどうか)をチェックしています。
 このチェックに使っているis_int()関数整数かどうかを判別する関数です。

PHPBaseの考え方

 以上、ざっとですがコンテンツDBクラスであるcprefectureクラスについて説明しました。
 PHPBaseの考え方は、このようなコンテンツDBクラスを記述する作業と、prefecture_list.phpやprefecture_detail.phpを記述する作業を分けることで、コードの階層化を図ろう、ということです。
 各テーブルに対して(あるいはリレーショナルで同時にSELECT文に含めるテーブルのグループに対して)、コンテンツDBクラスを記述することで、上層部のコードからはSQL文を意識しないで記述ができるようになります。
 チームで組む場合は、例えばプログラムリーダー的な人がコンテンツDBクラスを担当し、それ以外に人が各ページを担当すれば、SQLの発行はプログラムリーダーが組むコンテンツDBクラスからしかできない形になります。
 こうしておけば、突然、クライアントの要求でテーブルの内容を変える必要が出てきたとしても、上層部のプログラマは意識しないですみます。テーブル修正によるSQL文の修正などもプログラムリーダーが行えばいい形になります。