忍者ブログ

VB.NET-TIPS などプログラミングについて

VB.NETのTIPS(小技集)を中心に、Javascript、PHP その他のプログラミングについて少し役に立つ情報を発信します。いわゆる個人的な忘備録ですが、みなさんのお役に立てれば幸いです。

PHP PDO(PHP Data Objects)クラスを使ったデータベースへのアクセスについて


PDO とは PHP Data Objects の略称で、アクセス先のデータベースの種類を意識することなく、アクセスできるようにしたクラスです。

今まではMySQLへ接続する場合は「mysql_connect」関数を使用し、PostgreSQLの場合は「pg_connect」関数で行っていました。 また、SQL実行などもそれぞれの関数を使用しなければなりませんので、データベースごとに異なるクラスを使用する必要があります。

しかし、PDO であればデータベースの違いを吸収してくれますので、同じソースで各データベースにアクセスできます。 (厳密な意味においては、データベース毎の特殊な処理とか、SQLの違いはありますが。)

この記事では、データベースへの接続とテーブルレコード取得を説明します。
テーブルに対しての登録(INSERT)、更新(UPDATE)、削除(DELETE)については、別の記事で説明します。




■データベースへの接続

そこで、先ずは mysql データベースへの接続に付いて説明します。
PDOクラスでは、インスタンス生成を行うと同時に接続を行います。
PDOクラスのコンストラクタ(クラス生成時の関数)は以下の様な宣言になっています。

public PDO::__construct ( string $dsn [, string $username [, string $passwd [, array $options ]]] )

$dsn   	   データソース名(Data Source Name)で、データベースに接続するために 必要な情報が含まれます。
$username  ユーザ名
$passwd    パスワード
$options   ドライバ固有の接続オプションを指定する配列

返り値:    接続成功時に PDO オブジェクトを返します。 

エラー:    データベースへの接続に失敗した場合 PDOException を投げます。


尚、データソース名(DSN)の指定は以下の様になります。

//	DSN指定文字列
mysql:host=hostname;port=portno;dbname=databasename

hostname     データベースサーバが存在するホスト名
portno       データベースサーバーが待機しているポートNO
databasename データベース名

実際にデータベースへの接続を行うソースを見て下さい。 ホストは xampp と共にインストールした MySQL への接続を行っています。
尚、 Windowsコマンドプロンプトで実行するため echo 出力が文字化けしない様に、先頭でコンソール出力への文字コード変換用に 関数を定義しています。

最後の行で、接続を閉じるために PDO オブジェクト変数に null を設定しています。 この処理によって明示的にデータベースへの接続を閉じることが出来ます。 この処理が無くても、スクリプトの終了時にデータベースへの接続を閉じられますが、書いた方が無難かと思います。

<?php
//	Windowsコマンドプロンプトで実行するために、コンソール出力を UTF-8 ⇒ Shift-JIS 変換
ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); });
//  MySQLデータベースに接続
$dsn      = "mysql:host=localhost;dbname=pdo;";
$user     = 'root';
$password = 'password';

try {
    //  PDOクラス生成(データベース接続)
    $pdo = new PDO($dsn, $user, $password);
    echo "データベース接続OK\n";
} catch (PDOException $e) {
    //  エラー発生
    echo 'データベース接続ERROR:'.$e->getMessage()."\n";
    exit();
}

//  データベースへの処理...

//  接続を閉じる
$pdo = null;
?>

これを実行すると以下の様に表示されます。

C:\xampp\htdocs\_test>php pdo1.php
データベース接続OK
 

MySQL は起動されていて pdo と名付けられたデータベースは存在するので、接続OKと表示されます。
そこで エラーを発生させるためにデータベース名を間違ったものにしてみます。 5行目の「pdo」 を 「pdo1」 にして上記のソースを実行します。

C:\xampp\htdocs\_test>php pdo1.php
データベース接続ERROR:SQLSTATE[HY000] [1049] Unknown database 'pdo1'
 

さらにパスワードを間違ったものにしてみます。 7行目の 'password' の中身を 'passwordx' にして上記のソースを実行します。

C:\xampp\htdocs\_test>php pdo1.php
データベース接続ERROR:SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)
 

■テーブルレコード取得(SELECT : query)の例

まず最初に pdo データベースにテスト用のテーブルを作成します。 テーブルは商品マスタ的なものとし 「ID」「名称」「単価」のカラムを持つ簡単なものにします。

データベースを生成し、商品マスタ(tm_shohin)テーブルを生成後、数件のデータを登録しています。 これらの処理をコマンドプロンプトから mysql を起動し実行した結果が以下の様になります。

MariaDB [(none)]> CREATE DATABASE IF NOT EXISTS `pdo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.03 sec)

MariaDB [(none)]> use pdo
Database changed
MariaDB [pdo]> CREATE TABLE `tm_shohin` (
    ->   `id`    bigint(20) NOT NULL,
    ->   `name`  varchar(100) DEFAULT NULL,
    ->   `price` bigint(20) DEFAULT NULL
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.50 sec)

MariaDB [pdo]> ALTER TABLE `tm_shohin`
    ->   ADD PRIMARY KEY (`id`);
Query OK, 0 rows affected (1.34 sec)
Records: 0  Duplicates: 0  Warnings: 0

MariaDB [pdo]> desc tm_shohin;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id    | bigint(20)   | NO   | PRI | NULL    |       |
| name  | varchar(100) | YES  |     | NULL    |       |
| price | bigint(20)   | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

MariaDB [pdo]> set names cp932;
Query OK, 0 rows affected (0.00 sec)

MariaDB [pdo]> INSERT INTO `tm_shohin` (`id`, `name`, `price`) VALUES
    -> (1, 'パソコン001', 100000),
    -> (2, 'パソコン002', 202000),
    -> (3, 'パソコン003', 303000),
    -> (4, 'プリンタ001',  50000),
    -> (5, 'プリンタ002', 150000);
Query OK, 5 rows affected (0.08 sec)
Records: 5  Duplicates: 0  Warnings: 0

MariaDB [pdo]> select * from tm_shohin;
+----+-------------+--------+
| id | name        | price  |
+----+-------------+--------+
|  1 | パソコン001 | 100000 |
|  2 | パソコン002 | 202000 |
|  3 | パソコン003 | 303000 |
|  4 | プリンタ001 |  50000 |
|  5 | プリンタ002 | 150000 |
+----+-------------+--------+
5 rows in set (0.00 sec)

準備は整いましたので、PDOオブジェクトの queryメソッドを使ってデータを取得してみます。 (queryメソッドの説明は本家のマニュアルから以下となります)

//	queryメソッド定義
public PDO::query ( string $statement ) : PDOStatement

$statement 準備、発行する SQL ステートメント。

返り値:    PDOStatement オブジェクトを返します。 失敗した場合は FALSE を返します。 

PDO::query() は、一回の関数コールの中で SQL ステートメントを実行し、このステートメントにより返された 結果セット (ある場合) を PDOStatement オブジェクトとして返します。

複数回発行する必要があるステートメントの場合、 PDO::prepare() で PDOStatement ステートメントを準備し、 PDOStatement::execute() でそのステートメントを 複数回発行する方がより良いパフォーマンスを得られると実感するでしょう。

PDO::query() を次にコールする前に 結果セット内の全てのデータを取得しない場合、そのコールは失敗します。 PDOStatement::closeCursor() をコールし、 次に PDO::query() をコールする前に PDOStatement オブジェクトに関連付けられたリソースを解放してください。 


この query メソッドにSQL文を渡し、返される PDOStatement オブジェクトの fetch メソッドでデータ取得を行います。 (queryメソッドの説明は本家のマニュアルから以下となります)

//	fetchメソッド定義
public PDOStatement::fetch ([ int $fetch_style [, int $cursor_orientation = PDO::FETCH_ORI_NEXT [, int $cursor_offset = 0 ]]] ) : mixed

$fetch_style        レコードを呼び出し元に返す方法を制御します。
                    PDO::FETCH_* 定数のどれかで、デフォルトは  PDO::FETCH_BOTH です。 
$cursor_orientation スクロール可能なカーソルを表す PDOStatement オブジェクトの場合、 
                    この値により呼び出し側に返される行を定義します。
$cursor_offset      スクロール可能なカーソルを表すPDOStatementオブジェクトの場合で、
                    cursor_orientationパラメータが PDO::FETCH_ORI_ABSに設定された場合、
                    この値により 取得される結果セットの行の絶対位置が指定されます。 

返り値:    この関数が成功した場合の返り値は、取得形式によって異なります。
           失敗した場合は常に FALSE を返します。 

queryメソッドは第1引数しか使ったことが無く、通常でしたら引数無しでも十分です。 それでは、商品マスタから「id = 1」の行を取得し表示するソースを以下に示します。

<?php
ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); });
//  MySQLデータベースに接続
$dsn      = "mysql:host=localhost;dbname=pdo;";
$user     = 'root';
$password = 'password';
try {
    //  PDOクラス生成(データベース接続)
    $pdo = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
    //  エラー発生
    die('データベース接続ERROR:'.$e->getMessage()."\n");
}

//  データ取得SQL(結果が1行しか返さない)
$sql = "select * from `tm_shohin` where id = 1";
//  SELECTクエリの実行
$pdostmt = $pdo->query($sql);
//  PDOStatementクラスの fetch メソッドで1行のデータ取得
$result = $pdostmt->fetch();
//  データ表示
echo "id   :".$result["id"]."\n";
echo "name :".$result["name"]."\n";
echo "price:".$result["price"]."\n";

//  接続を閉じる
$pdo = null;
?>

これを実行すると以下の様に表示されます。

C:\xampp\htdocs\_test>php pdo2.php
id   :1
name :パソコン001
price:100000

変数 $resultprint_r で見てみると以下の様になります。 カラム名とカラム位置のキーでデータの配列が設定されていることが分かります。

Array
(
    [id] => 1
    [0] => 1
    [name] => パソコン001
    [1] => パソコン001
    [price] => 100000
    [2] => 100000
)

■テーブルレコード取得(SELECT : query)の複数取得の例

上記のスクリプトでは1件のデータのみの取得を行いましたが、1回目の fetch 処理の後、再度 fetch を行うとどうなるでしょうか?
では以下のスクリプトを実行してみます。

<?php
ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); });
//  MySQLデータベースに接続
$dsn      = "mysql:host=localhost;dbname=pdo;";
$user     = 'root';
$password = 'password';
try {
    //  PDOクラス生成(データベース接続)
    $pdo = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
    //  エラー発生
    die('データベース接続ERROR:'.$e->getMessage()."\n");
}

//  データ取得SQL(結果が1行しか返さない)
$sql = "select * from `tm_shohin` where id = 1";
//  SELECTクエリの実行
$pdostmt = $pdo->query($sql);
//  PDOStatementクラスの fetch メソッドで1行のデータ取得
$result = $pdostmt->fetch();
//  データ内容表示
var_dump($result);

//	再度の fetch メソッド実行
$result = $pdostmt->fetch();
//  データ内容表示
var_dump($result);
//  接続を閉じる
$pdo = null;
?>

これを実行すると以下の様に表示されます。

array(6) {
  ["id"]=>
  string(1) "1"
  [0]=>
  string(1) "1"
  ["name"]=>
  string(15) "パソコン001"
  [1]=>
  string(15) "パソコン001"
  ["price"]=>
  string(6) "100000"
  [2]=>
  string(6) "100000"
}
bool(false)

1回目の fetch 処理後は変数 $result には配列データが返っていますが、 2回目の fetch 処理後は false が返っています。 これは、2回目の fetch でエラーが発生したことを示しています。
指定されたSQL文は1件のデータしか返さないので、2回目の取得ではエラーが発生するのは当然です。

もし複数のデータを返すSQL文で、連続して fetch 処理を行う場合に $result の内容が false であれば、 データの最後まで来て終わったことを示します。 では、複数データを返す例を以下のスクリプトで示します。

<?php
ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); });
//  MySQLデータベースに接続
$dsn      = "mysql:host=localhost;dbname=pdo;";
$user     = 'root';
$password = 'password';
try {
    //  PDOクラス生成(データベース接続)
    $pdo = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
    //  エラー発生
    die('データベース接続ERROR:'.$e->getMessage()."\n");
}

//  データ取得SQL(全てのデータを「id」昇順に取得)
$sql = "select * from `tm_shohin` order by id";
//  SELECTクエリの実行
$pdostmt = $pdo->query($sql);
//  PDOStatementクラスの fetch メソッドで1行のデータ取得
while ($result = $pdostmt->fetch()) {	//	データの取得と終りの判定を同時に行う!!
    //  データが取得できた場合(データ表示)
    echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n";
}
echo "end...\n";
//  接続を閉じる
$pdo = null;
?>

これを実行すると以下の様に表示されます。

C:\xampp\htdocs\_test>php pdo4.php
id:1 name :パソコン001 price:100000
id:2 name :パソコン002 price:202000
id:3 name :パソコン003 price:303000
id:4 name :プリンタ001 price:50000
id:5 name :プリンタ002 price:150000
end...



■テーブルレコード取得(SELECT : prepare, excute)の例

データ取得の方法は query メソッドを使用する他に、 prepare, excute メソッドを使用する方法があります。

//	prepareメソッド定義
public PDO::prepare ( string $statement [, array $driver_options = array() ] ) : PDOStatement

$statement      有効な SQL 文のテンプレート
$driver_options この配列はこのメソッドによって返される PDOStatement オブジェクトに対して
                1 もしくはそれ以上の key=>value の組を含みます。

返り値:  データベースサーバーが正常に文を準備する場合、PDO::prepare() は PDOStatement オブジェクトを返します。
         もしデータベースサーバーが文を準備できなかった場合、
         PDO::prepare() は FALSE を返すか PDOException を発行します (エラー処理 の方法に依存します)。 

このメソッドで指定するSQL文のテンプレートは、後から指定される値で置き換えされるパラメータ識別子を含むものになります。 以下にその例を記します。

//	名前付きパラメータを用いて SQL ステートメントのテンプレート
select * from `tm_shohin` where id >= :id1 and id <= :id2;

//	疑問符パラメータを用いて SQL ステートメントのテンプレート
select * from `tm_shohin` where id >= ? and id <= ?;

名前付きパラメータのSQLは「:id1」「:id2」がパラメータで、疑問符パラメータのSQLは2個の「?」マークがパラメータです。
尚、このSQL文を prepare メソッドで実行し、返された PDOStatement オブジェクトの excute メソッドでパラメータを指定し、 SQLの実行を行います。

//	executeメソッド定義
public PDOStatement::execute ([ array $input_parameters = NULL ] ) : bool

$input_parameters  実行される SQL 文の中のバインドパラメータと同数の要素からなる、値の配列。
                   すべての値は PDO::PARAM_STR として扱われます。

                   ひとつのパラメータに対して複数の値をバインドすることはできません。
                   例えば、IN() 句の中のひとつのパラメータに対して 2 つの値をバインドすることはできません。 

返り値:  成功した場合に TRUE を、失敗した場合に FALSE を返します。 

それでは、名前付きパラメータと疑問符パラメータを用いた場合のスクリプトを以下に記します。

<?php
ob_start(function($buf){ return mb_convert_encoding($buf, 'SJIS', 'UTF-8'); });
//  MySQLデータベースに接続
$dsn      = "mysql:host=localhost;dbname=pdo;";
$user     = 'root';
$password = 'password';
try {
    //  PDOクラス生成(データベース接続)
    $pdo = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
    //  エラー発生
    die('データベース接続ERROR:'.$e->getMessage()."\n");
}

//  データ取得SQL(名前付きパラメータ)
$sql = "select * from `tm_shohin` where id >= :id1 and id <= :id2 order by id";
//  SELECTクエリのprepare
$pdostmt = $pdo->prepare($sql);
//  SELECTクエリのパラメータに実際の値設定
$pdostmt->execute(array(":id1" => "1", ":id2" => "3"));
//  PDOStatementクラスの fetch メソッドで1行のデータ取得
while ($result = $pdostmt->fetch()) {
    //  データが取得できた場合(データ表示)
    echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n";
}
echo "end...1\n";

//  データ取得SQL(疑問符パラメータ)
$sql = "select * from `tm_shohin` where id >= ? and id <= ? order by id";
//  SELECTクエリのprepare
$pdostmt = $pdo->prepare($sql);
//  SELECTクエリのパラメータに実際の値設定
$pdostmt->execute(array(0 => "1", 1 => "4"));	// array("1", "4") でもOK
//  PDOStatementクラスの fetch メソッドで1行のデータ取得
while ($result = $pdostmt->fetch()) {
    //  データが取得できた場合(データ表示)
    echo "id:".$result["id"]." name :".$result["name"]." price:".$result["price"]."\n";
}
echo "end...2\n";
//  接続を閉じる
$pdo = null;
?>

これを実行すると以下の様に表示されます。

C:\xampp\htdocs\_test>php pdo5.php
id:1 name :パソコン001 price:100000
id:2 name :パソコン002 price:202000
id:3 name :パソコン003 price:303000
end...1
id:1 name :パソコン001 price:100000
id:2 name :パソコン002 price:202000
id:3 name :パソコン003 price:303000
id:4 name :プリンタ001 price:50000
end...2

よほどのことが無ければ「疑問符パラメータ」は使用しないと思います。「名前付きパラメータ」の方が分かりやすいと思います。












PR

コメント

コメントを書く