activerecord update() 数组写法天然防sql注入,因yii2会通过pdo预处理绑定参数;动态字段名需白名单校验,原生sql必须用bindvalue/bindvalues,where条件须用数组而非字符串拼接。

ActiveRecord update() 数组写法天然防注入
只要用 update() 接收数组参数,Yii2 就会走 PDO 预处理,用户输入自动绑定为参数,不会拼进 SQL 字符串里。这是最安全、最常用的方式。
常见错误是把字段名或值拼成字符串传进去,比如 User::updateAll("status = 1 WHERE id = $id", ...) —— 这种写法完全绕过参数化,等同于裸奔。
-
User::updateAll(['status' => 1], ['id' => $id])✅ 安全,底层生成UPDATE user SET status = ? WHERE id = ? -
$user->status = 1; $user->save()✅ 安全,save()内部也走参数化更新 -
User::updateAll('status = 1', 'id = ' . (int)$id)⚠️ 即使强转 int,也不推荐——字段名、条件逻辑一旦变复杂,极易漏掉校验
动态字段更新必须白名单校验
如果要根据用户输入决定更新哪个字段(比如后台编辑器允许改 title 或 content),update() 的数组键名不能直接来自用户请求——因为字段名本身无法被 PDO 绑定。
错误示例:User::updateAll([$field => $value], ['id' => $id]),其中 $field 来自 $_POST['field']。
- 必须先判断
$field是否在允许列表中:in_array($field, ['title', 'content', 'status'], true) - 禁止使用
array_merge(['id' => $id], $_POST)直接传给updateAll(),否则攻击者可提交['class' => 'yii\db\Connection']类似恶意键名 - 更稳妥的做法是显式映射:
$data = []; if ($field === 'title') $data['title'] = $value;
原生 SQL 更新必须用 bindValue / bindValues
绕过 ActiveRecord、手写 createCommand() 执行 UPDATE 时,唯一安全路径是占位符 + 绑定,绝不能字符串拼接。
典型错误:"UPDATE post SET title = '$title' WHERE id = $id" —— 单引号、双引号、转义函数都救不了你。
- ✅ 正确:
Yii::$app->db->createCommand('UPDATE post SET title = :title WHERE id = :id')->bindValue(':title', $title)->bindValue(':id', $id)->execute() - ✅ 多参数更简洁:
->bindValues([':title' => $title, ':id' => $id]) - ⚠️ 禁用
bindParam()除非你真需要引用传递(比如循环批量更新),否则容易因变量生命周期问题导致数据错乱
批量更新时注意 where 条件的参数化
updateAll() 的第二个参数是 WHERE 条件,它和 where() 一样:数组安全,字符串危险。
例如电商后台按状态批量改价:User::updateAll(['price' => $newPrice], ['status' => 'on_sale']) 没问题;但若写成 ['status = "on_sale" AND category_id = ' . $catId] 就立刻失效。
- 复合条件优先用嵌套数组:
['and', ['status' => 'on_sale'], ['category_id' => $catId]] - 涉及运算符(如
BETWEEN、IN)时,用Expression包裹 SQL 片段,再用addParams()绑定外部变量 - 永远不要信任前端传来的
WHERE字符串片段——哪怕它看起来“只是个数字”
最常被忽略的是动态字段名和复合 WHERE 字符串拼接,这两个点一旦松动,参数化就形同虚设。安全不是靠“大概率没问题”,而是每个入口都确认过绑定或白名单。











