PHP 8.5 中必须显式设置 PDO::ATTR_EMULATE_PREPARES => false 以启用真实预处理,否则存在SQL注入风险;DSN 需含 charset=utf8mb4;getAttribute 返回整数 0/1,校验须用 === 0。

PHP 8.5 中 PDO::ATTR_EMULATE_PREPARES 必须显式设为 false
PHP 8.5 默认仍保持 PDO::ATTR_EMULATE_PREPARES => true,哪怕你写了 prepare() + execute(),只要没关模拟,就不是真预处理。MySQL 服务端根本没收到编译过的语句模板,攻击者在宽字节或旧版本 MySQL 下仍可能绕过防护。
这不是“建议关闭”,而是生产环境安全底线:必须在创建 PDO 实例时传入该选项,不能依赖后续 setAttribute() 补救(部分驱动在连接建立后不接受该属性变更)。
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);- DSN 中必须含
charset=utf8mb4,例如:mysql:host=localhost;dbname=test;charset=utf8mb4 - 验证是否生效:
var_dump($pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES));—— 返回0(注意是整数 0,不是布尔false)才正确
为什么 getAttribute(PDO::ATTR_EMULATE_PREPARES) 返回 0 而不是 false
这是 PDO 内部实现的类型不一致问题:虽然文档说该属性是布尔值,但 getAttribute() 实际返回整数 0 或 1。用 !== false 判断会永远为真,导致校验逻辑失效。
- ❌ 错误校验:
if ($pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES) !== false) - ✅ 正确校验:
if ($pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES) === 0)或if (!$pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES)) -
setAttribute(PDO::ATTR_EMULATE_PREPARES, 0)和setAttribute(PDO::ATTR_EMULATE_PREPARES, false)都可接受,但推荐统一用整数0避免歧义
prepare() 失败时 execute() 会直接 fatal error
当 PDO::ATTR_EMULATE_PREPARES => false 且 MySQL 不支持原生预处理(如极老版本、某些代理中间件),prepare() 可能返回 false。此时若直接链式调用 ->execute(),PHP 会抛出致命错误,而不是触发异常。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 必须检查
prepare()返回值:$stmt = $pdo->prepare($sql); if (!$stmt) { throw new RuntimeException('Prepare failed'); } - 不要写成:
$pdo->prepare($sql)->execute(...)—— 这在原生预处理不可用时必然崩 - PostgreSQL 用户注意:PHP 8.5 已废弃
PDO::PGSQL_ATTR_DISABLE_PREPARES,但 PostgreSQL 默认走原生预处理,无需额外开关
命名参数绑定对冒号和大小写极其敏感
使用命名占位符时,键名必须与 SQL 中的占位符**逐字符一致**,包括冒号、大小写、空格。PHP 不做任何 normalize,错一个字符就绑定失败,且不会报错,只是查不到数据。
- ✅ 正确:
$stmt->execute([':user_id' => 123])对应WHERE id = :user_id - ❌ 错误:
['user_id' => 123](缺冒号)、[':USER_ID' => 123](大小写不匹配)、[':user_id ' => 123](尾部空格) - 位置参数从 1 开始编号:
$stmt->bindValue(1, $val, PDO::PARAM_STR),不是数组下标 0
真正危险的不是不会写,而是以为写了 prepare 就万事大吉——只要 PDO::ATTR_EMULATE_PREPARES 没关,或者 DSN 缺字符集,或者标识符(表名/字段名)拿白名单校验漏了一种 case,整条防护链就断了。这些点都不报错,只悄悄失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










