
直接用 createCommand() + 参数绑定,是 Yii 防 SQL 注入最可靠的方式;Active Record 虽自动防护,但一碰动态字段、排序、原生子查询就容易漏防。
createCommand() 必须绑定参数,不能拼字符串
常见错误是把用户输入直接塞进 SQL 字符串里,比如:"SELECT * FROM user WHERE name = ‘" . $_GET[‘name’] . "’"——这等于给攻击者开了个数据库后门。Yii 的 createCommand() 本身不防注入,防注入靠的是后续的 bindValue() 或 bindValues()。
占位符必须带冒号前缀::status,不是 status 或 ?<code>绑定时键名要严格一致:bindValue(‘:status’, $status),少个冒号就失效字符串类型建议显式指定:bindValue(‘:name’, $name, PDO::PARAM_STR),避免数字型字段被误判为整数导致截断IN 条件要用循环绑定或 str_repeat() 拼占位符,不能写成 "id IN (" . implode(‘,’, $ids) . ")"
Active Record 不是万能盾,动态部分照样会中招
像 User::find()->where([‘status’ => $status])->all() 这种写法确实安全,因为 AR 内部走的是预处理。但一旦涉及字段名、表名、ORDER BY、GROUP BY 等动态结构,AR 就不兜底了。
排序字段必须白名单校验:if (!in_array($sort, [‘created_at’, ‘price’, ‘name’])) { throw new BadRequestHttpException(); }不能写:->orderBy($sort . ‘ DESC’),而要写:->orderBy([$sort => SORT_DESC])(仅限白名单内字段)连表别名、自定义字段别名若来自用户输入,也得先过滤再拼,否则 AS $_GET[‘alias’] 就是注入点用 andFilterWhere() 代替手拼条件,它会跳过空值且保持绑定逻辑
queryAll() / queryOne() 和 execute() 别混用
这是新手高频翻车点:拿 execute() 去查数据,或者拿 queryAll() 去执行 INSERT——结果要么没数据、要么报错、要么静默失败。
execute() 只用于无结果集语句:INSERT / UPDATE / DELETE / CREATE / DROP,返回受影响行数(int)queryAll() 返回二维数组,queryOne() 返回一维数组,queryScalar() 返回单个值(如 COUNT)查 SELECT 却调了 execute(),你会拿到 0,但实际什么都没取出来INSERT 用 insert(‘table’, $data) 更简洁,但含 ON DUPLICATE KEY UPDATE 时,还是得回 createCommand() + execute()
事务里复用 command 对象要注意变量引用
批量操作时想复用一个 createCommand() 实例提升性能,用 bindParam() 比 bindValue() 更合适,但必须注意变量生命周期。
bindParam() 绑定的是变量引用,不是值快照;循环中修改变量,下次 queryOne() 就用新值如果在事务闭包外定义了绑定变量,闭包内又没重新赋值,可能拿到的是上一次的旧值更稳妥的做法是:在循环体内每次调 bindValue(),或确保 bindParam() 所绑变量只在当前迭代内有效事务未提交前,重复执行同一 command 不会自动刷新连接状态,出错时记得手动 rollback()
最危险的不是不会写绑定,而是以为“用了 AR 就安全”或“写了 createCommand 就算防护了”。SQL 注入的入口往往藏在 ORDER BY、字段别名、IN 列表、甚至 LIMIT 的偏移量里——这些地方不校验、不绑定,预处理机制就形同虚设。

评论(0)