意外と知られていない? queryメソッドで値をbindできるってこと

CakePHP 1.2.2を利用してます。この話は1.2だったらどれでも適用できると思います(cake1.1は分かりません)

ーーーーーーーーーーーーーーーーー
CakePHP2であれば、Model::query()が、擬似bindではなくPrepared StatementでSQL発行するので安心です
http://d.hatena.ne.jp/cakephper/20120204/1328324327
ーーーーーーーーーーーーーーーーー

集計用SQLなど、SQLが複雑になったりする場合や、SQL文を直接書いたほうが開発効率が上がる個所に関しては、Model::query()を利用して、下記のように直接SQL文を発行してます。

$this->Model->query("SELECT `Post`.`id` FROM `posts` AS `Post` WHERE `Post`.`id` = 100");


実はqueryメソッドは、第2引数で配列を与えると、値をbindしてくれます。(SQL文の中でbindさせたい個所は?にしておきます)

$sql = "SELECT `Post`.`id` FROM `posts` AS `Post` WHERE `Post`.`id` = ? LIMIT ?";
$this->Model->query($sql, array(100,1));

こうすると、下記のSQL文が発行されます

SELECT `Post`.`id` FROM `posts` AS `Post` WHERE `Post`.`id` = 100 LIMIT 1


変数をダイレクトにSQL文に埋め込む場合、エスケープ処理などをし忘れると大変ですが、このbindの機能を使っておけば、引数で与えた配列の値は自動でエスケープ処理してくれるので、安心です。


bindしてくれる話って、CakeBookにも書いてないしAPIにも書いてないんだけどw



追記2
ここに、queryメソッドを使うとデフォルトではキャッシュされた結果が利用されるとありました。バインドする場合で、キャッシュを効かせないようにするためには、第3引数にfalseを指定すればOK(1.2.2のソースを読む限りはそれでいける)。

$sql = "SELECT `Post`.`id` FROM `posts` AS `Post` WHERE `Post`.`id` = ? LIMIT ?";
$this->Model->query($sql, array(100,1), false);

追記
miauさんから下記の意見を頂きました。ありがとうございます。

「bind できる」っていうと DB のバインド機構が使われると思われてしまうような・・・。
ソースちゃんと読んでませんけど、
CakePHPプレースホルダを自前で置換してる擬似バインド機構ですよね?
http://twitter.com/miau_jp/status/1539713129

結論は、Cakeが自前でやってる擬似バインドです。



ちょっとソースを追ってみました。
まず、Model::query()を呼び出すと、DboSource::query()に渡されます。ここで第1引数しかない場合(SQL文のみ)は、DboSource::fetchAllメソッドが呼ばれてSQLが実行されます。


今回のように、第2引数が配列の場合は、その配列に対してvalueメソッドが呼ばれて(呼ばれるのは、MySQLであればDboMysql::value()とか)、値のエスケープ処理(mysql_real_escape_string)が実行されます。そのエスケープ処理された配列を、String::insert()に渡して、擬似的にバインドさせて一つのSQL文を作ります。(String::insertなんてあったんだ、知らなかった。)


最後にそれを、DboSource::fetchAll()に渡して、DboSource::execute()が呼ばれて、SQLが実行されます。余談ですが、execute()では、SQLの実行時間とかSQL文を保持させてます。これを使って最初のSQL Explainコンポーネントを作りました。

executeするとDboMysql::_execute()が呼ばれて、その中でmysql_query()が実行されてSQL処理が走ります。