PDOを使ってみた

PHPとMySQLとの接続について,これまでは,mysql_connect()関数を使用してきたのですが,最近,PHP 5.5より非推奨らしいということで,いずれmysql_connect()は使えなくなってしまうらしいのです.では,代わりに何があるのか調べてみました.

方法として3種類あるそうな.

  • mysql — これまでの拡張モデル(PHP 5.5より非推奨)
  • mysqli — 改良版拡張モジュール(PHP 5.0以上,MySQL 4.1以上)
  • PDO_MySQL — PHP Data Object データアクセスの抽象レイヤ(PHP 5.1以上)

参考 PHP: MySQL 用 PHP ドライバの概要 – Manual
http://jp2.php.net/manual/ja/mysql.php

これまで,PHPは,MySQLとの接続には「mysql_connect()」,PostgreSQLとの接続には「pg_connect()」関数をそれぞれ利用していました.そのため,データベースを移植した場合は,プログラムを大幅に書き換える必要がありました.そこで,どのデータベースを利用してもプログラムを変更する事無く使えるよう,抽象的レイヤPDOを利用できるようになりました.

今回は,SQLite3のデータベースを準備.

$ sqlite3 sample.sqlite3
SQLite version 3.7.10 2012-01-16 13:28:40
Enter ".help" for instructions
Enter SQL statements terminated with a ";"

sqlite> select * from member;
0001|Taro Kagoshima|171.5|65.2
0002|Hanako Ohsumi|158.5|50.0

sqlite> .schema member
CREATE TABLE member (
id char(4) not null,
name char(100),
height char(10),
weight char(10),
primary key(id)
);

sqlite>.quit

では,PDOを利用したサンプルプログラムを見てください.

$dsn = 'sqlite:./sample.sqlite3';
$user = null;
$password = null;

try {
  // データベースへの接続,PDOインスタンスの生成
  $db = new PDO($dsn, $user, $password);
} catch(PDOException $e) {
  printf("Error: %s\n", $e->getMessage());
  die();
}

$sql = "SELECT * FROM member WHERE id = ?";
$stmt = $db->prepare($sql);
$stmt->execute(array('0002'));

$rows = $stmt->fetch(PDO::FETCH_ASSOC);

print_r($rows);
print_r($rows['name']);

実行結果はこうなります.

$ php -f test1.php
Array
(
    [id] => 0002
    [name] => Hanako Ohsumi
    [height] => 158.5
    [weight] => 50.0
)
Hanako Ohsumi

PDOインスタンス生成の際に,データソース名($dsn),ユーザ名,パスワードを指定します.今回はSQLiteの例を示しましたが,MySQLの場合は次のような感じになります.

$dsn = 'mysql:dbname=sample;host=localhost;charset=utf8';
$user = 'foo';
$password = 'hogehoge';

この部分だけ書き換えれば,後のプログラムはいじることなくデータベースを移行できます.

PDOオブジェクト($db)のprepare()メソッドの引数にSQLを指定して呼び出すことにより,PDOStatementオブジェクト($stmt)を生成します.そのPDOStatementオブジェクトのexecute()メソッドの引数に配列,これにはプリペアステートメントの「?」にあたる部分の値を配列形式で指定します.今回は,「?」が1つだけなので,要素数1の配列になります.fetch()メソッドにPDO::FETCH_ASSOCを指定することにより,フィールド名をキーとする連想配列でレコードを取得することが可能です.

他にも,

$id = '0002';
$sql = "SELECT * FROM member WHERE id = ?";
$stmt = $db->prepare($sql);
$stmt->bindParam(1, $id, PDO::PARAM_STR);
$stmt->execute();

や,次のような指定が可能です.

$id = '0002';
$sql = "SELECT * FROM member WHERE id = :id";
$stmt = $db->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_STR);
$stmt->execute();

fetch()メソッドをfetchObject()メソッドに書き換え,引数にクラス名を指定することで,その出力がそのクラスのオブジェクトになります.

class Member {
}

$dsn = 'sqlite:./sample.sqlite3';
$user = null;
$password = null;

try {
// データベースへの接続, PDOインスタンスの生成
    $db = new PDO($dsn, $user, $password);
} catch(PDOException $e) {
    printf("Error: %s\n", $e->getMessage());
    die();
}

$id = '0002';
$sql = "SELECT * FROM member WHERE id = :id";
$stmt = $db->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_STR);
$stmt->execute();

$rows = $stmt->fetchObject('Member');

print_r($rows);
print_r($rows->name);

結果としてはこうなります.

Member Object
(
    [id] => 0002
    [name] => Hanako Ohsumi
    [height] => 158.5
    [weight] => 50.0
)
Hanako Ohsumi

せっかくMemberクラスを作成したので,次のような感じで,find()メソッドを作成し,引数にIDの値を指定することにより,結果セットを取得したいのですが,どうすれば良いでしょうか.

$obj = Member::find('0002');
print_r($obj);

そのためには,Memberクラスにstaticな関数を定義します.ついでにデータベース接続関係もinit()メソッドとして定義します.ここまでのソースを次ぎに示します.

<?php                                                                                                                                                   
class Member {
    private static $db;

    // データベースへの接続,PDOインスタンスの生成
    public static function init() {
        $_dsn = 'sqlite:./sample.sqlite3';
        $_user = null;
        $_password = null;
        try {
            self::$db = new PDO($_dsn, $_user, $_password);
        } catch(PDOException $e) {
            printf("Error: %s\n", $e->getMessage());
            self::$db = null;
        }
    }

    public static function find($id) {
        $sql = "SELECT * FROM member WHERE id = :id";
        $stmt = self::$db->prepare($sql);
        $stmt->bindParam(':id', $id, PDO::PARAM_STR);
        $stmt->execute();
        $ret = $stmt->fetchObject("Member");
        return $ret;
    }
}

Member::init();
$obj = Member::find('0002');
print_r($obj);
?>

同様に,すべてのレコードオブジェクトを出力するfindAll()メソッドも実装してしまいましょう.今回の結果セットは配列になりますので,返り値が配列になっています.

<?php                                                                                                                                                   
class Member {
 :
 (略)
 :
     public static function findAll() {
          $sql = "SELECT * FROM member";
          $stmt = self::$db->prepare($sql);
          $stmt->execute();
          $rets = array();
          while($ret = $stmt->fetchObject('Member'))
               $rets[] = $ret;
          return $rets;
     }       
}

Member::init();
$obj = Member::findAll();
print_r($obj);
?>

結果は次の様になります.

Array
(
    [0] => Member Object
        (
            [id] => 0001
            [name] => Taro Kagoshima
            [height] => 171.5
            [weight] => 65.2
        )

    [1] => Member Object
        (
            [id] => 0002
            [name] => Hanako Ohsumi
            [height] => 158.5
            [weight] => 50.0
        )
)

ついでに,削除も考えてみます.

$obj = Member::find('0002');
$obj->delete();
print_r($obj);

こんな風に,削除できるようにするには,次のようなインスタンスメソッドを準備します.

class Member {
 :
 (略)
 :
     // 任意のIDのレコードを削除する
     public function delete() {
          $sql = "DELETE FROM member WHERE id = :id";
          $stmt = self::$db->prepare($sql);
          $stmt->bindParam(':id', $this->id, PDO::PARAM_STR);
          $stmt->execute();
     }    

}

Member::init();

$obj = Member::find('0002');
$obj->delete();

$obj = Member::findAll();
print_r($obj);
?>

そうなってくると,次のような感じで,任意のレコードの更新や,新規レコードの生成など,ActiveRecordのような感じでやってみたくなります.

更新は...

$obj = Member::find('0002');
$obj->name = 'Hana Ohsumi';
$obj->height = '160.0';
$obj->save();

新規作成は...

$obj = new Member;
$obj->id = '0005';
$obj->name = 'Hayato Satsuma';
$obj->height = '168.0';
$obj->save();

いずれにしてもsave()メソッドを作ることになります.色々やり方はあると思いますが,ここは単純に,対象オブジェクトで指定したidが,既に使われているidであれば更新(update)で,存在しないidであれば追加(insert)とします.すると,saveメソッドは次のような実装になります.

    public function save() {
        $obj = self::find($this->id);
        // 新規レコード追加(存在しないidの場合)
        if($obj == null) {
            $stmt = self::$db->prepare("INSERT INTO member (id, name, height, weight) values (:id, :name, :height, :weight)");
            $stmt->bindParam(':id', $this->id, PDO::PARAM_STR);
            $stmt->bindParam(':name', $this->name, PDO::PARAM_STR);
            $stmt->bindParam(':height', $this->height, PDO::PARAM_STR);
            $stmt->bindParam(':weight', $this->weight, PDO::PARAM_STR);
            $stmt->execute();
        }
        // 既存レコードの更新
        else {
            $stmt = self::$db->prepare("UPDATE member SET name = :name, height = :height, weight = :weight WHERE id = :id");
            $stmt->bindParam(':name', $this->name, PDO::PARAM_STR);
            $stmt->bindParam(':height', $this->height, PDO::PARAM_STR);
            $stmt->bindParam(':weight', $this->weight, PDO::PARAM_STR);
            $stmt->bindParam(':id', $this->id, PDO::PARAM_STR);
            $stmt->execute();
        }
    }

それでは,ここまでのまとめです.ここでは新たに(今後のために)PDOStatementオブジェクトを引数に持ち,単一のレコードオブジェクトを返すgetRecord()メソッドと,複数の結果セット,即ちレコードオブジェクト配列を返すgetRecords()メソッドを加えました.そのため,find()メソッドとfindAll()メソッドに少し修正を加えています.

<?php
class Member {
	private static $db;

	// データベースへの接続,PDOインスタンスの生成
	public static function init() {
		$_dsn = 'sqlite:./sample.sqlite3';
		$_user = null;
		$_password = null;
		try {
			self::$db = new PDO($_dsn, $_user, $_password);
		} catch(PDOException $e) {
			printf("Error: %s\n", $e->getMessage());
			self::$db = null;
		}
	}

	// 結果セット,レコードオブジェクトの配列を返す
	protected static function getRecords(PDOStatement $stmt) {
		$rets = array();
		while($ret = $stmt->fetchObject("Member")) $rets[] = $ret;
		return $rets;
	}
	
	// 単一のレコードオブジェクトを返す
	protected static function getRecord(PDOStatement $stmt) {
		$ret = $stmt->fetchObject("Member");
		return $ret;
	}

	// 任意のIDの単一レコードを返す
	public static function find($id) {
		$sql = "SELECT * FROM member WHERE id = :id";
		$stmt = self::$db->prepare($sql);
		$stmt->bindParam(':id', $id, PDO::PARAM_STR);
		$stmt->execute();
		return self::getRecord($stmt);
	}
	
	// すべてのレコードを返す
	public static function findAll() {
		$sql = "SELECT * FROM member";
		$stmt = self::$db->prepare($sql);
		$stmt->execute();
		return self::getRecords($stmt);
	}

	// 任意のIDのレコードを削除する
	public function delete() {
		$sql = "DELETE FROM member WHERE id = :id";
		$stmt = self::$db->prepare($sql);
		$stmt->bindParam(':id', $this->id, PDO::PARAM_STR);
		$stmt->execute();
	}	

	// レコードの保存
    public function save() {
        $obj = self::find($this->id);
        // 新規レコード追加(存在しないidの場合)
        if($obj == null) {
            $stmt = self::$db->prepare("INSERT INTO member (id, name, height, weight) values (:id, :name, :height, :weight)");
            $stmt->bindParam(':id', $this->id, PDO::PARAM_STR);
            $stmt->bindParam(':name', $this->name, PDO::PARAM_STR);
            $stmt->bindParam(':height', $this->height, PDO::PARAM_STR);
            $stmt->bindParam(':weight', $this->weight, PDO::PARAM_STR);
            $stmt->execute();
        }
        // 既存レコードの更新
        else {
            $stmt = self::$db->prepare("UPDATE member SET name = :name, height = :height, weight = :weight WHERE id = :id");
            $stmt->bindParam(':name', $this->name, PDO::PARAM_STR);
            $stmt->bindParam(':height', $this->height, PDO::PARAM_STR);
            $stmt->bindParam(':weight', $this->weight, PDO::PARAM_STR);
            $stmt->bindParam(':id', $this->id, PDO::PARAM_STR);
            $stmt->execute();
        }
    }

}
// bootstrap
Member::init();

// idが0002のレコードの内容を更新する
$obj = Member::find('0002');
$obj->name = 'Hana Ohsumi';
$obj->height = '160.0';
$obj->save();

// 新しいレコードを追加する
$obj = new Member;
$obj->id = '0005';
$obj->name = 'Hayato Satsuma';
$obj->height = '168.0';
$obj->save();

// 現在のすべてのレコードを表示する
$obj = Member::findAll();
print_r($obj);
?>

これで利用する側としては,SQLを一切書くこと無く,ある程度のCRUD処理が可能となります.まだまだ色々とバグがありそうですが...

広告

One thought on “PDOを使ってみた

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中