PDO::__construct()第四个参数$options必须为数组或null,若传入字符串(如误用双引号包裹数组变量),PHP会触发TypeError,因类型约束要求?array但实际得到string。

PDO::__construct() 第四个参数必须是数组,传字符串会直接报 TypeError
为什么 PDO::__construct() 的第四个参数必须用数组
这个参数叫 $options,PHP 类型约束强制要求是 array|null。如果误写成字符串(比如把整个数组 var_export 后当字符串拼进去)、或在类属性里声明为 var $options; 但没初始化、或用 json_encode() 后传入,都会触发 Fatal error: Uncaught TypeError: PDO::__construct(): Argument #4 ($options) must be of type ?array, string given。
-
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION是最该设的,否则错误静默失败 -
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC避免每次fetch()都要指定类型 -
PDO::ATTR_PERSISTENT => true要慎用,长连接在 CLI 或 FPM 模式下行为不同 -
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"在 DSN 不带charset时才需要
DSN 里写了 charset,还要不要在 $options 里设 MYSQL_ATTR_INIT_COMMAND
不需要。DSN 中的 charset=utf8mb4 会由 MySQL 驱动自动处理字符集协商,比手动执行 SET NAMES 更底层、更可靠。重复设置反而可能在某些 MySQL 版本上引发 warning。
- 推荐 DSN 写法:
"mysql:host=localhost;dbname=test;charset=utf8mb4" - 若 DSN 没写 charset,且你无法修改 DSN(如框架封装了连接逻辑),再考虑加
MYSQL_ATTR_INIT_COMMAND -
PDO::ATTR_EMULATE_PREPARES => false应始终开启,避免 PHP 层模拟预处理导致 SQL 注入绕过
PHP8.2 下 PDO::ATTR_STRINGIFY_FETCHES 和 PDO::ATTR_ORACLE_NULLS 还能用吗
能用,但多数场景下不该设。PHP8.2 没废弃它们,但语义容易误导:
立即学习“PHP免费学习笔记(深入)”;
-
PDO::ATTR_STRINGIFY_FETCHES => true会让整数、布尔等全转成字符串——破坏类型一致性,ORM 和现代类型声明(如int返回值)会出问题 -
PDO::ATTR_ORACLE_NULLS只对 Oracle 有效,MySQL/PostgreSQL 忽略;设了也没效果,纯冗余 - PHP8.2 默认已关闭自动字符串化,保持默认
false即可
多数据库实例共存时,$options 数组能不能复用
可以,但要注意副作用。同一个数组变量被多次传给不同 PDO 实例,本身没问题;但如果中途修改了某个实例的属性(比如 $pdo1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT)),只影响该实例,不影响其他。
- 安全做法:每次 new PDO 时用字面量数组,例如
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] - 若从配置文件读取选项,确保是深拷贝(
array_merge([], $config['pdo_options'])),避免引用共享 - 特别注意
PDO::ATTR_PERSISTENT => true:相同 DSN + 用户 + 密码 + options 才算同一连接池,哪怕只差一个空格,也会新建池
最容易被忽略的是持久连接的“池匹配规则”——它严格比对 DSN 字符串、用户名、密码、options 数组的完整结构。少一个 PDO::ATTR_EMULATE_PREPARES 键,就等于开了个新池,起不到复用效果。



















