コーヒーに砂糖を1つ入れるぐらいの感覚で 仕様変更 は起こるんだが、
コーヒーでも飲みながら対応しよう。
先人が記事を残してくれている。
「PHPでMySQLiの変数の数を可変(動的)にする方法」ある蜜柑の上にアルミ缶
https://s8a.jp/php-mysqli-bind-param
どのような問題か
「条件を1つ増やすのも 2つ増やすのも 変わらないだろう」というと 変わるし、「SQLはただの文字列だし ちょちょっと足すだけじゃないか」というと クラッカーも「SQLはただの文字列だし ちょちょっと足し」てきて クラッキングしてくるし、
「SQLインジェクション」Wikipedia
https://ja.wikipedia.org/wiki/SQL%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
SQLは ただの文字列ではあるものの バインド という手法を使おう、というのが 手 なわけだ。
おっと、家が揺れている。萎える。
例えば
WHERE name = ? AND age = ? AND weight = ?
みたいな条件文があるとして、2000年代なら 社内のシステムだのクラッキングは無いだの 頭に「dammy = TRUE」でも足して
WHERE dammy = TRUE AND name = ? AND age = ? AND weight = ?
みたいな構文にしておけば あとは「 AND 変数名 = ?」を文字列連結だ ワハハ、みたいな素朴なテクニックで済んだんだが
Web系で仕事を増やそうと思うと ワハハ とも言ってられないので 問題にぶち当たるたびに 手持ちのスキルを アップデートしていこう。
call_user_func_array( ) 関数
「call_user_func_array」php
http://php.net/manual/en/function.call-user-func-array.php
call_user_func_array( ) 関数 自体は、SQLとは関係なく、あなたの関数を呼び出しますよ、引数は配列指定で、といった汎用的なものだが、これが bind_param( ) 関数にも使えるらしい。へー。
bind_param( ) 関数
bind_param( ) 関数というのは ちょっと変わったやつで、
$stmt->bind_param('sii', $name, $age, $weight);
第一引数が 型指定 になっていて、s が文字列、 i が数字になっていて、
第二引数以降が 名前、年齢、体重 なら 第一引数は 'sii' になるわけだ。
慣れてくると もちろん sssssis
とか書き出す。
こんな変わった関数に どうやって call_user_func_array( ) を使うのかと思ったら なんのことはない、
配列 params[0] に .=
演算子を使って ssssisisisis とか文字列連結していき、
params[]=
演算子(要するに Addメソッドだ)を使って 第二引数以降を追加していくだけだ。
でも、WHERE句の方はどうすんだ、と思ってみると
$sql .= ' WHERE ' . implode(",", $sqlWhere);
とかいう変わった書き方をしている。
「implode」sql
http://php.net/manual/en/function.implode.php
WHERE 句のところに 条件式をカンマで並べていいのだろうか?
調べても出てこないので 無難に AND に書き換えておくか……。
使い方は分かったが もっと簡単な方法が欲しいもんだ。
試しに 書いてみる。
参照渡し?
サンプル・プログラムにこう書いてあった。
// bind_paramに渡す引数を参照渡しに変更
$params = [];
foreach ($sqlParams as $key => $value) {
$params[$key] = &$sqlParams[$key];
}
じゃあ、bind_param( ) の引数は 参照渡しだったのか?
とりあえず
rensyu.php 抜粋
// 参考: 「[PHP] mysqli使い方まとめ(MySQL接続~SELECT実行まで)」Qiita http://qiita.com/yasumodev/items/bd2ba476f31804d527d3
// 参考: 「PHPでMySQLiの変数の数を可変(動的)にする方法」ある蜜柑の上にアルミ缶 https://s8a.jp/php-mysqli-bind-param
// ~略~
// http://★サイトアドレス/rensyu.php?name=むずでょ&age=37&weight=80
// c_ ... criteria 検索条件
$c_name = $_GET['name']; // 絞り込み: 名前
$c_age = $_GET['age']; // 絞り込み: 年齢
$c_weight = $_GET['weight']; // 絞り込み: 体重
if(!empty($c_name) || !empty($c_age) || !empty($c_weight)){
$bindParam_args = [];
$bindParam_args[0] = "";
$query = 'SELECT NAME, AGE, WEIGHT FROM RENSYU_TBL WHERE 1=1';
if(!empty($c_name)){
$bindParam_args[0] .= "s";
$bindParam_args[] = $c_name;
$query .= ' AND NAME = ?';
}
if(!empty($c_age)){
$bindParam_args[0] .= "i";
$bindParam_args[] = $c_age;
$query .= ' AND AGE = ?';
}
if(!empty($c_weight)){
$bindParam_args[0] .= "i";
$bindParam_args[] = $c_weight;
$query .= ' AND WEIGHT = ?';
}
$query .= ' ORDER BY AGE, WEIGHT DESC LIMIT 20';
// bind_paramに渡す引数を参照渡しに変更
$bindParam_prms = [];
foreach ($bindParam_args as $key => $value) {
$bindParam_prms[$key] = &$bindParam_args[$key];
}
$stmt = $mysqli->stmt_init();
if($stmt->prepare($query))
{
// 条件値をSQLにバインドする
// bind_param の第1引数 "is" は後続のデータ型を表します。
// i=integer、s=string、d=double、b=blob など。DATE型は s で良いみたいです。
// また下記のように値を引数内に直書きすることはできません。
// 誤)$stmt->bind_param("is", 123, "hanako");
call_user_func_array(array($stmt, 'bind_param'), $bindParam_prms);
// 実行
$stmt->execute();
// 結果はこの変数に入れるようにする
$stmt->bind_result($NAME, $AGE, $WEIGHT);
// TODO: ここで結果出力
// 結果セットを閉じる
$stmt->close();
}
else
{
print "Failed to prepare statement\n";
}
}
else
{
// バインドが要らない方
}
// 以下略
このコードは そのままでは使えないが、要点だけ書きぬくと こうなる。
コードの読みにくさも 抑えた方だろうか。
なんということだろう。 WHERE 1=1
テクニックから離れられない。「これは何ですか?」と言われれば うしろで「 AND 条件文」をつなげるための番兵のようなものなんだが、急に マ (愚痴を言う職業プログラマの略称)っぽくなる。
ページの頭に 検索条件リンクを並べようぜ
こんなんで いいんだろうか?
// http://★サイトアドレス/rensyu.php?name=むずでょ&age=37&weight=80
// c_ ... criteria 検索条件
$c_name = $_GET['name']; // 絞り込み: 名前
$c_age = $_GET['age']; // 絞り込み: 年齢
$c_weight = $_GET['weight']; // 絞り込み: 体重
function createGETString($c_name, $c_age, $c_weight){
$arr = [];
if(!empty($c_name)){
$arr[]='name='.$c_name;
}
if(!empty($c_age)){
$arr[]='age='.$c_age;
}
if(!empty($c_weight)){
$arr[]='weight='.$c_weight;
}
return implode('&', $arr);
}
//----------
// 絞り込み検索 name (名前)
//----------
$query = 'SELECT DISTINCT NAME FROM RENSYU_TBL';
if ($result = $mysqli->query($query)) {
if(0 < $result->num_rows){
// 絞り込み条件なし
echo '<a href="./' . basename($_SERVER['PHP_SELF']) . '?' . createGETString('', $c_age, $c_weight) . '">全て</a> '. "\n";
}
// 連想配列を取得
while ($row = $result->fetch_assoc()) {
// URLの末尾に絞り込み条件を付けます
echo '<a href="./' . basename($_SERVER['PHP_SELF']) . '?' . createGETString($row['NAME'], $c_age, $c_weight) . '">' . $row['NAME'] . '</a> '. "\n";
}
echo '<br />' . "\n";
// 結果セットを閉じる
$result->close();
}
//----------
// 絞り込み検索 age (年齢)
//----------
$query = 'SELECT DISTINCT AGE FROM RENSYU_TBL';
if ($result = $mysqli->query($query)) {
if(0 < $result->num_rows){
// 絞り込み条件なし
echo '<a href="./' . basename($_SERVER['PHP_SELF']) . '?' . createGETString($c_name, '', $c_weight) . '">全て</a> '. "\n";
}
// 連想配列を取得
while ($row = $result->fetch_assoc()) {
// URLの末尾に絞り込み条件を付けます
echo '<a href="./' . basename($_SERVER['PHP_SELF']) . '?' . createGETString($c_name, $row['AGE'], $c_weight) . '">' . $row['AGE'] . '</a> '. "\n";
}
echo '<br />' . "\n";
// 結果セットを閉じる
$result->close();
}
//----------
// 絞り込み検索 weight (体重)
//----------
$query = 'SELECT DISTINCT WEIGHT FROM RENSYU_TBL';
if ($result = $mysqli->query($query)) {
if(0 < $result->num_rows){
// 絞り込み条件なし
echo '<a href="./' . basename($_SERVER['PHP_SELF']) . '?' . createGETString($c_name, $c_age, '') . '">全て</a> '. "\n";
}
// 連想配列を取得
while ($row = $result->fetch_assoc()) {
// URLの末尾に絞り込み条件を付けます
echo '<a href="./' . basename($_SERVER['PHP_SELF']) . '?' . createGETString($c_name, $c_age, $row['WEIGHT']) . '">' . $row['WEIGHT'] . '</a> '. "\n";
}
echo '<br />' . "\n";
// 結果セットを閉じる
$result->close();
}
ざっくりした サンプル・プログラムを置いとくんで、実践で使うには データベースのテーブルの方にキーも設定するとか、何かしら改造がいるだろう。
おわり!
ソート
メールを よく読んでみると ソートに使うキーの順番も書いてある。
そりゃそうか。
WHERE 句に条件を足したなら ORDER BY 句にも足したい。
だったら ORDER BY に並べる順序で if 文を並べるか?
もっと よくよく読んでみると、最後にクリックした方を 優先順位を高くしてほしいらしい。
後入れ先出し法、つまり スタックを使えば それは可能だが、PHP でスタックとかあるのか?
「配列をスタック、キュー、セットとして使う編集する」SeeSaaWiki
http://seesaawiki.jp/izt_php/d/%C7%DB%CE%F3%A4%F2%A5%B9%A5%BF%A5%C3%A5%AF%A1%A2%A5%AD%A5%E5%A1%BC%A1%A2%A5%BB%A5%C3%A5%C8%A4%C8%A4%B7%A4%C6%BB%C8%A4%A6
array_ナントカ関数でいけそうだ。
じゃあ、 ?name=むずでょ&age=37&weight=80
がどういう順序で並んでいるのか、$_GET[ ]配列で調べることができるのだろうか?