Сначала я вообще ничего не понял, но поразмыслив, разобрался, про что этот вопрос.
И поскольку он повторяет весьма распространённые заблуждения, то будет полезно на него подробно ответить.
Можно ли как-то безопасно сделать такой запрос, который я соберу в строку?
Нет, нельзя.
Это распространённое, но очень смешное заблуждение. Если подумать, то само по себе
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);