
本文详解如何在 php 中通过 pdo 正确使用参数化查询,避免将变量直接拼入 sql 语句(尤其是列名和表名),并提供安全、可复用的动态字段更新方案。
本文详解如何在 php 中通过 pdo 正确使用参数化查询,避免将变量直接拼入 sql 语句(尤其是列名和表名),并提供安全、可复用的动态字段更新方案。
在使用 PDO 执行 SQL 操作时,一个常见误区是试图将列名(如 $res)或表名、条件字段名(如 $fabrik)作为参数占位符(?)传入 prepare() —— 这是不被支持且极度危险的做法。PDO 的参数绑定(? 或 :name)仅适用于数据值(values)**,绝不能用于标识符(identifiers),例如列名、表名、ORDER BY 字段等。否则不仅会触发 SQL 语法错误(如 Unknown column 'wood' in 'field list'),更会打开 SQL 注入漏洞。
你原始代码中的问题在于这两行:
$sql = "UPDATE village SET `$res` = ? WHERE `$villageID` = ? AND `$fabrik` = ?"; $stmt->execute([$res, $villageID, $fabrik]);
这里:
-
`$res`将$res(如"wood")当作列名插入,但$res是用户可控变量,未校验即拼接 → SQL 注入风险; -
`$villageID`和`$fabrik`同样被误用为列名(实际应为villageID和fabrik字段),且$villageID是整数 ID,不应作为列名使用; -
execute()中第一个参数$res是列名字符串(如"wood"),却赋值给SET wood = ?的?占位符 → 逻辑错乱(应传入要写入的新值,而非列名)。
✅ 正确做法分两步:
- 白名单校验列名与条件字段名:只允许预定义的合法字段参与 SQL 构建;
-
对数据值严格使用参数绑定:所有用户输入的值(如新资源数量、ID、状态)必须通过
?绑定。
以下是安全、可复用的改进版本:
public function setVillageRes(int $villageID, int $ownerID, string $resColumn, mixed $newValue, string $conditionColumn, mixed $conditionValue): string
{
// Step 1: 白名单校验字段名(防止注入)
$allowedColumns = ['wood', 'stone', 'iron', 'crop', 'pop', 'storage', 'warehouse'];
$allowedConditions = ['villageID', 'ownerID', 'level', 'type'];
if (!in_array($resColumn, $allowedColumns, true)) {
throw new InvalidArgumentException("Invalid resource column: $resColumn");
}
if (!in_array($conditionColumn, $allowedConditions, true)) {
throw new InvalidArgumentException("Invalid condition column: $conditionColumn");
}
// Step 2: 先验证村庄是否存在(使用安全参数化查询)
$stmt = $this->pdo->prepare("SELECT 1 FROM village WHERE villageID = ? AND ownerID = ?");
$stmt->execute([$villageID, $ownerID]);
if (!$stmt->fetch()) {
return "RE-3001"; // 村庄不存在或权限不符
}
// Step 3: 构建动态 SQL(字段名来自白名单,值全部参数化)
$sql = "UPDATE village SET `$resColumn` = ? WHERE `villageID` = ? AND `$conditionColumn` = ?";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([$newValue, $villageID, $conditionValue]);
return $stmt->rowCount() > 0 ? "SUCCESS" : "UPDATE_FAILED";
}
? 关键要点总结:
- ✅ 永远不要拼接用户输入到 SQL 字符串中作为列名/表名 —— 必须通过白名单或正则严格限制;
- ✅
?占位符只绑定值,如$newValue、$villageID、$conditionValue; - ✅ 首次查询(
SELECT 1 FROM...)也必须参数化,杜绝任何拼接; - ⚠️ 若需支持多条件(如
AND fabrik = ?),请扩展白名单并动态构建WHERE子句,但始终确保字段名来自可信源; - ? 进阶建议:使用数据库抽象层(如 Doctrine DBAL)或构建字段映射配置,进一步解耦业务逻辑与 SQL 结构。
遵循以上原则,你就能写出既灵活(一套函数支持所有资源类型)又安全(零 SQL 注入风险)的数据操作代码。










