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句]
 
 となります。これを、関数内で組み立てていることになります。
 ただ一つだけ問題なのが
 のような場合です。
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次元配列を作成しています。
プリペアードステートメントの使用
 PHPBaseは
PDOのプリペアードステートメントも使えます。使用するには
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文の修正なども
プログラムリーダーが行えばいい形になります。