@rd100

Как сделать prepare запрос без bind_params?

Как мне сделать надежный запрос не используя переменные для вставки?
$request = "SELECT * FROM users WHERE name=Stepan OR name=Stiv"
$sql = $mysqli->prepare($request);
$sql->bind_param();
$sql->execute();


Можно ли как-то безопасно сделать такой запрос, который я соберу в строку?
Не хочется использовать лишние переменные и обработку для типов данных и самих значений в bind_params
Я так понял, что если я выполняю $sql = $mysqli->prepare($request), то выполнится только один запрос, который первый, даже, если в него вложен будет второй, пусть я его строкой и засунул.
  • Вопрос задан
  • 120 просмотров
Решения вопроса 1
ipatiev
@ipatiev
Потомок старинного рода Ипатьевых-Колотитьевых
Сначала я вообще ничего не понял, но поразмыслив, разобрался, про что этот вопрос.
И поскольку он повторяет весьма распространённые заблуждения, то будет полезно на него подробно ответить.

Можно ли как-то безопасно сделать такой запрос, который я соберу в строку?

Нет, нельзя.

Это распространённое, но очень смешное заблуждение. Если подумать, то само по себе prepare() ни от чего не защищает. Это не волшебное слово типа "Экспекто патронум!" - написал, и дальше уже можно не париться, оно само как-то магически защитит от инъекций. В реальной жизни таких слов ещё, увы, не придумали. А защищает именно замена актуальных переменных в запросе на знаки подстановки и последующая привязка через bind_param. А prepare просто говорит базе данных ,что запрос еще рано исполнять, это только схема запроса, а данные для него приедут позже.

Я так понял, что если я выполняю $sql = $mysqli->prepare($request), то выполнится только один запрос, который первый, даже, если в него вложен будет второй, пусть я его строкой и засунул.

Это тоже очень распространённое, и очень вредное заблуждение. Запрос выполнится только один, да. Тут всё верно. Но проблема в том, что SQL инъекция - это не один запрос
INSERT INTO students (name) VALUES ('Robert');DROP TABLE students;
, который рисуют в примерах для первоклассников. Инъекция - это любой посторонний код в запросе, вообще любой.

Поэтому собирать в строку надо всё равно запрос со знаками подстановки, а потом его выполнить. Благо, в 5.6 версии пхп появился оператор распаковки параметров, который значительно облегчил эту задачу.

Не хочется использовать лишние переменные и обработку для типов данных

Без переменных тут не обойтись, но вот с типами данных можно не запариваться. Исторически mysql всегда спокойно принимает строки для любых типов данных. Исключений настолько мало, что ими можно пренебречь.

Для запроса, который приведен в примере, код будет относительно простым, поскольку заменяется на вызов IN:

$names = ["Stepan" , "Stiv"];
$in    = str_repeat('?,', count($names) - 1) . '?'; // получаем строку вида "?,?"
$sql   = "SELECT * FROM users WHERE name IN ($in)"; // поставляем её в SQL
$stmt  = $mysqli->prepare($sql); // prepare
$types = str_repeat('s', count($names)); // получаем строку "sss" по количеству переменных
$stmt->bind_param($types, ...$names); // привязываем наш массив
$stmt->execute(); // выполняем
$result = $stmt->get_result(); // дальше всё как обычно
$data = $result->fetch_all(MYSQLI_ASSOC);


Для более сложных запросов принцип будет тот же: собираем строку с плейсхолдерами, а переменные для неё - в массив. И потом выполняем так же через bind_param($types, ...$parameters);, как-то так:

$conditions = [];
$parameters = [];
if (!empty($_GET['name']))
{
    $conditions[] = 'name LIKE ?';
    $parameters[] = '%'.$_GET['name']."%";
}
// любое количество таких условий
if ($conditions)
{
    $sql .= " WHERE ".implode(" AND ", $conditions);
}

и дальше выполняем обычным порядком.

Ну и в завершение, ответим на вопрос из заголовка буквально
Как сделать prepare запрос без bind_params?

Как правильно заметил TheAndrey7 в комментариях, начиная с версии 8.1 можно будет отправить переменные сразу в execute():

$names = ["Stepan" , "Stiv"];
$in    = str_repeat('?,', count($names) - 1) . '?';
$sql   = "SELECT * FROM users WHERE name IN ($in)";
$stmt  = $mysqli->prepare($sql); 
$stmt->execute($names); 
$data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы