«Can I bind an array to an IN() condition?» — вопрос, который мучает каждого второго PHP-разработчика
Вы когда-нибудь писали запрос вроде SELECT * FROM users WHERE id IN (?) и пытались передать туда массив через bindValue()? Если да — вы точно знаете это чувство разочарования, когда PDO возвращает всего одну строку вместо всех нужных. Или ещё хуже — выбрасывает ошибку.
Этот вопрос на Stack Overflow собрал тысячи просмотров, и неспроста. Казалось бы, чего проще: передал массив, получил результат. Но PDO (PHP Data Objects) — штука строгая. Он не умеет «разворачивать» массивы в список значений для IN() автоматически. И вот тут начинается самое интересное: разработчики придумывают топ ошибок, которые мы сегодня разберём. А главное — научимся их избегать.
В этой статье мы не просто дадим готовое решение. Мы пройдём путь от неправильного кода к безопасному, производительному и красивому. Поехали!
Ошибка №1: Наивная передача массива через bindValue
Самый частый «грабли» — попытка скормить массив прямо в bindValue().
// ❌ НЕПРАВИЛЬНО
$ids = [1, 2, 3, 5];
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (?)");
$stmt->bindValue(1, $ids, PDO::PARAM_STR);
$stmt->execute();
$users = $stmt->fetchAll();
Что произойдёт? PHP преобразует массив в строку "Array", и запрос превратится в WHERE id IN ("Array"). Результат — ноль строк или ошибка, в зависимости от драйвера. PDO просто не понимает, что массив нужно разложить на отдельные плейсхолдеры.
Почему так происходит? PDO ожидает скалярное значение (строку, число, null). Массив — это составной тип, и он не подходит для прямого биндинга.
Как исправить?
Нужно вручную создать плейсхолдеры для каждого элемента массива. Никакой магии, только код.
// ✅ ПРАВИЛЬНО
$ids = [1, 2, 3, 5];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
$users = $stmt->fetchAll();
Здесь мы создаём строку вида ?,?,?,? и передаём массив прямо в execute(). PDO сам подставит значения по порядку. Просто и безопасно.
Ошибка №2: Игнорирование типов данных
Допустим, вы используете именованные плейсхолдеры. И тут возникает соблазн сделать так:
// ❌ НЕПРАВИЛЬНО
$ids = [1, 2, 3];
$placeholders = [];
foreach ($ids as $i => $id) {
$placeholders[] = ":id_$i";
}
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";
$stmt = $pdo->prepare($sql);
foreach ($ids as $i => $id) {
$stmt->bindValue(":id_$i", $id, PDO::PARAM_INT);
}
$stmt->execute();
Вроде бы всё верно. Но есть нюанс: если массив пустой, запрос станет WHERE id IN (), что вызовет синтаксическую ошибку SQL. Вторая проблема — если в массиве есть строки, а вы указали PDO::PARAM_INT, PDO может привести их к нулю.
Решение: всегда проверяйте массив на пустоту и динамически определяйте тип.
// ✅ ПРАВИЛЬНО
$ids = [1, 2, 3];
if (empty($ids)) {
return []; // или выбросить исключение
}
$placeholders = [];
foreach ($ids as $i => $id) {
$placeholders[] = ":id_$i";
}
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";
$stmt = $pdo->prepare($sql);
foreach ($ids as $i => $id) {
$type = is_int($id) ? PDO::PARAM_INT : PDO::PARAM_STR;
$stmt->bindValue(":id_$i", $id, $type);
}
$stmt->execute();
Ошибка №3: SQL-инъекция через «ручное» экранирование
Некоторые разработчики в отчаянии пишут что-то вроде:
// ❌ ОПАСНО
$ids = [1, 2, 3];
$ids_escaped = array_map('intval', $ids); // только для чисел!
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $ids_escaped) . ")";
$stmt = $pdo->query($sql);
Для чисел это ещё прокатывает. Но если в массиве строки (скажем, email'ы), intval превратит их в ноль. А если вы используете addslashes() или mysqli_real_escape_string() — вы всё равно рискуете, особенно с многобайтовыми кодировками.
Золотое правило PDO: никогда не вставляйте данные напрямую в SQL. Только через плейсхолдеры. Даже если кажется, что «я же экранирую».
Ошибка №4: Забыли про производительность
Когда массив большой (сотни или тысячи элементов), создание сотен плейсхолдеров может замедлить запрос. PDO и база данных будут обрабатывать каждый плейсхолдер отдельно, что увеличивает время подготовки.
Альтернатива: используйте временную таблицу или подзапрос. Но это уже для продвинутых сценариев. Для большинства случаев (до 100-200 элементов) метод с плейсхолдерами работает отлично.
Если массив действительно огромный — разбейте его на чанки по 100-200 элементов и выполните несколько запросов, а потом объедините результаты в PHP.
// ✅ ДЛЯ БОЛЬШИХ МАССИВОВ
$allIds = range(1, 5000);
$chunks = array_chunk($allIds, 200);
$results = [];
foreach ($chunks as $chunk) {
$placeholders = implode(',', array_fill(0, count($chunk), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($chunk);
$results = array_merge($results, $stmt->fetchAll());
}
Ошибка №5: Неправильная работа с NULL в массиве
Если в массиве есть NULL, запрос WHERE id IN (1, NULL, 3) не вернёт строки,