Я искал способ динамического изменения количества переменных, используемых в вызове MYSQLi. Я наткнулся на один очень полезный пост на PHP.net от 5 лет назад (http://php.net/manual/en/mysqli-stmt.bind-param.php#100879). Однако затем я немного сошел с ума и хотел бы помочь, зная, что моя адаптация его работы все еще безопасна / эффективна / полна ошибок, я не достаточно умен, чтобы это увидеть.
Идея состоит из пяти частей:
Все это достигается, я надеюсь, примерно так:
doMYSQL (‘INSERT INTO table (id, name) VALUES ($ id, $ name)’);
Обратите внимание, что в приведенной ниже функции запрос (со встроенными переменными, как старый MYSQL) заключен в одинарные кавычки — переменные анализируются как фактические имена переменных, а не их значения. Значения имеют место только один раз на этапе подготовки операторов MYSQLi (и поэтому, насколько я могу судить, должна быть одинаковая защита от атак с использованием запретов).
А теперь официальные заметки. Я хотел бы получить любые отзывы о том, как сделать это лучше или если есть где-то явная ошибка. Весь код ниже последней заметки («Разный код») был взят из поста PHP.net, большую часть которого я не понимаю, поэтому любые комментарии по этому поводу также будут полезны. Если эта функция пройдет через планку, это, безусловно, облегчит мою жизнь, так что, надеюсь, другие люди тоже найдут для нее применение :).
Просто чтобы прояснить, это сработало на всех тестах, которые я пытался выполнить, поэтому у меня нет оснований думать, что что-то не так. Я просто достаточно опытен, чтобы знать, что я недостаточно опытен, чтобы знать, есть ли какие-либо красные флаги. Поэтому я даю всем шляпу и прошу помощи в проверке безопасности функции.
Спасибо!
<?php
/*
doMYSQL($sql, $debug_local [optional]);
$sql = Statement to execute;
$debug_local = 'print' to show query on page but not run, 'both' to show it and run, leave blank for normal execution.
(You can add a $debug variable at the top of the page to control all doMYSQL functions at once, though local ones take precedence.
*/
function doMYSQL($sql, $debug_local = 'none')
{
$mysqli = new mysqli("localhost", "username", "password", "database");
$print = $sql; // Save unaltered copy in case 'print' is enabled later
// Get debug settings (priority is user-set $debug_local, then global $debug, then default to 'none')
global $debug;
if (($debug == 'print' OR $debug == 'both') AND $debug_local == 'none'){$debug_local = $debug;}
// Create list of variables in the query
preg_match_all('/\$\w+/',$sql,$matches);
// For each variable found, find its value and add its kind and value to $params
$params = array();
foreach ($matches[0] AS $match)
{
$match = substr($match,1); // Get rid of the now-unneccessary '$'' on the variable name
global $$match; // Get the global value for that variable
$kind = gettype($$match); // Get the kind for that variable
// Convert PHP kind to mysqli kind for bind_result
if ($kind == "integer"){$kind = 'i';}
if ($kind == "double"){$kind = 'd';}
if ($kind == "string"){$kind = 's';}
$params[0] .= $kind; // Adds to ongoing list of types in $param[0]
$params[] = $$match; // Adds to ongoing list of values in $params[1+]
$sql = str_replace("$"."$match", '?', $sql); // Switch variable with '?' in the query
$print = str_replace("$"."$match", $$match."[$kind]", $print); // Switch variable with '?' in the query
}
// If debug is print or both, print
if ($debug_local == "print" OR $debug_local == "both")
{
echo "MYSQLi Debug: $print<br>";
}
// If debug is not 'print', run it
if ($debug_local != 'print')
{
// Get first word; if a select/explain, set $close to false; otherwise set to 'true.' If irregular query, error message.
$temp = explode(' ',trim($sql),2);
$firstword = strtolower($temp[0]);
if ($firstword == 'select' OR $firstword == 'explain'){$close=false;}
else if ($firstword == 'update' OR $firstword == 'delete' OR $firstword == 'insert'){$close=true;}
else {echo "Invalid first word on query $query!<br>";}
// Start misc code found on the PHP link
$stmt = $mysqli->prepare($sql) or die ("Failed to prepared the statement!");
call_user_func_array(array($stmt, 'bind_param'), refValues($params));
$stmt->execute();
if($close){
$result = $mysqli->affected_rows;
} else {
$meta = $stmt->result_metadata();
while ( $field = $meta->fetch_field() ) {
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), refValues($parameters));
while ( $stmt->fetch() ) {
$x = array();
foreach( $row as $key => $val ) {
$x[$key] = $val;
}
$results[] = $x;
}
$result = $results;
}
$stmt->close();
$mysqli->close();
return $result;
}
}
function refValues($arr)
{
if (strnatcmp(phpversion(),'5.3') >= 0) //Reference is required for PHP 5.3+
{
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
Examples (generic):
doMYSQL('SELECT * FROM table WHERE id = $id');
doMYSQL('SELECT * FROM table');
doMYSQL('INSERT INTO table(id, name) VALUES ($id,$name)');
Examples (with data):
$user = 1;
$location = 'California';
$result = doMYSQL('SELECT * FROM watchlists_locations WHERE user = $user AND location = $location');
print_r($result);
doMYSQL('INSERT INTO watchlists_locations(user, location) VALUES ($user,"1000")');
?>
Хе-хе, я понимаю, что вы собираетесь, но это не должно быть так сложно 🙂
Если вы хотите использовать mysqli, я бы просто использовал двойные кавычки и отправлял ваш SQL через «SELECT * FROM table WHERE id = $ id». Все, что приходит от пользовательского ввода, вы сначала запускаете через mysqli_real_escape_string ().
Что касается возврата соответствующего ответа в зависимости от типа запроса, вот урезанная версия функции, которую я использую.
function query($sql) {
$arr = explode(' ',trim($sql));
$command = strtolower($arr[0]);
switch ($command) {
case 'call':
case 'select':
// run query and return results
break;
case 'insert':
case 'replace':
// run query, then return insert_id
break;
case 'update':
case 'delete':
// run query and return resulting integer (rows affected)
break;
}
}
Хотя, если вы хотите безопасно и быстро связать переменные, я бы отказался от «mysqli» и использовал подход PDO.
$result = query("SELECT * FROM table WHERE id = :id", [':id' => $id]);
function query($sql, $params) {
$db = new PDO('mysql:database=yourdb;host=127.0.0.1', 'user', 'password');
$stmt = $db->prepare($sql);
$arr = explode(' ',trim($sql));
$command = strtolower($arr[0]);
switch ($command) {
case 'call':
case 'select':
// run query and return results
$stmt->execute($params);
return $stmt->fetchAll();
break;
case 'insert':
case 'replace':
// run query, then return insert_id
return $stmt->execute($params);
break;
case 'update':
case 'delete':
// run query and return resulting integer (rows affected)
return $stmt->execute($params);
break;
}
}
Других решений пока нет …