dポイントプレゼントキャンペーン実施中!

いつもお世話になっております。
PHP5.2.5

1|$conn = new PDO($dsn,~略~);   //データベース接続
2|$sql = "略";             //SQL文
3|$stmt = $conn->prepare($sql);   // PDOStatementオブジェクトを返す
4|$stmt->bindParam(~略~);     //プレースホルダに変数をバインド
5|$stmt->execute();         //クエリ実行

という、よくある流れにおいて、
4行目のbindParamメソッドは、PDOクラスのメソッドではなく、
PDOStatementクラスのメソッドだと思うのですが、
これを、$stmt-> で呼び出せているのが、どうしてなのかがよく分からずにいます。

3行目で、PDOクラスのprepareメソッドを使うことで、
PDOStatementオブジェクトが返されるため、
その返り値を受け取った「$stmt」によって、
「$stmt->bindParam」できているという感じでしょうか?

★まず、この私の考え方について、合っているか間違っているか、
お聞きしたいと思います。(質問1)

//===================================================

恐らく、上記の考え方は「合っている」のだと思うのですが、
その上で、さらに質問をさせて頂くと、

ではなぜ、PDOStatementクラスを使う時に、
new PDOStatement;
というようにしないのでしょうね?

PDOクラスの1メソッドであるprepareメソッドを使うようにして、
PDOStatementクラスをインスタンス化しているのはなぜなのでしょう?
(prepareメソッドはどういうコードになっているのでしょう?その中で、new PDOStatement という記述があるのでしょうね?)

PDOのこのような仕様の背景には、どういう意図があるのか、
どなたか何か思い当たることなどありましたらご助言下さい。
(質問2)

A 回答 (8件)

■prepareメソッドが自分で作成したprivate領域にある情報等々、PDOのメンバ変数がpublicかprotectedなのかprivateなのかを知るすべ


知るすべは無いです。多分。

どちらにしても、マニュアル上、特に記載が無いので、全部privateかprotectedになっているのではないかと思います。

とりあえず、想像される内容に関しては、エラー処理や細かい設定云々全て考えないこととして、

public function prepare($sql, $options){

return new PDOStatment($this->db, $sql, $option);

}

こんな感じなんだと思います。ここに出てきた「$this->db」に関しては、mysql_connect/sqlite_openなどが返す、リソースと同様なものと思ってください。
このメンバ変数は、PDOがnewされ、DBに接続を行ったときに取得されると想定されます。
そしてこのメンバ変数はprivateかprotectedか、とにかく外部からの参照が出来ないものです。
外部から参照が出来ないわけですが、prepareメソッドは内部で持っているメソッドなので、参照することが出来ます。
ゆえに、prepareメソッドが、PDOStatmentクラスのコンストラクタに引数として与えることが出来ます。

ここら辺は、一度、適当なクラス「A」と「B」でも作って、AクラスのメソッドがBクラスのコンストラクタを返すようなサンプルプログラムでも作ってみて、確認してみてください。


■exec/query/prepareメソッドに関して
どのメソッドも、恐らくPDOStatmentクラスを使ってると思います。
単純にexec/queryは、プリペアドステートメントを利用しないものですので、メソッド内部で、PDOStatmentのコンストラクタを作って、executeメソッドを叩くところまで行って、
execは実行結果影響のあった行数を返し、queryはexecute実行済みのPDOStatmentを返します。

想定される使い方としては、execメソッドの場合は、DELETE文やINSERT文、UPDATE文など、SQL発行後、fetchなどして結果を得る必要が無い文の場合に利用する。
queryメソッドの場合は、SELECT文などで、SQL発行後に、fetchなどして結果を得たい場合に利用する。

そんなところだと思います。

この回答への補足

>知るすべは無いです。多分。

了解しました。


>こんな感じなんだと思います。

大変分かりやすい例で助かります。


>prepareメソッドは内部で持っているメソッドなので、参照することが出来ます。
ゆえに、(以下、略)

こちらも問題なく理解OKです。


>■exec/query/prepareメソッドに関して
>どのメソッドも、恐らくPDOStatmentクラスを使って>ると思います。

execでも、PDOStatmentクラスは使ってそうでしょうか?
これには驚きました。

結局、SQL文の処理を実行するには、
PDOStatmentクラスを呼び出す必要がある、
ということなんでしょうかね?
(この件は、既出かもしれませんが。汗)


>単純にexec/queryは、プリペアドステートメントを利用しないものですので

プリペアドステートメントは利用しないが、
PDOStatmentクラスは利用するわけですね。

一方、prepareの方では、
プリペアドステートメントを利用し、かつ、
PDOStatmentクラスも利用するわけですよね。

言い方を変えて、

exec/queryでは、
どちらのメソッドも内部的にPDOStatmentクラスをnewし、
それと同時に、
プリペアドステートメントの準備に関わる内部的な処理を行う、
ということは、「していない」ということでしょうかね。

で、prepareの方では、
内部的にPDOStatmentクラスをnewし、
それと同時に、プリペアドステートメントの準備に関わる内部的な何らかの処理を行う、
ということも「している」と。

このような感じでしょうか。


>メソッド内部で、PDOStatmentのコンストラクタを作って、executeメソッドを叩くところまで行って、

(1)PDO::exec
(2)PDO::query

これら2つは、
「メソッド内部で、PDOStatmentのコンストラクタを作って、executeメソッドを叩くところまで行く」
ここまでの処理内容はほとんど同じ、ということでしょうか。

execとqueryについて。
クエリ実行後に、その後の処理のために、
クエリ結果を受け取る必要があるかないかで、
execとqueryは使い分けられる、ということですよね。
同じような例に、
sqlite_exec()とsqlite_query()がありますね。
ちょっと話がそれましたが、次へ。


(3)PDOStatment::execute

こちらについてですが、
PDOStatment::query
のようなものはないのでしょうか。
SELECT文を処理する際(結果セットを受け取るような処理をする際)には、プリペアドステートメントは使わないから必要ない、という感じなのでしょうかね。
変なことを言ってそうですが、宜しくお願い致します。
今回、かなりスッキリしたように思います!

補足日時:2009/10/11 01:29
    • good
    • 0
この回答へのお礼

(修正・補足)

上記補足で書いた文章について修正をさせて頂きます。

>PDOStatment::queryのようなものはないのでしょうか。

先ほど、「PDOStatment::execute」がクエリの結果セットを受け取れることを知りました。
というわけで、「PDOStatment::queryのようなもの」が必要ないことも同時に理解できました。

お礼日時:2009/10/11 10:06

>これら2つは、


>「メソッド内部で、PDOStatmentのコンストラクタを作って、executeメソッドを叩くところまで行く」
>ここまでの処理内容はほとんど同じ、ということでしょうか。
そうですね。ただ実際にソースを確認したわけではないので、「恐らくそうでしょう」というところになります。
ただ、先の回答で伝えたとおり、PDOStatmentクラスがSQLの実行及び操作を担当していることから、exec/queryでも同様にPDOStatmentクラスを呼び出して実行するであろうな、と想像は付きます。

>execとqueryについて。
>クエリ実行後に、その後の処理のために、
>クエリ結果を受け取る必要があるかないかで、
>execとqueryは使い分けられる、ということですよね。
そういうことですね。
    • good
    • 0
この回答へのお礼

今回も知りたいことについて分かりやすく回答されており、助かりました。

>ただ実際にソースを確認したわけではないので、「恐らくそうでしょう」というところになります。

そうですね。
そのような仕様なのだろう、ということで理解しておきます。

今回、この質問を通し、質問内容に関連する事柄まで一緒に理解することができ、とても助かりました。
こういった拡張モジュールを使う際に、

「利用者はどこまでその仕組みを把握することができるのか、また、どこまで把握しておけば事足りるのか」

この点について、感覚的に掴めたように思います。
自分で作っていないモジュールを利用するのはこれが初めてでしたので、
この要領で今後少しずつ、様々なモジュールに手を出して行ければなと思います。
また何か分からないことがありましたら、色々と教えて下さい。
今回もどうもありがとうございました。
よくわかりました^^

お礼日時:2009/10/11 19:19

>質問1と質問2なので、そちらに主眼を置いて頂けると嬉しいです。


了解しました。

質問1に関しては最初のnaktakさんの回答で終了してますね。

質問2については、なぜ、「new PDOStatment」としないのか、ということでよいのでしょうか?
naktakさんの二番目の回答のように、何らかの情報「$stmt_prepare」を受け取って、
new PDOStatment($stmt_prepare);
とするとして、では「$stmt_prepare」に与えられる「何らかの情報」ってなんでしょうね、と考えたときに、少なくとも
・mysql_connect/sqlite_openなどで渡されるようなDB接続リソース相当のもの
・プリペアドステートメントでつかうSQL文
・prepareメソッドの第二引数に入れたオプション
が$stmt_prepareの中に必要です。
で、これらの情報を外だしすることに意味がなさそうですが、いかがでしょう。むしろ変数として外だししたのであれば、コーディングミスなどでその変数に何らかの値を上書きして、予期せぬエラーを起こしかねません。
それだけでもprepareメソッドが自分で作成したprivate領域にある情報等々をPDOStatmentに勝手に代入して、インスタンス化したものを戻してくれるのであれば願ったりかなったりではないかなぁと思いますがいかがでしょうか。


ちなみに、プリペアドステートメントを利用しなくても良い、ということであれば、
<?php
$conn = new PDO($dsn,~略~); //データベース接続
$sql = "略"; //SQL文
$stmt = $conn->query($sql); // すでにexecuteメソッドが叩かれた状態のPDOStatementオブジェクトを返す。

$data = $stmt->fetch();
?>
こんな書き方も出来ますが、これではだめですか?

prepareはあくまでプリペアドステートメントを使うための準備を行うメソッドです。
それに対して、PDOStatmentは、与えられた命令文に対して操作を行うクラスです。
命令文とは、SQL文と、今までのDB関連関数群には無かった概念であるプリペアドステートメント(コンパイルされたSQL文)の二種類あります。
なので、結果セットを操作するメソッド(fetchなど)と、命令文を実行するメソッドのほかに、プリペアドステートメントに渡す変数をセットするメソッド(bindParamなど)を持つ必要があります。

それぞれの役割分担として、かなり綺麗にクラスが分かれているように思えますが、それでもやっぱりPDOに全て集約したほうが判りやすいと思われますか?

この回答への補足

>質問1に関しては

こちらは仰る通り、解決済みでした。


>質問2については、なぜ、「new PDOStatment」としないのか

はい、その方向でよろしくお願い致します。


>new PDOStatment($stmt_prepare);
>これらの情報を外だしすることに意味がなさそうですが、いかがでしょう。

----------------------------------------------------------
PDOクラスで各種の基本情報を生成

その情報を他のクラスへ渡すために外出し($stmt_prepare)

new PDOStatment($stmt_prepare); //情報が渡された
----------------------------------------------------------
こういう話になるのだと思いますが、この場合の、「外出し」に何の意味があるのか。
この答えは、謎です。笑
確かに、無駄であるように思えますね。


で、現行のPDOだと、
----------------------------------------------------------
PDOクラスで各種の基本情報を生成

PDOクラスの1メソッド内で、PDOStatementをインスタンス化し、
さらに、そのインスタンスに対しPDOクラスで生成した基本情報を
渡し、その結果完成したハンドルを返り値として変数に格納。
----------------------------------------------------------
こんな感じでしょうか。


ご指摘の通り、
外出しすると、変数のバッティングなどの危険性が増しますね。


>prepareメソッドが自分で作成したprivate領域にある情報等々

この辺りは結構、モヤモヤしています。
基本情報(プロパティ)それぞれの「アクセス権限」は、privateなのでしょうかね?
public、protectとかありますけども、、、。
こういうことを知る術はありますか?(知る必要などまず無いのでしょうけども…。)

クラスライブラリ等は、中身がよく分からなくても使用できる所に便利さがあるようですが、
今の私の場合だと、その恩恵を受けられていない状態ですよね…。苦笑


>$stmt = $conn->query($sql); // すでにexecuteメソッドが叩かれた状態のPDOStatementオブジェクトを返す。

ここ、すごく気になりました!(本件の本質がこの辺りに詰まっていそうな気がします。)


(1)PDOクラスで実行
-----------------------------
$stmt = $conn->exec($sql);
-----------------------------


(2)PDOクラスで作成したPDOStatementクラスで実行
-----------------------------
$stmt = $conn->query($sql);/
-----------------------------


(3)PDOStatementクラスで実行(プリペアドステートメントを使用)
-----------------------------
$stmt = $conn->prepare($sql);
$stmt->execute($sql);
-----------------------------

この(1)~(3)が、ちょっとゴチャゴチャになってます。苦笑
(1)と(2)では、返り値がえらく違う点に驚きました。
ご指摘を受け、今気がつきました。
PHPマニュアルを一度、しっかり見てきた方が良さそうですね。
どういう違いがあり、どういう風に使い分けるのか等、勉強する必要がありそうです。
(PHPマニュアルを読んで、はたして、それは分かるようになるのだろうか…)


>prepareはあくまでプリペアドステートメントを使うための準備を行うメソッドです。
>それに対して、PDOStatmentは、与えられた命令文に対して操作を行うクラスです。

なるほどなるほど。分かりやすいです。


>命令文とは、SQL文と、
>今までのDB関連関数群には無かった概念であるプリペアドステートメント(コンパイルされたSQL文)の二種類

プリペアドステートメントという概念がまだ私はよく分かっていないのかもしれません。
この辺り、もう少し勉強してみます。


>なので、
>結果セットを操作するメソッド(fetchなど)
>命令文を実行するメソッド
>プリペアドステートメントに渡す変数をセットするメソッド(bindParamなど)
>を持つ必要があります。

なるほど。こちらも分かりやすいです。


>かなり綺麗にクラスが分かれているように思えますが

じわじわ伝わって来ました。^^


>それでもやっぱりPDOに全て集約したほうが判りやすいと思われますか?

思わないです。笑
まだ、完璧には理解できていませんが、ひとまず、「思わなくなりました」!

ゴールは近そうです。
今回もありがとうございました。

補足日時:2009/10/10 01:36
    • good
    • 0

ちょっと横からのちゃちゃいれのようになってしまいますが



下記の質問の補足/お礼を読んで尚、
「結局、【B】での返り値は、何なのさ」
と思いました。
返り値が何なのかが確定しないと、次回の回答もしづらいと思います。

>ご提示頂いた、
>$res1 = $conn->prepare($sql1);
>この式で返されるリソースとは一体…。
コレは多分、PDOStatmentクラスが無くても、prepareに渡されたSQLやその関連情報をPDOクラスに格納するといった場合、
$conn->prepare($sql1);
$conn->prepare($sql2);
$conn->prepare($sql3);
などと連続してSQLを叩かれた場合に、PDOクラス内部で、情報として、それらを区別して全部持ってる必要があるので、それらに何らかの番号を振る必要があるわけですよね。
例えば内部的には、
$sql = array(0=>$sql1, 1=>$sql2, 3=>$sql3)
とかといったように。
リソースとはその番号のことをさしているのではないかなと思います。
そうでなければ、
$conn->bind();
と叩いたときに、いったいどのSQLに対してそのメソッドが発行されたのか分からなくなってしまいますよね。

この回答への補足

こちらでもお世話になります。

>ちょっと横からのちゃちゃいれのようになってしまいますが

大丈夫です。
少なくとも、私はそうは感じませんし、
ここで回答されている方々は皆知的な方だと思いますので、
そのように解釈されることはまずないかと思います。
ご安心下さい。

さて、早速、本題に移りますが、

>返り値が何なのかが確定しないと、次回の回答もしづらい

どこから話したら良いやら…という風に思えてなりませんが、
この「返り値」の話は、実のところ、本題からはちょっとズレた脱線した話なんです。
結局の所、知りたいことは質問文に書いた、
質問1と質問2なので、そちらに主眼を置いて頂けると嬉しいです。

この点をお伝えした上で、
例の「返り値」の件について、一応、お返事をさせて頂きますと、

質問・回答・補足の流れの中で、
現行のPDOの仕様に対する疑問として、
PDOStatementクラスなどを作らずに、PDOクラスのみで、
現在のPDOと同等の仕様にすることはできないものか?

ということを考える方向へと、話が進んでいきました。

その上で、
PDOStatementクラス全体と、さらに、
その中にある1メソッドであるprepareメソッドの機能が、
PDOクラス内に格納されているという設計で
PDOの使用法を考えた場合に、

「こんな感じの流れで、できやしませんか~」

という、半ば勢い(感覚)で、書いたコードが
例の【B】の例ですが、

ここで、ツッコミを入れられている「返り値」については、
「正直、その内容が何であるかまでは、考えが及んでいない」
というのが本当の所です。

「返り値」が何であるかも想定せずに、
話を展開させてしまっているので、
かなり乱暴な書き方になってしまっているかもしれませんが、
私が意図している話の方向性は、

「PDOクラスのみで、できやしませんか」

「(回答)これこれ、こういう理由で不便ですよ」

「なるほど、ということは、PDOStatementクラスは必要だ」
「それは分かりましたが、ではなぜ、new PDOStatement;のような使い方をしないのでしょう」

「それはですね…。//★知りたいこと」

ということなので、

今、ちょっとした問題になっているこの「返り値」がどうなっているかの件については、
後付的に考えてもらえれば良いという程度にしか考えておりませんでした。

もっと言えば、
【B】のような展開でなくてもいいわけです。
「返り値問題」が発生して、ややこしくなるようでしたら、
いっそのこと、【B】は忘れて頂き、他の方法でも良いということです。

あと、こんなことを言っては、身もフタもありませんが、
この「返り値」について、

どういった返り値が返っている必要があるのか、
ということすら、まだ今の私にはよく分かっていないということもあります。
(もっとも、こういう恥ずかしい状況であるがゆえの、本質問なのかなぁとも思えますが…。苦笑)

ただ、なんとなくではありますが、
「どのDBに対して処理しようとしているのか(DBリソース)」
などといった情報を、この【B】の「返り値」では含む必要がありそうだなというのは一応は分かっているつもりです。

この辺りのニュアンスをご理解頂けると、
私の迷子状況がある程度伝わるのではないかと思います。


>などと連続してSQLを叩かれた場合に、PDOクラス内部で、情報として、それらを区別して全部持ってる必要がある

えっと、クラスのプロパティに格納していくといった考え方ですよね。


>それらに何らかの番号を振る必要があるわけですよね。
例えば内部的には、
$sql = array(0=>$sql1, 1=>$sql2, 3=>$sql3)
とかといったように。

「連続してSQLを叩かれた場合」の識別子(例えば、番号など)を、
リソースとして返すようにすれば良い、
というような話なのですかね。
なんとなく分かる気がします。

補足日時:2009/10/09 03:00
    • good
    • 0
この回答へのお礼

(補足からの続きです)

>そうでなければ、
$conn->bind();
と叩いたときに、いったいどのSQLに対してそのメソッドが発行されたのか分からなくなってしまいますよね。

PDOを使う上でセッティングしておく一連の情報を、
オブジェクト単位でその情報を保持させるのではなく、
メソッド単位で保持させるとなると、
PDOで各種の処理をする度に、各種メソッドの引数に、
そのセッティングしてある一連の情報を与える必要が生じることになりがちなのかな、と思いました。
(もしかしたら、抜け道があるのかもしれませんが、そこまでクラスの使い方を熟知していない私には全く検討もつきません。)

と、こんなまとまりのないことを書きましたが、
知りたいことは、
やはり、質問文に書いた、質問1と質問2なので、
もしよろしければ、そちらについてもご助言頂けると嬉しいです。
よろしくお願い致します。

お礼日時:2009/10/09 03:04

2つにする理由は、対象の絞り込みと役割の違いというところかな。


>PDOクラスの中に必要なメソッドを与える
すべてを一つのクラスに入れる方法ですと、データベースごとの関数mysql_fetch などのように、どのqueryからのリソースを使うのかを、毎回引数で指定しないとなりません。
クラス化の利点があんまり無くなってしまいます。
別クラスにすることで、queryごとにそれぞれ別のインスタンスに保持して、基本となること(リソースがどれか)は、インスタンスが知っているから、引数にそれを指定しなくても、そこから何を取り出したいのかによって、メソッドを使うだけとなります。
とはいえ、もともと、接続用リソースと、query結果用リソースがあって、それぞれにクラスを作ったともいえますので、開発の基本理由までは解りませんが。

オブジェクト指向の考え方の一つに、クラスの粒度というのがあります。
今回ご指摘のように、PDOクラス一つに全部閉じこめるのか、それとも、query結果だけを操作できるクラスを作成して、query結果にしか作用しないメソッドをそこに集めて、PDOクラスの方は、接続やエラー処理に専念できるようにするのか、といったことですが、おおむね、クラス分化する方がいいようです。

ご呈示のソースA、Bでは表示上同じですが、
$stmt1 = $conn->prepare($sql1);
$stmt1->execute(); //PDOオブジェクト1のexecuteメソッドの返り値
Bの場合、$stmt1 には、何が返っているのでしょう?$connを複製して、prepareした接続情報をもつもの?

$connが異なる状態で複製が存在すると、上記の作業を交互にやった場合
$stmt1 = $conn->prepare($sql1);
$stmt2 = $conn->prepare($sql1);
$stmt1->execute();
$stmt2->execute();

$stmt1->execute 時点で、$stmt1 が持っている接続情報は、最新ではなくなり、発行できないかもしれません。よって、fetchまで全部やってからでないと次のsql文をprepareできない状況が容易に発生します。
それよりは、prepareの返値は成功したか否かとリソース識別情報だけとして、$connがずっとprepare後の基本情報を保持していればいいわけですよね?
→ クラスを一つにするには、以下のようになります。
// 前回のaテーブルデータをbテーブルに挿入の場合
$res1 = $conn->prepare($sql1);// リソース識別子を返す、不成功時はfalse
$res2 = $conn->prepare($sql2);
if( $res1 !== false and $res2 !== false ){
$conn->bind(,, $res1);// リソース識別子ごとにbind
$conn->execute($res1);
while( $row= $conn->fetch($res1) ){
$conn->bind(,, $res2);
$conn->execute($res2);
// $conn->fetch($res2);
}
}

結局、クラスメンテナンス上も、一つにするとバグ探しは大変だし、利用する側からも、クラス分けされていた方が使い勝手がよいと、私は思います。
オブジェクト指向の目的の一つにメンテナンスを楽にするというのもあるので。

この回答への補足

またしても、ありがとうございます。
早速ですが、お返事を。(長くてすみません)

基本情報(リソースがどれか)を固定的にセットしておき、
その上に載せるクエリを適宜変更するようにすることで、
土台の使い回しが利くようになるわけですね。
で、さらに、クエリごとに、その実行基盤を用意することになりますが、
それをPDOクラスにではなく、PDOStatementクラスに担当させているのが
現在のPDOの仕様なのでしょうね。


/*---
別クラスにすることで、queryごとにそれぞれ
『別のインスタンスに』保持して、基本となること(リソースがどれか)は、インスタンスが知っている
---*/

PDOクラスに基本情報(リソースがどれか)をセットし、
さらに、このPDOクラスの中にprepareメソッドを新設する考え方だと、
「クエリごとに用意すべき実行基盤」をどうするか、
という問題が生じますね。
この考え方の場合、あくまでPDOクラスのみで完結させようとする考え方ですから、
「クエリごとの実行基盤」は、PDOクラスのインスタンスに担当してもらうことになりますでしょうか。

恐らくこれでも動作はするのでしょうが、
PDOStatementクラスを用意する方法と比べて、
どちらの方がより便利か、という話になるのだと思います。

PDOStatementクラスの機能を全部、
PDOクラスにしょい込ませると、
一体、どうなってしまうのやら…。

/*---
クラスの粒度
おおむね、クラス分化する方がいい
---*/

こちら、頭に叩き込んでおきます。


/*---
とはいえ、もともと、接続用リソースと、query結果用リソースがあって、それぞれにクラスを作ったともいえますので、
---*/

そういった見方もあるわけですね。
大変勉強になります。
様々な理由が絡み合って、今の仕様に落ち着いているということなのでしょうね。


/*---
Bの場合、$stmt1 には、何が返っているのでしょう?
$connを複製して、prepareした接続情報をもつもの?
---*/

過去に、似たような質問を他の回答者の方からも質問されてしまいましたが、
前回の時点では、知識がまだまだ無かったため、
ちゃんと答えられませんでしたが、
今ならもう少しまともに答えられるかなと思います。

$stmt1 = $conn->prepare($sql1);

【B】における上式はつまり、
PDOクラスに新設した(仮想)、prepare()メソッドの引数に、
クエリ$sql1を渡し、その返り値を$stmt1で受けているものですが、
では、prepare()では、どんな処理をし、何を返しているのか、
これはですね…(笑)、
えっと、
【A】におけるPDOStatementクラスのprepare()メソッドが返すものと同じもの、
と答えたい所ですが、
それでは答えになっていないのでしょうか…ね。
答えになっていないからこそのご質問なのだと思いますが、
どうして「答えになっていないのか」について、
これを考えてみる必要がありそうです。

すぐに答えが出ました。笑

「// PDOStatementオブジェクトを返す」

つまり、PDOクラスオンリーの話では、
PDOStatementオブジェクトは存在しませんから、
当然、返り値として返すことができなくなるわけですよね。

あ~なるほど。
「$stmt = $conn->prepare($sql);」という式が、
【A】、【B】それぞれで、どういう意味を持つか考えると、
【A】では、ある情報がセットされたインスタンスであるのに対し、
【B】では、(PDOクラスの1メソッドであるprepareメソッド[新設(仮想)])の「インスタンス型ではない」返り値、
ということになるわけですね。

で、話を戻して、
「結局、【B】での返り値は、何なのさ」
ということになりますね。

と、こういったことを先読みし、
回答頂けているように思いましたので、
そちらへ話を移したいと思います。
(至れり尽くせりな回答をどうもありがとうございます。)

補足日時:2009/10/06 00:42
    • good
    • 0
この回答へのお礼

/*---
$connが異なる状態で複製が存在すると、
上記の作業を交互にやった場合
---*/

$stmt2 = $conn->prepare($sql1);
→$stmt2 = $conn->prepare($sql2);

という解釈(訂正?)でよろしいでしょうか。
(念のため)

「$connが異なる状態で複製が存在すると、」

というところが、私には難解でした…。
分かるような分からないような…という感じです。

/*---
$stmt1->execute 時点で、$stmt1 が持っている接続情報は、最新ではなくなり
---*/

よって、ここも、同様に…。
大事な点であることはよく分かるのですが…。


/*---
prepareの返値は成功したか否かとリソース識別情報だけとして、
$connがずっとprepare後の基本情報を保持していればいいわけですよね?
---*/

PDOクラスおんりーで考える場合には、
そのような考え方になるのですね。
つまり、先ほどの【B】の返り値に通じる説明箇所なのだと思います。


/*---
$res1 = $conn->prepare($sql1);
// リソース識別子を返す、不成功時はfalse
---*/

インスタンスを返すのではなく、
リソース識別子を返す(不成功時はfalse)ようにするわけですね。

結局のところ、
prepareメソッドというのは、その内部では、
一体、何をしているんでしょうね。

ご提示頂いた、
$res1 = $conn->prepare($sql1);

この式で返されるリソースとは一体…。


所々、分からない点もありましたが、
全体の流れは掴めたように思います。
(たぶん。笑)
あと、もうちょっと、、という所まで来ている気がするのですが…。

またよろしければ、添削・ご助言を頂けると嬉しいです。
どうぞ宜しくお願い致します。

お礼日時:2009/10/06 00:46

SQLiteのオブジェクト指向言語型スタイルも同じ作りなので、オブジェクト指向についての理解を深めるのがよいかと思いますが、


私も、実際のphp本体のソースを見たわけではありませんが、いろいろ自分でクラス作成していくとこの動作にするのが結構有用なのが推測できます。

1.PDOStatementを個別newして、PDOオブジェクトを引数で渡すとすると、
PDOStatementオブジェクトから、引数で渡されたPDOオブジェクトのデータを見るには、それらのデータがpublicになっているまたは、publicなgetterメソッドが必要ですが、
PDOオブジェクトのメソッド内であれば、PDOオブジェクトのprivateデータ(データベース接続idや、場合によってはパスワードなども?)を外に開示することなくPDOStatementオブジェクトに引き渡すことが出来ます。
php5でクラスのプロパティやメソッドにpublicとprivateの区別ができるようになったので、こういった作りが可能になったとも云えます。

2.また、PDOクラスとPDOStatementとを分けているのは、PDOオブジェクトは一つのままでも、異なるsql文での結果を、個別に保持できるからです。
ex. 以下ご呈示のソースにちょっと味付け
1|$conn = new PDO($dsn,~略~);  //データベース接続
 |$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // errorの時例外を投げるように設定する
2|$sql1 = "select * from a where 略";  //SQL文
3|$stmt = $conn->prepare($sql1);  // PDOStatementオブジェクトを返す
4|$stmt->bindParam(~略~);    //プレースホルダに変数をバインド
5|$stmt->execute();        //クエリ実行, select文の結果行を保持
6|// database が対応していれば、ここでtransaction 開始
7|try{
8| $sql2 = "insert into b 略";   //SQL文
9| $stmt2 = $conn->prepare($sql2); //$stmtとは別の PDOStatementオブジェクトを返す
10| while ($row = $stmt->fetch(PDO::FETCH_ASSOC) ){ // $sql1の各行処理
11|  $stmt2->bindParam(~略~);  //プレースホルダにsql1から得られたデータをバインド
12|  $stmt2->execute();      //$sql2のクエリ実行
13| }
14|} catch (PDOException $e) {
15|// transaction に対応しているデータベースなら rollback
16|}

先に全部fetchしないと、次のsql文を発行できないデータベースもあるかもしれませんが、MySQLやSQLiteでは、上記の様なことは問題なく出来ています。execute()の時点でPDOStatementオブジェクト内に、データをストックしているような感じです。

この回答への補足

回答をありがとうございます。
以下、返事が長くなりますが、どうか宜しくお願い致します。
(補足→回答 の順にお読み下さい。)

>オブジェクト指向についての理解を深めるのがよいかと

私自身も、その知識(応用力)が足りないために、
今回つまずいているのだろうなと感じています。


>1.
PDOStatementオブジェクトから、引数で渡されたPDOオブジェクトのデータを見るには、それらのデータがpublicになっているまたは、publicなgetterメソッドが必要

確かに、そうなりそうですね。
パスワードなどは言うまでもなく、
基本的にクラスのデータは出来る限りpublicではないアクセス権限で設定する方が良い
ということもあるでしょうから、
あるクラスの引数に別のクラスのオブジェクトを渡す方法だと、
あまりよろしくない、ということになりそうですね。


>PDOオブジェクトのメソッド内であれば、PDOオブジェクトのprivateデータ(データベース接続idや、場合によってはパスワードなども?)を外に開示することなくPDOStatementオブジェクトに引き渡すことが出来ます。

このような貴重なアドバイスを頂くことができ、
大変嬉しく思います。
まさにごもっともなご意見かと思います。


>php5でクラスのプロパティやメソッドにpublicとprivateの区別ができるようになったので、こういった作りが可能になったとも云えます。

なるほど、確かにそういった事情もありそうですね。


>2.また、PDOクラスとPDOStatementとを分けているのは、PDOオブジェクトは一つのままでも、異なるsql文での結果を、個別に保持できるからです。

この点も大事な点であるようにとても感じましたが、
残念なことに、私は少々、理解につまずいています。
そのつまずきを質問という形で、お伝えしたいと思います。
もしよろしければ、またアドバイスを頂けると嬉しいです。

------------------------------------------------
2|$sql1 = "select * from a where 略";  //SQL文(★データ抽出)
~~中略~~
5|$stmt->execute();        //クエリ実行, select文の結果行を保持
------------------------------------------------

■あるDBからデータを抽出し、いつでも取り出せるような状態にしていますね。(コード:2~5行目)


------------------------------------------------
8| $sql2 = "insert into b 略";   //SQL文(★データ挿入)
9| $stmt2 = $conn->prepare($sql2); //$stmtとは別の PDOStatementオブジェクトを返す
------------------------------------------------

■同じDBに対し、データ挿入クエリの準備をしていますね。


------------------------------------------------
10| while ($row = $stmt->fetch(PDO::FETCH_ASSOC) ){ // $sql1の各行処理
------------------------------------------------

■DBから抽出したデータを1つずつ繰り返し、取り出す作業をしていますね。


------------------------------------------------
11|  $stmt2->bindParam(~略~);  //プレースホルダにsql1から得られたデータをバインド
12|  $stmt2->execute();      //$sql2のクエリ実行
------------------------------------------------

■DBから抽出したデータを、$sql2のプレースホルダにバインドさせているようですね。
こういった処理自体、PDOとは関係なく、私はしたことがないので、
ちょっととっつきにくい印象を受けましたが、
そういうことをしようとした場合の例として、
この流れをひとまず理解することとします。

つまり、ここでしようとしていることは、
あるDBからある条件でデータを抽出し、それをもとに、
その同じDBに対し、データのインサート処理を行う、
ということだと思いますが、

この例が示すことは、

PDOクラスとPDOStatementとを分けることで、
PDOオブジェクトは一つのままでも、
異なるsql文での結果を、個別に保持できる

ということなのだと思います。(勘違いでしたらすみません)

そこで、この例示を受け、
私の考えでは、
PDOクラスの中に
「PDOStatementオブジェクトに相当するメソッド」
を与えることでも、

『PDOオブジェクトは一つのままでも、
異なるsql文での結果を、個別に保持させる』

ということは実現できるのではないかと思うのですが、
そういった発想ではダメなのでしょうか?

補足日時:2009/10/04 12:44
    • good
    • 0
この回答へのお礼

(補足の続き)
極論すると、PDOStatementにPDOオブジェクトのデータを渡すこともなければ、
PDOStatementオブジェクトにbindやexcuteさせることもなく、
PDOクラスの中に必要なメソッドを与えることで、
PDOクラスだけで全てを完結させられないのだろうか、と思えてしまうのですが、
やはり、役割分担をさせた方が、何かと効率的なのでしょうかね?

これは、オブジェクト指向がよく理解できていないからこそ浮かぶ疑問なのかもしれませんね。

>先に全部fetchしないと、次のsql文を発行できないデータベースもあるかもしれませんが、MySQLやSQLiteでは、上記の様なことは問題なく出来ています。

こちらの実際的な情報も大変、有益に感じました。
ありがとうございます。


>execute()の時点でPDOStatementオブジェクト内に、データをストックしているような感じです。

そういうことなんでしょうね。
これを、くどくなりますが、前述の通り、メソッド単位で、
データをストックできるようにはできないでしょうか。

つまり、
---【A】----------------------------------------------
$stmt1 = $conn->prepare($sql1);
$stmt1->execute(); //PDOStatementオブジェクト1にストック

$stmt2 = $conn->prepare($sql2);
$stmt2->execute(); //PDOStatementオブジェクト2にストック
-------------------------------------------------
というような考え方を、

PDOクラス内に用意されているprepareメソッドで、
PDOStatementオブジェクトを立ち上げるかのような機能を実現し、
さらに、PDOクラス内にexecuteメソッドを新設し、
それらの連携によって、上記と同様なことはできないだろうか、
というのが以下のコード例です。
(もちろん、仮想の話です。)

---【B】-----------------------------------------------
$stmt1 = $conn->prepare($sql1);
$stmt1->execute(); //PDOオブジェクト1のexecuteメソッドの返り値

$stmt2 = $conn->prepare($sql2);
$stmt2->execute(); //PDOオブジェクト2のexecuteメソッドの返り値
--------------------------------------------------
※全てPDOクラスだけで完結している点が、【A】とは異なります。

【A】【B】どちらのケースも、オブジェクトを複数作る点では同じですが、
使用するクラスの数が2つなのか、1つなのか、で異なります。

お礼日時:2009/10/04 12:46

#1です。


私もPDOはさっぱり利用してきませんでしたし、なぜそういう仕様になったかまでは
説明することは出来ません。
最近はめっきりPHPも触らなくなってきたので・・・。

> 1|//(1):「指定されたクエリをプリペアドステートメントとして準備を行う」
> 2|$stmt_prepare = $conn->prepare($sql);
> 3|
> 4|//(2):(1)を受けて、「PDOStatementオブジェクトを生成し、戻り値として返す」
> 5|$stmt = new PDOStatement($stmt_prepare);

確かにこのような方法でも可能のように思われますが、
この方法では、$stmt_prepareには何が設定されるのでしょう?
この方法では、$stmt_prepareには最低限、どういったデータベースに
接続したのかという情報やらPDOStatementで利用可能な『準備された情報』が
必要になってしまい、新たなオブジェクトを要すと思いますし、
『準備された情報』がせっかく準備されたのに、あとで利用者に
よっていじられる可能性を秘めていると思います。

PDOのやり方ではそれを必要としません。
PDOStatementからクエリの実行などが行えることから、PDOStatement
には少なからずデータベースの接続情報などが存在していると思います。
PDOオブジェクト上で準備を整えるのではなく、準備が整ったPDOStatement
オブジェクトを$stmtが得ている感じです。

たぶん。
何故そういう仕様なのかと聞かれると気にしたことないので分かりませんが、
決まったSQLは決まったデータベースで動作するはずだという考えの
下に出来上がった仕様なのでは?

> PDOStatementクラスというものを利用する場合は、
> 「常に、」PDOクラスのprepareメソッドを通じて利用することになる、
> ということになるのでしょうかね?
事実上そうなります。
そういったクラスはPDOに限らず多々ありますよ。
    • good
    • 0
この回答へのお礼

補足をどうもありがとうございます。

>この方法では、$stmt_prepareには何が設定されるのでしょう?

私はまだPDOの仕組みについて、よく分かっていないのかもしれません。
(特に、PDOクラスとPDOStatementの連携について)

で、何が設定されるか、についてですが、
正直、そのように質問されると分からなくなってしまいますね。^^;
すみません。多分、仕組みを理解していないからこそ書けた内容だったのだと思います。
言ってみれば、イメージ的に書かせて頂いたわけなのですが、
その事に対する鋭いツッコミのおかげで、
私が何を理解していないのかが分かりやすくなったように思います。
助かります。

>この方法では、$stmt_prepareには最低限、どういったデータベースに
接続したのかという情報やらPDOStatementで利用可能な『準備された情報』が必要になってしまい、新たなオブジェクトを要す

これはどういった意味なのでしょう?
すみません、分からないことだらけでして…。
多分、このあたりが分かれば、私の疑問は解決されるように思います。

>PDOオブジェクト上で準備を整えるのではなく、準備が整ったPDOStatementオブジェクトを$stmtが得ている感じです。

分かるような、分からないような…。
PDOクラスのprepareメソッドを使って、PDOStatementオブジェクトの準備を整える点がよく分からないんだと思います。

(質問文から引用)
1|$conn = new PDO($dsn,~略~);   //データベース接続

として、PDOクラス内にデータベース接続の情報を受け取っているのだから、
それを使って、プリペアドステイトメントの準備をするとなると、
その準備を担当するメソッドは、当然、PDOクラス所属のものにした方が妥当だろう、
という考えから、PDOクラスにprepareというメソッドを用意し、
そのメソッドの中で、プリペアドステイトメントの準備をし、
さらに、そこからの振る舞いは、
PDOStatementクラスに委ねる形にするために、
PDOクラスのprepareメソッドにて、 PDOStatementオブジェクトを生成しているのかな、
なんて思うわけですが、
それならば、初めからPDOStatementクラスなど用意せずに、
PDOオブジェクトで全て処理できてしまえるような仕様にしてしまえばいいのに…、
と思えてならないのです。

何か変なことを言っていましたら、是非、ご指導頂けると嬉しいです。

お礼日時:2009/10/03 02:09

> これを、$stmt-> で呼び出せているのが、どうしてなのかがよく分からずにいます。


$conn->prepare($sql)によってPDOStatementクラスでインスタンス化された
オブジェクトが戻り値になっているからです。
つまり、PDO::prepareメソッドでは、内部でPDOStatementオブジェクトを生成しています。
http://jp.php.net/manual/ja/pdo.prepare.php

PDOStatementクラスはプリペアドステートメントの準備が行われてから初めて操作
できるものです。
bindParamメソッドに関して言えば、以下の例にあるように、プリペアドステートメントで
指定したパラメータ名にマッピングして値を設定します。
http://jp.php.net/manual/ja/pdostatement.bindpar …

executeメソッドに関しても、準備の出来てないプリペアドステートメントに対して
実行など出来るはずがありません。
http://jp.php.net/manual/ja/pdostatement.execute …

事実上、そういう事が実行可能なように、指定されたクエリをプリペアドステートメントとして
準備を行うのがprepareメソッドになります。

つまり、PDOStatementクラスは、プリペアドステートメントの準備が
行われていなければ意味のないクラスです。
ですからnew PDOStatementとしても意味がありませんし、誤った利用を
助長してしまいます。
何の脈絡もなく突然外部からインスタンス化されても、プリペアドステー
トメントの準備が行えていませんからPDOStatementクラスとしては一切
動作することが出来ません。
その為、『PDOStatementはPDO::prepare()より取得する』という制限が
存在するわけです。

この回答への補足

私の分かりにくい質問を読み、さらに、回答までして頂き、どうもありがとうございます。

>$conn->prepare($sql)によってPDOStatementクラスでインスタンス化されたオブジェクトが戻り値になっているからです。

やはり、そういう理解で良いのですよね。
ということで、質問1については、
私の考えていたことは「合っている」とします。

>事実上、そういう事が実行可能なように、指定されたクエリをプリペアドステートメントとして準備を行うのがprepareメソッドになります。

prepareメソッドの機能を大きく分けると、
(1)「指定されたクエリをプリペアドステートメントとして準備を行う」
(2)「さらに、(その内部で)PDOStatementオブジェクトを生成し、戻り値として返す」

と、prepareメソッドには、少なくとも上記の2つの機能が存在していそうだなと感じました。
上記2つの機能は、大きく連携はしているものの、機能そのものの意味性や構造においては「関係性がない」と思うので、
それらを1つのメソッド(つまり、PDOクラスのprepareメソッド)の中で処理させるPDO(のシステム)の仕様が、
私をこのような「迷子」にさせてしまっているのかなと思いました。

(1)があって、初めて(2)は意味を成す(つまり連携)、
ということは分かるのですが、
その仕様として、必ずしも同時に行う必要性はあるのだろうか、
と思っているわけです。

つまり、
PDOクラスのprepareメソッドでは、(1)の機能だけを行い、
これとは別の形で、new PDOStatementへ、というようなフローとしても、
連携はできていることになりますから、
このような仕様であっても問題なかったのではと思うのです。

//=============================
//イメージ的には、↓こんな感じ。
//(×)実際には、こんな書き方は無い。
//こういう使用法であれば、分かりやすかったのになぁ、というような意味で書いています。
//もし、変なことを言っていましたら、ご指摘頂けると嬉しいです。
//=============================

1|//(1):「指定されたクエリをプリペアドステートメントとして準備を行う」
2|$stmt_prepare = $conn->prepare($sql);
3|
4|//(2):(1)を受けて、「PDOStatementオブジェクトを生成し、戻り値として返す」
5|$stmt = new PDOStatement($stmt_prepare);
6|
7|$stmt->bindParam(~略~);     //プレースホルダに変数をバインド
8|$stmt->execute();         //クエリ実行

//=============================

で、以下、話を「正しい使用法」の方に戻しますと、

PDOStatementクラスというものを利用する場合は、
「常に、」PDOクラスのprepareメソッドを通じて利用することになる、
ということになるのでしょうかね?

もし、またよろしければ教えて下さい。
宜しくお願い致します。

補足日時:2009/10/02 17:16
    • good
    • 0
この回答へのお礼

この度は回答頂いたにも拘わらず、ポイントをお贈りすることができず、
大変申し訳ございません。
私としてはとても貴重なアドバイスを頂いたと思っておりますので、
そのことだけはお伝えしたいと思い、こちらにてお礼の言葉を書かせて頂くことにしました。
私のために分かりやすく2度も回答頂き、どうもありがとうございました。
また分からないことがありましたら、色々と教えて下さい。

お礼日時:2009/10/14 23:51

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!