由於 MySQL 的 IN 條件無法直接傳入陣列,在 PHP 中動態產生 SQL 的 IN 子句是個常見需求,但如果不注意 SQL Injection,可能帶來嚴重的安全風險。
問題背景
PDO 的 prepare 語句支援 ? 或 :name 佔位符(placeholder),但這些佔位符代表「單一值」,無法直接綁定一個陣列到 IN (?) 中:
1
2
3
4
|
// ❌ 這樣不能正確運作
$ids = [1, 2, 3];
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (?)");
$stmt->execute([$ids]); // 只會傳入第一個值或報錯
|
解法一:字串拼接(注意 SQL Injection)
早期常見做法,用 implode 拼接成字串後插入 SQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
$array_uid = [
['uid' => 1],
['uid' => 2],
['uid' => 3],
];
$list = '';
if (is_array($array_uid)) {
foreach ($array_uid as $user_val) {
$list .= "'" . $user_val['uid'] . "',";
}
$list = substr($list, 0, -1); // 移除最後一個逗號
$filter = " AND u.uid NOT IN ($list) ";
}
$sql = "SELECT * FROM xxx WHERE 1=1 $filter";
|
安全警告:若 uid 來自使用者輸入,必須先做 intval() 或 htmlspecialchars() 等過濾,否則有 SQL Injection 風險。
解法二:動態生成 Placeholder(推薦,安全)
根據陣列長度動態產生對應數量的 ?,再用 execute 傳入:
1
2
3
4
5
6
7
8
9
10
|
<?php
$ids = [1, 2, 3, 4];
// 產生 "?,?,?,?"
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$sql = "SELECT * FROM users WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($ids);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
這個方式完全透過 PDO 的 Prepared Statement 處理,資料不會直接插入 SQL 字串,不存在 SQL Injection 問題。
解法三:具名 Placeholder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
$ids = [1, 2, 3];
$params = [];
$placeholders = [];
foreach ($ids as $i => $id) {
$key = ':id' . $i;
$placeholders[] = $key;
$params[$key] = $id;
}
$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
|
解法四:使用 ORM(Eloquent、Doctrine)
現代框架的 ORM 已內建處理 IN 條件,不需要手動生成 SQL:
1
2
3
4
5
|
// Laravel Eloquent
$users = User::whereIn('id', [1, 2, 3])->get();
// 對應到 NOT IN
$users = User::whereNotIn('id', [1, 2, 3])->get();
|
1
2
3
4
5
6
7
8
|
// Doctrine QueryBuilder
$users = $em->createQueryBuilder()
->select('u')
->from('User', 'u')
->where('u.id IN (:ids)')
->setParameter('ids', [1, 2, 3])
->getQuery()
->getResult();
|
解法比較
| 方式 |
安全性 |
複雜度 |
適用場景 |
| 字串拼接(無過濾) |
❌ |
低 |
不建議 |
| 字串拼接(有過濾) |
⚠️ |
低 |
僅限整數型 ID |
動態 ? Placeholder |
✅ |
低 |
一般情境 |
| ORM whereIn |
✅ |
最低 |
有使用 ORM 的專案 |