ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PHP 8.1+ 中 mysqli--execute() 直接传参功能详解

PHP 8.1+ 中 mysqli--execute() 直接传参功能详解 前言在 PHP 8.1 之前用 mysqli 预处理语句Prepared Statement必须走两步先bind_param()绑变量再execute()执行。bind_param()的第一个参数是类型字符串如sdi它和后面的变量必须一一对应且数量严格相等多一个少一个都会报Number of elements in type definition string must match number of bind variables。这个错误在参数动态增减比如按条件拼接 WHERE时几乎必然出现。PHP 8.1.0 起mysqli_stmt::execute()增加了一个可选的$params参数可以直接把参数数组传进execute()不用再先bind_param()。这个特性来自 RFCmysqli_bind_in_execute行为对齐了 PDO 早就有的PDOStatement::execute($params)。标题里的版本号8.1是对的但方法名写得不精确这个特性加在mysqli_stmt::execute()上过程式别名是mysqli_stmt_execute()而不是mysqli::execute()。mysqli类本身没有execute()方法直接写$mysqli-execute([...])会报未定义方法。本文按真实的方法名mysqli_stmt::execute()来讲。一、签名与生效版本项目内容生效版本PHP8.1.0来源 RFCmysqli_bind_in_execute方法mysqli_stmt::execute(?array $params null): bool过程式别名mysqli_stmt_execute()、mysqli_execute()依赖驱动仅mysqlndMySQL Native Driver与 PDO 的关系行为对齐PDOStatement::execute($params)版本边界要记牢PHP 8.0 及更早版本给execute()传数组会直接报错典型错误是ArgumentCountError: mysqli_stmt::execute() expects exactly 0 arguments, 1 givenPHP 7 及更早则是未定义行为。所以这个特性完全不能靠降级兼容代码里必须锁死最低版本。判断当前环境用的是不是 mysqlnd可以在 PHP 里直接读客户端信息?php declare(strict_types1); echo mysqli client info: , mysqli_get_client_info(), PHP_EOL; $isNative str_contains(mysqli_get_client_info(), mysqlnd); echo is mysqlnd: , $isNative ? yes : no, PHP_EOL;如果输出is mysqlnd: no也就是编译时链接的是 libmysqlclient那么给execute()传数组会报错提示该驱动不支持这种绑定方式——这不是你代码写错了而是环境不对换 PDO 或重新编译 mysqli 才能解决。二、和 bind_param 的对比两种写法的差异不只是少写一行维度bind_param($types, ...$vars)execute(array $params)生效版本一直都有PHP 8.1.0参数传递按引用$var传变量按值传数组类型控制显式指定s/i/d/b无全部按字符串绑定数量校验运行时比对类型字符串交给引擎数量不匹配抛ValueError数组形式需要展开运算符...$arr直接传数组天然支持动态参数null处理需要显式处理数组里的null保持为 SQLNULL混用两者不能混用两者不能混用这里最关键的一条是全部按字符串绑定。也就是说无法像bind_param(i, $id)那样显式告诉 MySQL 这是个整数。绝大多数情况下这不是问题——MySQL 会按列类型做隐式转换WHERE id 42在整型索引列上仍然走索引。但在ORDER BY、LIMIT这类不接受占位符的位置上你本来也不能用参数所以这个限制影响面有限。真正需要在 SQL 层面对二进制大对象BLOB做字节级绑定时才需要回到bind_param()用b类型。三、参数规则与错误行为参数数组的规则不多但每条违反了都会立刻报错属于错了就告诉你的类型必须是 list 数组下标从 0 开始且连续。用字符串键的关联数组会抛ValueError提示参数必须是 list。元素个数必须与 SQL 里的占位符数量完全相等。少了或多了都抛ValueError提示需要恰好 N 个元素、实际给了 M 个。传非数组比如直接传一个整数会抛TypeError。null会作为 SQLNULL发送不会被转成空字符串。值会被记住。第一次execute([...])之后后续再调用无参的execute()会复用上次绑定的值——这和 PDO 的行为一致也是明明没传参却插入了旧数据这类诡异问题的来源。还有一条容易忽略的mysqli_stmt::execute()是可被继承的方法子类重写它时必须把新增的$params参数写进签名否则 PHP 8.1 会因为签名不兼容而报错。四、完整可运行示例下面这段是完整程序先建表再增删查改。运行前需要一个可连的 MySQL/MariaDB用 Docker 起一个也很快docker run -d --name mysql8 -e MYSQL_ROOT_PASSWORDroot -e MYSQL_DATABASEdemo -p 3306:3306 mysql:8 # 等 20 秒左右让 mysqld 初始化完成?php declare(strict_types1); // 需要 PHP 8.1execute() 直接传参在 PHP 8.5 上同样可用 $mysqli new mysqli(127.0.0.1, root, root, demo, 3306); $mysqli-set_charset(utf8mb4); $mysqli-query(DROP TABLE IF EXISTS my_city); $mysqli-query( CREATE TABLE my_city ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, country_code CHAR(3) NOT NULL, district VARCHAR(64) NULL, UNIQUE KEY uk_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 ); // ---- 1. 插入直接传数组不再 bind_param ---- $stmt $mysqli-prepare(INSERT INTO my_city (name, country_code, district) VALUES (?, ?, ?)); $stmt-execute([Stuttgart, DEU, Baden-Wuerttemberg]); $stmt-execute([Nashville, USA, null]); // null 会作为 SQL NULL 存入 echo inserted rows: , $stmt-affected_rows, PHP_EOL; // 值会被记住无参调用会复用上一次绑定的值。 // 表上有 UNIQUE KEY uk_name所以这次会撞唯一键——恰好证明了参数确实被记住了。 try { $stmt-execute(); } catch (mysqli_sql_exception $e) { echo values persisted, duplicate rejected, PHP_EOL; } // ---- 2. 动态占位符IN 查询参数个数运行时决定 ---- $cities [Stuttgart, Nashville, Nowhere]; $holders implode(,, array_fill(0, count($cities), ?)); $in $mysqli-prepare(SELECT name, country_code FROM my_city WHERE name IN ($holders) ORDER BY name); $in-execute($cities); // 直接把数组扔进去无需 ...展开 foreach ($in-get_result() as $row) { printf(%-10s %s\n, $row[name], $row[country_code]); } // ---- 3. 关联数组会报错的验证 ---- try { $in-execute([k Stuttgart, v Nashville]); } catch (ValueError $e) { echo assoc array rejected: , $e-getMessage(), PHP_EOL; } // ---- 4. 元素个数不匹配的验证 ---- try { $in-execute([Stuttgart]); // 需要 3 个只给 1 个 } catch (ValueError $e) { echo count mismatch: , $e-getMessage(), PHP_EOL; } // ---- 5. 更新与删除同样适用 ---- $upd $mysqli-prepare(UPDATE my_city SET district ? WHERE name ?); $upd-execute([Tennessee, Nashville]); echo updated rows: , $upd-affected_rows, PHP_EOL; $del $mysqli-prepare(DELETE FROM my_city WHERE name ?); $del-execute([Nowhere]); echo deleted rows: , $del-affected_rows, PHP_EOL; $stmt-close(); $in-close(); $upd-close(); $del-close(); $mysqli-close();预期输出inserted rows: 1 values persisted, duplicate rejected Nashville USA Stuttgart DEU assoc array rejected: mysqli_stmt::execute(): Argument #1 ($params) must be a list array count mismatch: mysqli_stmt::execute(): Argument #1 ($params) must consist of exactly 3 elements, 1 present updated rows: 1 deleted rows: 0注意最后deleted rows: 0是正确结果my_city表里从来没有Nowhere这一行删除影响 0 行是符合预期的。五、别忽略 mysqli 在 8.1 的另一个变化同一个版本里mysqli 还有一处破坏性变更默认错误模式从静默/警告改成了抛出异常等价于默认启用了MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT。也就是说一条语法错误的 SQL 现在会抛mysqli_sql_exception而不是返回false。如果你在升级后看到以前只是返回 false 的地方现在直接 500 了就是这个原因。要恢复旧行为?php declare(strict_types1); mysqli_report(MYSQLI_REPORT_OFF); // 回到 8.0 之前的默认行为但更推荐的做法是保留抛异常并统一接住?php declare(strict_types1); try { $stmt $mysqli-prepare(SELECT * FROM not_exists); $stmt-execute(); } catch (mysqli_sql_exception $e) { // mysqli_sql_exception 继承自 RuntimeException同时也实现 Throwable error_log(db error: . $e-getMessage()); throw new RuntimeException(数据库操作失败, 0, $e); }常见坑点1. 以为execute()直接传参是 PHP 8.0 的特性❌ 在 PHP 8.0 上写$stmt-execute($params)报expects exactly 0 arguments, 1 given✅ 确认运行版本是 8.1或者统一改用bind_param()以兼容更老的版本2. 把mysqli对象当成有execute()的类❌$mysqli-execute([a])——mysqli类没有这个方法 ✅$mysqli-prepare(...)之后在返回的mysqli_stmt上调用execute()3. 用关联数组传参❌$stmt-execute([name Tom])抛ValueError因为要求是 list ✅ 用位置数组$stmt-execute([Tom])或$stmt-execute(array_values($assoc))4. 以为传进去的值能指定类型❌ 期望execute([$id])按整数绑定结果拿到字符串绑定后行为与预期不符 ✅ 需要显式类型尤其是b二进制时回到bind_param(i, $id)5.bind_param()和execute($params)混用❌ 先bind_param(s, $name)后面又execute([Tom])—— 两种绑定方式不能混用 ✅ 一个mysqli_stmt上只选一种方式从头用到尾6. 忽略了值会被记住这一点❌ 循环里$stmt-execute()不传参以为每次都是上次的值之外的新值结果反复写入同一条数据 ✅ 每次调用都显式传参不要把复用上次绑定当作正常逻辑7. 拿到的是 libmysqlclient却以为是代码问题❌ 折腾半天 SQL 和版本实际是驱动不支持 ✅ 先mysqli_get_client_info()判断是不是 mysqlnd不是就换 PDO8. 升级到 8.1 后没处理 mysqli 默认抛异常❌ 沿用if (!$stmt) { ... }的写法异常根本没机会被判断 ✅ 用try/catch (mysqli_sql_exception)或显式mysqli_report(MYSQLI_REPORT_OFF)后自行判断返回值总结关注点结论生效版本PHP 8.1.0方法在mysqli_stmt上不是mysqli数组要求必须是 list元素个数必须与占位符数量完全相等类型控制无值统一按字符串绑定null保持为 SQLNULL驱动依赖仅 mysqlndlibmysqlclient 会直接报不支持混用不能与bind_param()混用同版本其它变化mysqli 默认错误模式改为抛异常mysqli_stmt::execute()直接传参解决的是动态占位符 类型字符串对不上这个长期痛点写法上和 PDO 拉齐了。代价是失去显式类型控制、丢失bind_param的引用语义并且必须运行在 PHP 8.1 以上、且编译时用的是 mysqlnd。把这三条前提确认清楚这个特性就能放心用。
返回列表