綺麗に死ぬITエンジニア

PHPでMySQLiの変数の数を可変(動的)にする方法

2015-11-16

PHPからMySQLを使用する際には、MySQLiの使用が本家により推奨されています。

今回はそのMySQLiを使用したときの、プリペアドステートメントの数が動的に変化する場合のbind_paramメソッドの実装方法について解説します。

プリペアドステートメントの数が固定の場合

まず、通常のステートメントの数が固定の場合は、下記のようなコードにより、SQLを実行します。

<?php
// データベースに接続
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');

// プリペアドステートメント
$stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");

// 変数のバインド
$stmt->bind_param('sssd', $code, $language, $official, $percent);

$code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2;

// プリペアドステートメントの実行
$stmt->execute();

// ステートメントの終了
$stmt->close();

// 接続の終了
$mysqli->close();

SQLインジェクションの防止と、ソースコードの可読性向上の観点から、mysqli_stmt::bind_paramメソッドを使用して、SQLに変数を埋め込むのが一般的です。

しかし、mysqli_stmt::bind_paramメソッドを利用すると、そのままでは引数の数を可変にすることができません。

プリペアドステートメントの数が可変の場合call_user_func_array関数を利用し、実装することで引数の数を可変にすることが可能です。

<?php
$sqlParams = [];
$sqlParams[0] = "";

// WHERE句の組み立て
$sqlWhere = [];
foreach ($whereCond as $value) {  //引数などからWHERE句を組み立てる
    if (//条件) {
        $sqlWhere[] = "name = ?";
        $sqlParams[0] .= "s";
        $sqlParams[] = $value;
    } elseif (//条件) {
        $sqlWhere[] = "status = ?";
        $sqlParams[0] .= "i";
        $sqlParams[] = $value;
    } elseif (//条件) {
        $sqlWhere[] = "modified = ?";
        $sqlParams[0] .= "s";
        $sqlParams[] = $value;
    }
}

// ORDER BY句の組み立て
$sqlOrderBy = [];
foreach ($orderByCond as $value) {
    // 省略。WHERE句と同様に組み立てる
}

// LIMIT句の組み立て
$sqlLimit = [];
foreach ($limitCond as $value) {
    // 省略。WHERE句と同様に組み立てる
}

// SQL文の組み立て
$sql = 'SELECT name, status, modified FROM users';
if (count($sqlWhere) > 0) {
    $sql .= ' WHERE ' . implode(",", $sqlWhere);
}
if (count($sqlOrderBy) > 0) {
    $sql .= ' ORDER BY ' . implode(",", $sqlOrderBy);
}
if (count($sqlLimit) > 0) {
    $sql .= ' LIMIT ' . implode(",", $sqlLimit);
}

// bind_paramに渡す引数を参照渡しに変更
$params = [];
foreach ($sqlParams as $key => $value) {
    $params[$key] = &$sqlParams[$key];
}

// データベースに接続
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');

// プリペアドステートメント
if ($stmt = $mysqli->prepare($sql)) {

    // 変数のバインド
    call_user_func_array(array($stmt, 'bind_param'), $params);

    // プリペアドステートメントの実行
    if ($stmt->execute()) {

        // 結果の取得
        $result = $stmt->get_result();

        // 結果を出力
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
            // 結果を処理
        }

        // 結果セットを開放
        $result->free();
    }
    // ステートメントの終了
    $stmt->close();

// 接続の終了
$mysqli->close();

このようにすることで、可変のステートメントを安全に実装することができます。

なお、SQL文組み立てのところで変数を文字列に組み込んでしまうと、SQLインジェクションの元になってしまうので、そこだけは注意して実装してください。

まとめ

MySQLたのしい。

筆者について

フリーランスエンジニアとして活動している、「もりやませーた」です。

筆者のTwitterはこちら。記事に関するご意見等はTwitterの方へお寄せください。

その他業務に関するお問い合わせは、こちらのページをご覧ください。

PHP SQL