多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

PHP的PDO预定义常量讲解

PHP的PDO预定义常量讲解 前言PDO 的常量数量不少但结构其实很规整按用途可以分成五族PDO::PARAM_*参数与列的数据类型PDO::ATTR_*连接与语句的行为属性PDO::FETCH_*结果集的取回模式PDO::ERRMODE_*、PDO::CURSOR_*、PDO::CASE_*、PDO::NULL_*、PDO::FETCH_ORI_*各种枚举取值驱动专属的一批例如PDO::MYSQL_ATTR_*、PDO::PGSQL_ATTR_*、PDO::OCI_ATTR_*。围绕这些常量有三类常见误解。误解一把常量的整数值写死。网上流传的表里经常给出PDO::PARAM_STR 2这类数字。手册的预定义常量页面只标类型不标数值说明这些数字属于实现细节不同版本、不同驱动下不保证一致。代码里必须写常量名。误解二以为PDO::FETCH_ASSOC定义在PDOStatement上。取回模式确实是给语句用的但引用形式是PDO::FETCH_ASSOC——它们是PDO类的常量。手册把这一族常量的说明放在PDOStatement::fetch()页面而不是 PDO 的预定义常量页面上这个编排容易让人误判归属。误解三以为驱动专属常量到处都有。PDO::MYSQL_ATTR_SSL_CA只在pdo_mysql扩展加载后才存在。用它的时候如果扩展没装报的是未定义常量而不是一个友好的提示。本文按 PHP 8.1 把这几族常量梳理一遍重点讲清容易记错的几个和驱动相关的部分。一、参数类型常量 PARAM_*这一族用在bindParam()、bindValue()、quote()等需要声明类型的场合。常量含义备注PDO::PARAM_NULLSQL NULL 类型PDO::PARAM_BOOL布尔类型PDO::PARAM_INTSQL INTEGER 类型PDO::PARAM_STRSQL CHAR / VARCHAR 等字符串类型默认值PDO::PARAM_LOBSQL 大对象类型映射为 PHP 流PDO::PARAM_STMT记录集类型目前没有任何驱动支持PDO::PARAM_STR_NATL民族字符集字符串PHP 7.2.0 起PDO::PARAM_STR_CHAR常规字符集字符串PHP 7.2.0 起PDO::PARAM_INPUT_OUTPUT存储过程的 INOUT 参数必须按位或叠加基础类型几条必须记住的规则PDO::PARAM_STMT是个死常量。手册对它的说明直截了当——它代表记录集类型而目前没有任何驱动支持。看到老教程里用它来传递结果集可以确定是从别的语言搬过来的写法。PDO::PARAM_STR_NATL和PDO::PARAM_STR_CHAR必须与PDO::PARAM_STR按位或使用。手册明确写了这一点并补充说明截至 PHP 7.3只有dblib和mysql两个驱动支持这两个标志对 MySQL 而言它们只在PDO::ATTR_EMULATE_PREPARES为true默认值时才影响参数绑定。手册还给了个反面案例在utf8mb4的库里用PDO::PARAM_STR | PDO::PARAM_STR_NATL绑定 emoji可能触发排序规则混用错误因为带N前缀的字面量会被当成utf8而不是utf8mb4。规避办法就是老老实实用PDO::PARAM_STR并把 DSN 的charset设对。PDO::PARAM_INPUT_OUTPUT不能单独使用。它的作用是声明参数是存储过程的 INOUT 参数位模式需要与一个明确的PDO::PARAM_*数据类型做按位或。浮点数没有专用类型。这一族里没有PDO::PARAM_FLOAT。手册的用户注释里提到一个实践做法绑浮点数时用PDO::PARAM_STR。另外要注意bindParam()会把被绑定的变量类型转成字符串如果后续还要拿这个变量做数值运算可能引发类型错误——这种场合用bindValue()更稳妥。二、属性常量 ATTR_*PDO::ATTR_*通过构造函数选项数组、setAttribute()/getAttribute()读写。常用的可以按功能分组分类常量作用错误处理PDO::ATTR_ERRMODE配合PDO::ERRMODE_SILENT/PDO::ERRMODE_WARNING/PDO::ERRMODE_EXCEPTION取回默认值PDO::ATTR_DEFAULT_FETCH_MODE默认值为PDO::FETCH_BOTH预处理PDO::ATTR_EMULATE_PREPARES是否由 PDO 在 PHP 侧模拟预处理提交模式PDO::ATTR_AUTOCOMMIT手册措辞为尝试关闭自动提交持久连接PDO::ATTR_PERSISTENT只能在构造函数中设置大小写PDO::ATTR_CASE配合PDO::CASE_NATURAL/PDO::CASE_LOWER/PDO::CASE_UPPERNULL 转换PDO::ATTR_ORACLE_NULLS配合PDO::NULL_NATURAL/PDO::NULL_EMPTY_STRING/PDO::NULL_TO_STRING游标PDO::ATTR_CURSOR配合PDO::CURSOR_FWDONLY默认/PDO::CURSOR_SCROLL通信超时PDO::ATTR_TIMEOUT设置与数据库通信的超时秒数只读信息PDO::ATTR_SERVER_VERSION/PDO::ATTR_CLIENT_VERSION/PDO::ATTR_DRIVER_NAME查询用不可写取回修饰PDO::ATTR_STRINGIFY_FETCHES/PDO::ATTR_FETCH_TABLE_NAMES/PDO::ATTR_FETCH_CATALOG_NAMES影响结果集的呈现形式语句类PDO::ATTR_STATEMENT_CLASS指定自定义的语句类字符串默认类型PDO::ATTR_DEFAULT_STR_PARAM取值为PDO::PARAM_STR_NATL或PDO::PARAM_STR_CHARPHP 7.2.0 起其中PDO::ATTR_DRIVER_NAME值得单独提一下因为它是最实用的一条只读属性——用来判断当前连接跑在哪个驱动上从而决定某些分支逻辑走哪条路。手册给的示例就是这种用法?php // 适用于 PHP 8.0if ($pdo-getAttribute(PDO::ATTR_DRIVER_NAME) mysql) {// MySQL 专属的处理}注意这里用比较。getAttribute()的返回类型依赖具体属性用会在字符串与数值之间做松散比较容易踩到类型转换的坑。还有一条很容易忽略的只读属性不能写。手册对PDO::ATTR_SERVER_VERSION、PDO::ATTR_CLIENT_VERSION、PDO::ATTR_SERVER_INFO都标注了this is a read only attribute。试图setAttribute()它们不会按你想的方式生效。三、取回模式常量 FETCH_*这一族用在fetch()、fetchAll()、setFetchMode()上。常量返回形态PDO::FETCH_ASSOC以列名为键的数组PDO::FETCH_NUM以列序号从 0 开始为键的数组PDO::FETCH_BOTH同时带列名与序号两套键是ATTR_DEFAULT_FETCH_MODE的默认值PDO::FETCH_OBJstdClass实例列名作为属性PDO::FETCH_CLASS指定类的实例列映射为属性PDO::FETCH_INTO把数据填进一个已存在的对象实例PDO::FETCH_LAZYPDORow对象属性在访问时才创建PDO::FETCH_BOUND返回true把值赋给已用bindColumn()绑定的变量PDO::FETCH_NAMED列名重复时把多列合并成数组PDO::FETCH_COLUMN只取某一列的值PDO::FETCH_KEY_PAIR以第一列为键、第二列为值的关联数组PDO::FETCH_UNIQUE以结果集第一列为键索引PDO::FETCH_GROUP以第一列的值分组PDO::FETCH_FUNC每行调用指定回调用返回值构成结果PDO::FETCH_CLASSTYPE用第一列的值决定实例化哪个类PDO::FETCH_PROPS_LATE先调构造器再填属性PDO::FETCH_DEFAULT表示用默认取回模式较新版本才提供几个容易记混的点PDO::FETCH_UNIQUE与PDO::FETCH_GROUP都依赖第一列。手册说明想让结果数组以某列的值作键就在 SQL 的列清单里把这一列放在最前面再用PDO::FETCH_UNIQUE手册还专门警告这一列必须只含唯一值否则数据会丢失。PDO::FETCH_GROUP则产生三维数组若要与PDO::FETCH_COLUMN按位或就变成二维的分组数组。PDO::FETCH_CLASS的构造顺序反直觉。手册在常量页的注释里说明PDO::FETCH_CLASS会在设置属性之后才调用构造函数或者调用__set()之类的方法。想让构造函数先执行需要叠加PDO::FETCH_PROPS_LATE。PDO::FETCH_BOUND是给bindColumn()配的。它返回的是true而不是数据行数据通过引用被写进绑定变量里读 LOB 场景就靠它。版本变更要留心。手册在PDOStatement::fetchAll()的变更记录里写明PHP 8.0.0 起该方法总是返回数组此前失败时可能返回falsePHP 8.5.0 起把PDO::FETCH_INTO当取回模式传入会抛ValueError把PDO::FETCH_PROPS_LATE与非PDO::FETCH_CLASS的组合传入也会抛ValueError。老项目升级时要检查有没有踩到这几条。游标相关的常量PDO::CURSOR_FWDONLY与PDO::CURSOR_SCROLL也属于这一族的使用场景想要可滚动游标必须在PDO::prepare()时通过选项把PDO::ATTR_CURSOR设成PDO::CURSOR_SCROLL然后才能配合PDO::FETCH_ORI_NEXT/FETCH_ORI_PRIOR/FETCH_ORI_FIRST/FETCH_ORI_LAST/FETCH_ORI_ABS/FETCH_ORI_REL定位。很多驱动并不支持可滚动游标默认的PDO::CURSOR_FWDONLY才是可移植的那一档。四、驱动专属常量与自查方法驱动会把自家属性的常量挂到同一个PDO类命名空间下用前缀区分。以 MySQL 驱动为例手册列出的有PDO::MYSQL_ATTR_USE_BUFFERED_QUERYPDO::MYSQL_ATTR_LOCAL_INFILEPDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORYPDO::MYSQL_ATTR_INIT_COMMANDPDO::MYSQL_ATTR_READ_DEFAULT_FILEPDO::MYSQL_ATTR_READ_DEFAULT_GROUPPDO::MYSQL_ATTR_MAX_BUFFER_SIZEPDO::MYSQL_ATTR_DIRECT_QUERYPDO::MYSQL_ATTR_FOUND_ROWSPDO::MYSQL_ATTR_IGNORE_SPACEPDO::MYSQL_ATTR_COMPRESSPDO::MYSQL_ATTR_SERVER_PUBLIC_KEYPDO::MYSQL_ATTR_MULTI_STATEMENTSPDO::MYSQL_ATTR_SSL_KEYPDO::MYSQL_ATTR_SSL_CERTPDO::MYSQL_ATTR_SSL_CAPDO::MYSQL_ATTR_SSL_CAPATHPDO::MYSQL_ATTR_SSL_CIPHERPDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT其他驱动的数量少一些例如 PostgreSQL 的PDO::PGSQL_ATTR_DISABLE_PREPARESOracle 的PDO::OCI_ATTR_ACTION/PDO::OCI_ATTR_MODULE/PDO::OCI_ATTR_CLIENT_INFO/PDO::OCI_ATTR_CLIENT_IDENTIFIER。这些常量只在使用对应驱动时才存在。与其背下来不如让代码自己查。用反射可以一次性列出当前环境真正可用的常量?php // 适用于 PHP 8.0$all (new ReflectionClass(PDO::class))-getConstants();$groups [PDO::PARAM_ PARAM_,PDO::ATTR_ ATTR_,PDO::FETCH_ FETCH_,PDO::MYSQL_ATTR_ MYSQL_ATTR_,];foreach ($groups as $label $prefix) {$hits array_filter($all,static fn(string $name): bool str_starts_with($name, $prefix),ARRAY_FILTER_USE_KEY);printf(%s 共 %d 个\n, $label, count($hits));}这段代码在装了pdo_mysql的环境里会打印出PDO::MYSQL_ATTR_那一组的数量没装的话这一组是 0PDO::MYSQL_ATTR_SSL_CA这类常量也不存在代码里直接引用会报未定义常量。常见坑点把常量值硬编码❌$stmt-bindValue(1, $v, 2);用数字 2 代表PDO::PARAM_STR✅ 一律写常量名整数值属于实现细节不保证跨版本一致用了没人支持的PDO::PARAM_STMT❌bindParam(1, $stmt, PDO::PARAM_STMT)✅ 手册明确写着目前没有任何驱动支持别用PARAM_STR_NATL单独使用❌bindValue(:s, $v, PDO::PARAM_STR_NATL)✅ 必须PDO::PARAM_STR | PDO::PARAM_STR_NATL而且 MySQL 上要留意utf8mb4的排序规则冲突找不存在的浮点类型❌bindValue(:f, 1.5, PDO::PARAM_FLOAT)✅ 这一族没有PARAM_FLOAT浮点场景用PDO::PARAM_STR以为PDO::FETCH_UNIQUE的键是随便哪一列❌SELECT name, id FROM t然后fetchAll(PDO::FETCH_UNIQUE)✅ 手册要求把想当键的那列放在 SQL 列清单最前面且该列值必须唯一PDO::FETCH_CLASS期待构造函数先跑❌ 构造函数里依赖属性已被赋值 ✅ 默认是先填属性后调构造器要构造函数先执行需叠加PDO::FETCH_PROPS_LATE在没装对应驱动的环境里引用驱动专属常量❌ 直接写PDO::MYSQL_ATTR_SSL_CA而不确认扩展加载 ✅ 先用extension_loaded(pdo_mysql)或反射确认常量存在用比较getAttribute()的返回值❌if ($pdo-getAttribute(PDO::ATTR_DRIVER_NAME) mysql)✅ 用属性返回类型随属性而变松散比较不可靠总结常量族用途关键提醒PDO::PARAM_*声明参数与列类型PARAM_STMT无驱动支持PARAM_STR_NATL/PARAM_STR_CHAR需按位或无浮点类型PDO::ATTR_*连接与语句行为ATTR_ERRMODE默认值 PHP 8.0 起为异常部分属性只读PDO::FETCH_*结果集取回形态FETCH_BOTH是默认FETCH_UNIQUE/FETCH_GROUP依赖第一列PDO::ERRMODE_*错误策略取值三个取值SILENT / WARNING / EXCEPTIONPDO::CURSOR_*游标模式默认CURSOR_FWDONLY可滚动游标多数驱动不支持PDO::CASE_*/PDO::NULL_*列名大小写与 NULL 转换取值分别配合ATTR_CASE与ATTR_ORACLE_NULLS驱动专属MYSQL_ATTR_*/PGSQL_ATTR_*/OCI_ATTR_*仅在对应扩展加载后存在PDO 的常量设计思路是用名字表达语义把数值留给实现。记住三件事就能避开绝大多数坑常量名不能换成数字、PARAM_STR_NATL和PARAM_INPUT_OUTPUT这类标志必须按位或叠加基础类型、驱动专属常量要等扩展加载后才存在。至于具体有哪些常量可用反射一行代码就能列出来比背表可靠得多。
返回列表