前回作成したDBクラスはシンプルに書くことに努めた結果、足りない部分があったのでここでフォローしていきたいと思います。

ひとまず改善すべき点を整理します。

1.fetchモードで無駄なデータセットが発生しないよう、「カラム名」のみで返すよう指定する。

2.大規模システムにありがちな接続ホストやスキーマがいくつも分かれてる場合においても汎用的に使いやすくする工夫。

3.「操作」をするメソッドをもっとシンプルに使えるようオーバーライドする。

※オーバーライド = 継承メソッドの上書き実装


前回、「操作」は継承したままをそのまま使う形だったので、もっとシンプルになるよう工夫します。
具体的には接続時に返される「PDOオブジェクト」と、SQL実行時に返される「PDOStatementオブジェクト」とに分かれてるのが面倒なので統一化を試みます。
つまり$obj = new DB()で取得したオブジェクトですべて操作できるようにしようという試みです。[$obj->Method()形式]

ではでは書いていきましょ。


<?php
// 親クラス
class DB extends PDO
{
  protected $_sth = null;

  public function __construct($host='', $dbname='') {

    list($host, $dbname, $user, $pass) = $this->_set_info($host, $dbname);

    try {
      parent::__construct("mysql:dbname={$dbname};host={$host}", $user, $pass);
      parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
      parent::setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
      die($e);
    }
  }
  protected function _set_info($host, $dbname) {
    if (empty($host)) $host = 'localhost';
    if (empty($dbname)) $dbname = 'mysql';
    return array($host, $dbname, 'root', '');
  }
  public function query($sql) {
    $this->_sth = parent::query($sql);
    return $this->_sth;
  }
  /* 継承メソッドと同じなので不要
  public function exec($sql) {
    return parent::exec($sql);
  }
  */
  public function prepare($sql) {
    $this->_sth = parent::prepare($sql);
    return $this->_sth;
  }
  public function execute($param=array()) {
    $this->_sth->execute($param);
    return $this->rowCount();
  }
  public function rowCount() {
    return $this->_sth->rowCount();
  }
  public function fetch() {
    return $this->_sth->fetch();
  }
  public function fetchAll() {
    return $this->_sth->fetchAll();
  }
}

// 動作確認
$db = new DB();
$db->query("select 1");
$row = $db->fetch();
var_dump($row);

var_dumpの結果
array(1) {
  [1]=>
  string(1) "1"
}

はいっ!
狙い通り今度は1要素が返ってきました。改善点1が成功したということです。

コンストラクタ内に以下を追加することでfetchモード変更を行っています。
parent::setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

同じ場所に以下を追加してるのは、処理エラーが発生した場合にExceptionをスローさせるようにするためです。
これでエラー発生の可能性がある部分をtry,catchブロックで囲むことでハンドリングが楽になると思います。
parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

では次の改善点2についてですが、大規模システム等にある多ホスト、多スキーマへの対応として_set_info()メソッドをワンクッション入れています。
これによって、このDBクラスを継承した子クラスではコンストラクタ部分をイジらずに_set_info()メソッドをオーバーライドすることで自分の環境に合った調整がしやすくなると思います。

例えばhost1のときはBLOGスキーマに接続して、host2のときはBBSスキーマに接続するといったサービスごとに違った接続先になる場合を考えます。
以下のようにオーバーライドできると思います。
protected function _set_info($host, $dbname) {
  if ($host === 'host1') $dbname = 'BLOG';
  else if ($host === 'host2') $dbname = 'BBS';

  return array($host, $dbname, 'root', '');
}

また、HTTPリクエスト先によって接続先を分けるなど自由自在です。
$_SERVER['REQUEST_URI']や$_SERVER['SCRIPT_NAME']などから$host,$dbnameへのセット値を判断するなどなど・・
当然$_SERVER['SERVER_NAME']が"www1.example.com"ならBLOGで、"www2.example.com"ならBBSなどサーバ名で分けることもできますね。


そして改善点3ですが、query()メソッドが返すPDOStatementオブジェクトを使わずにfetch()できるようにしています。
クラス内部に追加した$_sthプロパティに保持させることで内部で管理するようにしています。
query()メソッドから$thisを返すようにすれば以下のようにメソッドチェーンで書くことだってできそうです。
$row = $db->query("select 1")->fetch();


と、これで改善はできました。
でももっと改善できないだろうか?
ということで次回につなげたいと思います。


今朝からずっと頭が痛くてモガいてるわけですが、幸いにも休みだったので事なきを得た。


今年は後半私なりに忙しくてたいへんな思いもしたが、まぁどうにかこうにか年越せそうだ。
何年ぶりだろうか、珍しく風邪をひいてキツかった。
でも自分の中に抵抗力を養うためにはたまには風邪ひいた方がいいのかな?


一方、近所のスーパーが2日まで休みだということが判明したので生命維持のためのブツを調達してストックしておく必要がある。
とは言っても2日間ぐらい無くても生命維持に支障はないだろう。
こちとら万が一の時のために缶詰のストックもあるもんね。



にしてもいち早く頭痛を治したいものだ。
以前箱買いした頭痛薬は温存しすぎたせいで、使用期限がとっくに過ぎてしまっていた。
これって効果が薄れるのかなぁ。。


薬って、もっと長持ちするように作れないものかね。
薬作ってるメーカーに問いたい。
薬を缶詰に入れておくとか真空パック?冷凍保存?なんかそういうの頼むぜぃ!



あと寒ーい!
防寒着着てても寒いよー! ><
もっと暖かい防寒着を見つけなくてはなるまい。(来年の目標)

データベースの処理は「接続」してその後に、「情報くれ」やら「この情報覚えといて」やら指示を与える操作になります。
「接続」と「操作」に大分類できるわけです。
この決まったパターンがあるわけですから、汎用的に使えるようクラス化しておきたくなりますね。

PHP5からは標準でPDOという拡張モジュールが使えると思うのでこれを使います。
というか、PDO自体クラス化されてるのでPHP実行時にはPDOクラスはロードされてることになります。

ではクラス化する必要ないのかというと、人間は欲張りですからもっと使いやすくしたいという欲望があります。
既存のクラスだとあまりにも汎用的過ぎて、毎回決まった指示工程を与える余計な部分がどうしても出てしまいます。

データベースを使っていると、だいたいMySQLを固定的に使う人だったり、ユーザ名やパスワードも一定だったりするのでできるだけSQL実行までの手間を省きたいというのがあります。

そこで以下の2パターンでクラスを作るように考えました。
どのレベルまで汎化しておくのかが重要です。

1.いろいろなシステムで汎用的に使い回せる親クラス
  「接続」を自分のパターンに固定する
  「操作」をもっと簡単にできるよう工夫する

2.親クラスを継承して特定のシステムで共通に使い回す子クラス
  「接続」はほぼ継承したままで変更の必要なし
  「操作」具体的なそのシステム専用のSQL文を親クラスの操作メソッドを流用して書き足す


ではまず親クラスを作ってみます。
先に書いたとおり、実行時にはPDOクラスは定義されてるのでこれを継承したDBクラスを親クラス(PDOから見たら子クラスですが)として作成することにします。

<?php
// 親クラス
class DB extends PDO
{
  public function __construct($host='', $dbname='') {
    if (empty($host)) $host = 'localhost';
    if (empty($dbname)) $dbname = 'mysql';
    try {
      parent::__construct("mysql:dbname={$dbname};host={$host}", 'root', '');
    } catch (PDOException $e) {
      die($e);
    }
  }
}

// 動作確認
$db = new DB();
$sth = $db->query("select 1");
$row = $sth->fetch();
var_dump($row);


MySQLが起動している状態でこれを実行してみます。


var_dumpの結果
array(2) {
  [1]=>
  string(1) "1"
  [2]=>
  string(1) "1"
}


うまく動作しました。
私の場合、DBMSにMySQLを使うのでハードコーディングで固定してしまいました。
また、ユーザはrootでパスワード無しで使うのでこれも固定にしています。

以下のように丁寧に指定したり、別ホスト別スキーマを指定できるよう汎用的な部分も残しています。
$db = new DB("localhost", "mysql");

でも気になる部分があります。
var_dumpの結果が1つではなく、2要素返ってきています。
これはfetchモードがデフォルトのままなのでご丁寧に「カラム名」と「カラム番号」添字で返してるためです。
無駄が発生するので、「カラム名」のみで返すよう親クラスを修正する必要があります。

あと、大規模システムにありがちな接続ホストやスキーマがいくつも分かれてる場合においても汎用的に使いやすくなるよう工夫する余地があります。