データベースとトモダチになる
PHPでデータベースとトモダチになるならPDO
を使うのが一般的のようです。@MINOも大分データベースと仲良くなってきましたが、まだまだ奥が深く、いつ仲違いしてしまうか油断は禁物です。
今回はやはりデータベースのデータをしっかり扱いたいという欲求からPDO
のquery
メソッドのリザルトの取得モードについてまとめてみました。
単発のSQL
を実行するならquery
メソッドは楽ですよね。
ちょっとした説明
query
メソッドの戻り値はPDOStatement
です。
@MINOなんかはforeach
で回して配列に収めた後に処理していましたが、これは無駄でした。
いちいち配列などに格納する必要はありません。
PDOStatement
オブジェクトはトラバーサル(横断的)にアクセス可能なので、そのままforeach
で回せます。
$sql = 'SELECT * FROM testtable';
foreach ($pdo->query($sql, PDO::FETCH_BOTH) as $value) {
var_dump($value);
}
取得モードいろいろ
PDO
では実に多彩に結果の取得形式を指定することが可能です。
以下のようなデータベーステーブルでテストしてみました。
id | name | age |
---|---|---|
1 | saitou | 23 |
2 | sasayama | 39 |
3 | ikariya | 33 |
4 | miyama | 23 |
5 | oota | 39 |
まず全体的なコードです
ここから一個ずつ説明していきたいと思います。
FETCH_BOTH 連想配列と添字配列
デフォルトの取得モードです。連想配列を添字配列の両方が一度に出力されます。
echo "-デフォルト--------------------------------\n\n";
//デフォルト
//連想配列と添字配列のハイブリット
foreach ($pdo->query($sql, PDO::FETCH_BOTH) as $value) {
var_dump($value);
}
一度で二度美味しいのですが、どちらかだけが必要になる場合が多い様に思えるので、意外と美味しくない取得方法かもしれません。
-デフォルト--------------------------------
array(6) {
["id"]=>
string(1) "1"
[0]=>
string(1) "1"
["name"]=>
string(6) "saitou"
[1]=>
string(6) "saitou"
["age"]=>
string(2) "23"
[2]=>
string(2) "23"
}
array(6) {
["id"]=>
string(1) "2"
[0]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
[1]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
[2]=>
string(2) "39"
}
array(6) {
["id"]=>
string(1) "3"
[0]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
[1]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
[2]=>
string(2) "33"
}
array(6) {
["id"]=>
string(1) "4"
[0]=>
string(1) "4"
["name"]=>
string(6) "miyama"
[1]=>
string(6) "miyama"
["age"]=>
string(2) "23"
[2]=>
string(2) "23"
}
array(6) {
["id"]=>
string(1) "5"
[0]=>
string(1) "5"
["name"]=>
string(4) "oota"
[1]=>
string(4) "oota"
["age"]=>
string(2) "39"
[2]=>
string(2) "39"
}
また必然的にデータが倍になるので、大きい結果だとメモリを圧迫するかもしれません。
なんにも考えなくても配列として回せるというのは便利だとは思います。
ネット上ではこのデフォルトは嫌われているみたいです。
FETCH_ASSOC 連想配列のみ
こちらは連想配列のみです。場合によってはデフォルトよりこっちの方が使いやすいかもしれません。
echo "\n-連想配列--------------------------------\n\n";
//連想配列
//コラム名がプロパティ名になる
foreach ($pdo->query($sql, PDO::FETCH_ASSOC) as $value) {
var_dump($value);
}
デフォルトよりデータは2分の1になるのでメモリの圧迫も軽減できるかもしれません。
-連想配列--------------------------------
array(3) {
["id"]=>
string(1) "1"
["name"]=>
string(6) "saitou"
["age"]=>
string(2) "23"
}
array(3) {
["id"]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
}
array(3) {
["id"]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
}
array(3) {
["id"]=>
string(1) "4"
["name"]=>
string(6) "miyama"
["age"]=>
string(2) "23"
}
array(3) {
["id"]=>
string(1) "5"
["name"]=>
string(4) "oota"
["age"]=>
string(2) "39"
}
FETCH_NUM 添字配列のみ
こちらは添字配列のみの取得になります。PHPは言語仕様上、添字配列と連想配列は同じものなので、扱いは連想配列同じにできます。
echo "\n-添字配列--------------------------------\n\n";
//添字配列
//0から始まる番号がプロパティ名となる
foreach ($pdo->query($sql, PDO::FETCH_NUM) as $value) {
var_dump($value);
}
カウントで何らかの処理を回したい場合は添字配列の方が便利な場合もあります。
-添字配列--------------------------------
array(3) {
[0]=>
string(1) "1"
[1]=>
string(6) "saitou"
[2]=>
string(2) "23"
}
array(3) {
[0]=>
string(1) "2"
[1]=>
string(8) "sasayama"
[2]=>
string(2) "39"
}
array(3) {
[0]=>
string(1) "3"
[1]=>
string(7) "ikariya"
[2]=>
string(2) "33"
}
array(3) {
[0]=>
string(1) "4"
[1]=>
string(6) "miyama"
[2]=>
string(2) "23"
}
array(3) {
[0]=>
string(1) "5"
[1]=>
string(4) "oota"
[2]=>
string(2) "39"
}
PHPの添字配列は処理の過程において歯抜けになる可能性があるので、カウントで回す時は十分気を付ける必要があります。
FETCH_COLUMN 指定したコラム番号に対応した値を取得
指定したコラム番号の値のみを取得します。値を一部だけ取得したい場合や、なんらかのデータ構造に組み替える場合には便利かもしれません。
echo "\n-コラム番号指定--------------------------------\n\n";
//コラム番号指定
//指定した番号に対応するコラムの値が取得される
foreach ($pdo->query($sql, PDO::FETCH_COLUMN, 1) as $value) {
var_dump($value);
}
一番最初のコラムは0
から始まります。
-コラム番号指定--------------------------------
string(6) "saitou"
string(8) "sasayama"
string(7) "ikariya"
string(6) "miyama"
string(4) "oota"
FETCH_CLASS 指定したクラスのオブジェクトとして取得
おそらく一番汎用性が高い取得方法だと思います。データベースの行をクラスとして扱えますし、独自のメソッドを搭載できるのでデータ操作をカプセル化できます。
echo "\n-クラス指定--------------------------------\n\n";
//クラス指定
//指定したクラスにカラム名がプロパティ名として値が取得される
foreach ($pdo->query($sql, PDO::FETCH_CLASS, DBData) as $value) {
var_dump($value);
}
指定するクラスは自作のクラスでもOKですし、PHPの標準クラスであるstdClass
を使ってもOKです。
コラム名がプロパティ名になり、対応した値が入ります。
-クラス指定--------------------------------
object(DBData)#4 (3) {
["id"]=>
string(1) "1"
["name"]=>
string(6) "saitou"
["age"]=>
string(2) "23"
}
object(DBData)#3 (3) {
["id"]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
}
object(DBData)#4 (3) {
["id"]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
}
object(DBData)#3 (3) {
["id"]=>
string(1) "4"
["name"]=>
string(6) "miyama"
["age"]=>
string(2) "23"
}
object(DBData)#4 (3) {
["id"]=>
string(1) "5"
["name"]=>
string(4) "oota"
["age"]=>
string(2) "39"
}
FETCH_COLUMN 既存オブジェクトの更新として取得
こちらは既存のオブジェクトのプロパティを更新する形で結果を取得します。
echo "\n-オブジェクト指定--------------------------------\n\n";
//オブジェクト指定
//指定した既存のオブジェクトのカラム名に対応するプロパティを更新
$dbData = new DBData();//stdClassでもOK
foreach ($pdo->query($sql, PDO::FETCH_INTO, $dbData) as $value) {
var_dump($value);
}
データ更新に近しいので、その特性を活かした処理では重要なモードになると思います。
-オブジェクト指定--------------------------------
object(DBData)#2 (3) {
["id"]=>
string(1) "1"
["name"]=>
string(6) "saitou"
["age"]=>
string(2) "23"
}
object(DBData)#2 (3) {
["id"]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
}
object(DBData)#2 (3) {
["id"]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
}
object(DBData)#2 (3) {
["id"]=>
string(1) "4"
["name"]=>
string(6) "miyama"
["age"]=>
string(2) "23"
}
object(DBData)#2 (3) {
["id"]=>
string(1) "5"
["name"]=>
string(4) "oota"
["age"]=>
string(2) "39"
}
FETCH_OBJ 匿名オブジェクトとして取得
匿名オブジェクトとして結果を取得する方法です。
基本的には「指定したクラスのオブジェクトとして取得」でstdClass
を用いた際となんら違いは無いと思います。
echo "\n-匿名オブジェクト--------------------------------\n\n";
//オブジェクト指定
//カラム名のプロパティが入った匿名オブジェクトとして取得
foreach ($pdo->query($sql, PDO::FETCH_OBJ) as $value) {
var_dump($value);
}
@MINOには違いがよくわかりません。
-匿名オブジェクト--------------------------------
object(stdClass)#4 (3) {
["id"]=>
string(1) "1"
["name"]=>
string(6) "saitou"
["age"]=>
string(2) "23"
}
object(stdClass)#6 (3) {
["id"]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
}
object(stdClass)#4 (3) {
["id"]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
}
object(stdClass)#6 (3) {
["id"]=>
string(1) "4"
["name"]=>
string(6) "miyama"
["age"]=>
string(2) "23"
}
object(stdClass)#4 (3) {
["id"]=>
string(1) "5"
["name"]=>
string(4) "oota"
["age"]=>
string(2) "39"
}
FETCH_LAZY PDORowオブジェクトとして取得
PHPマニュアルには
PDO::FETCH_LAZY: PDO::FETCH_BOTH とPDO::FETCH_OBJの 組合せで、オブジェクト変数名を作成します。
PDOStatement::fetch
http://php.net/manual/ja/pdostatement.fetch.php
と説明がありますが、どういったことを意図しているモードなのか把握できませんでした。
echo "\n-PDORowオブジェクト--------------------------------\n\n";
//PDORowオブジェクト
//PDORowオブジェクトとして取得
foreach ($pdo->query($sql, PDO::FETCH_LAZY) as $value) {
var_dump($value);
}
実際に使ってみるとPDORow
というクラスでの取得になり、SQLが格納されている点が他のオブジェクトとは違う点です。
-PDORowオブジェクト--------------------------------
object(PDORow)#6 (4) {
["queryString"]=>
string(23) "SELECT * FROM testtable"
["id"]=>
string(1) "1"
["name"]=>
string(6) "saitou"
["age"]=>
string(2) "23"
}
object(PDORow)#6 (4) {
["queryString"]=>
string(23) "SELECT * FROM testtable"
["id"]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
}
object(PDORow)#6 (4) {
["queryString"]=>
string(23) "SELECT * FROM testtable"
["id"]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
}
object(PDORow)#6 (4) {
["queryString"]=>
string(23) "SELECT * FROM testtable"
["id"]=>
string(1) "4"
["name"]=>
string(6) "miyama"
["age"]=>
string(2) "23"
}
object(PDORow)#6 (4) {
["queryString"]=>
string(23) "SELECT * FROM testtable"
["id"]=>
string(1) "5"
["name"]=>
string(4) "oota"
["age"]=>
string(2) "39"
}
ネットをいろいろ検索してみましたが、クリティカルな情報を得ることができませんでした。
いくつかのページではセキュリティ情報に関係がありそうな点も散見しましたが、よくわかりません。
使う場面があるんだろうか?
補足・ハマったポイント
オブジェクトで結果を受け取る際のプロパティのアクセス権限
今回例に示したクラスは以下の様にプロパティを何も定義していません。
//結果取得用クラス
class DBData {
public function __get($name)
{
return $this->$name;
}
}
このクラスのオブジェクトを使ってFETCH_INTO等で結果を取得する場合、コラム名に対応するプロパティがなければ、コラム名のプロパティ名となったプロパティが自動的に作られます。
しかし事前にコラム名と同じプロパティを作っておけばそこに値が入るようになります。
//結果取得用クラス
class DBData {
public $id;
public $name;
public $age;
public function __get($name)
{
return $this->$name;
}
}
この場合、プロパティのアクセス権をpublic以外にしていた場合、FETCH_INTO等のオブジェクトの更新による取得はエラーがでて止まります。
$idをprivateにした場合
Fatal error: Uncaught Error: Cannot access private property DBData::$id in /var/www/html/pdo.php:27
$idをprotectedにした場合
Fatal error: Uncaught Error: Cannot access protected property DBData::$id in /var/www/html/pdo.php:27
プロパティが存在しない場合は基底クラスのマジックメソッドの__set
でプロパティを作っているのではないかと思います。
もしコラム名と対応するプロパティがある場合は__set
は呼ばれず直接アクセスしようとしているのだと思います。
プロパティがpublic
でしか扱えないのはやや不安ですので、できるだけprivate
にしたいですよね。
そこで__set
のオーバライドです。
以下の様に__set
メソッドをオーバーライドして値に何らかの操作を加えるようにしてみました。
class DBData {
public function __set($name, $value)
{
$this->name = $value."<-set?";
return;
}
public function __get($name)
{
return $this->$name;
}
}
echo "\n-オブジェクト指定--------------------------------\n\n";
//オブジェクト指定
//指定した既存のオブジェクトのカラム名に対応するプロパティを更新
$dbData = new DBData();//stdClassでもOK
foreach ($pdo->query($sql, PDO::FETCH_INTO, $dbData) as $value) {
var_dump($value);
}
そうすると以下のように反映されます。
-オブジェクト指定--------------------------------
object(DBData)#3 (3) {
["id"]=>
string(6) "1<-set"
["name"]=>
string(11) "saitou<-set"
["age"]=>
string(7) "23<-set"
}
object(DBData)#3 (3) {
["id"]=>
string(1) "2"
["name"]=>
string(8) "sasayama"
["age"]=>
string(2) "39"
}
object(DBData)#3 (3) {
["id"]=>
string(1) "3"
["name"]=>
string(7) "ikariya"
["age"]=>
string(2) "33"
}
object(DBData)#3 (3) {
["id"]=>
string(1) "4"
["name"]=>
string(6) "miyama"
["age"]=>
string(2) "23"
}
object(DBData)#3 (3) {
["id"]=>
string(1) "5"
["name"]=>
string(4) "oota"
["age"]=>
string(2) "39"
コンテキスト上のクラスの__set
が使われているのであれば、プロパティをprivate
にしても値を入れることが可能なはずです。
//結果取得用クラス
class DBData {
private $id;
private $name;
private $age;
public function __set($name, $value)
{
$this->$name = $value;
return;
}
public function __get($name)
{
return $this->$name;
}
}
echo "\n-オブジェクト指定--------------------------------\n\n";
//オブジェクト指定
//指定した既存のオブジェクトのカラム名に対応するプロパティを更新
$dbData = new DBData();//stdClassでもOK
foreach ($pdo->query($sql, PDO::FETCH_INTO, $dbData) as $value) {
var_dump($value);
}
以下のように出力されました。
object(DBData)#3 (3) {
["id":"DBData":private]=>
string(1) "1"
["name":"DBData":private]=>
string(6) "saitou"
["age":"DBData":private]=>
string(2) "23"
}
object(DBData)#3 (3) {
["id":"DBData":private]=>
string(1) "2"
["name":"DBData":private]=>
string(8) "sasayama"
["age":"DBData":private]=>
string(2) "39"
}
object(DBData)#3 (3) {
["id":"DBData":private]=>
string(1) "3"
["name":"DBData":private]=>
string(7) "ikariya"
["age":"DBData":private]=>
string(2) "33"
}
object(DBData)#3 (3) {
["id":"DBData":private]=>
string(1) "4"
["name":"DBData":private]=>
string(6) "miyama"
["age":"DBData":private]=>
string(2) "23"
}
object(DBData)#3 (3) {
["id":"DBData":private]=>
string(1) "5"
["name":"DBData":private]=>
string(4) "oota"
["age":"DBData":private]=>
string(2) "39"
}
エラーが出ずにちゃんとprivate
で値が入っています。これでカプセル化は一応できそうです。
初期化処理
以上の事を踏まえると、__set
を工夫することで何らかの初期化的処理が値に対して行えることになります。
値のバリデーションなどを行うには良いのかもしれませんが、__set
の本来の役割も潰れる結果になってしまうかもしれません。
この辺のことはもっと勉強しないといけなさそうです…。
まとめ
いろいろな場合があるとは思いますが、おすすめはやっぱりFETCH_CLASSですね。一番拡張性が高い気がします。
行をクラスで扱えるのはやっぱり便利ですよ。
- PDOのqueryメソッドでは結果の取得モードを設定できる
- 連想配列、添字配列、クラス、オブジェクト、コラム番号など多彩にモードを変更できる
- プロパティのセットはマジックメソッドの
__set
が使われている模様 __set
メソッドをオーバーライドすることでプロパティをprivateに保つことも可能__set
メソッドをオーバーライドすることで値になんらかの初期化的処理を行うことも可能