PHP7.4升级到8.0的废弃函数怎么替换
前言把项目从 PHP 7.4 切到 8.0 之后最常见的两种现场是一是页面直接 500日志里是Fatal error: Uncaught Error: Call to undefined function each()二是代码根本解析不了报syntax error, unexpected {指向一行看起来人畜无害的$row[name]{0}。成因是 PHP 8.0 做了一轮大规模清理。7.x 里标了好几轮deprecated的东西这一次被整批删除没有过渡期同时还删掉了几处语法花括号下标、(unset)强制转换并且把一批原本只是警告的行为升级成了异常。这些改动是发散的靠逐个页面点开去试效率极低。本文分四类讲被整块删掉的函数、被删掉的语法、被升级成错误的行为变化以及只是弃用、还活着的那批。每类都给出替换写法和一个能直接在项目上跑的静态扫描脚本。一、被整批删掉的函数先看总表这些名字在 PHP 8.0 上调用就会得到Call to undefined function。PHP 7.4 还能用PHP 8.0 状态替换写法each($arr)已移除foreach ($arr as $k $v)create_function($a, return $a*2;)已移除fn($a) $a * 2或闭包money_format(%i, $n)已移除sprintf()/number_format()/NumberFormatter::formatCurrency()$php_errormsg配合track_errors已移除error_get_last()get_magic_quotes_gpc()已移除直接删掉判断magic quotes 从 PHP 5.4 起就没有了get_magic_quotes_runtime()已移除同上set_magic_quotes_runtime()已移除同上hebrevc($s)已移除hebrev()加nl2br()convert_cyr_string()已移除mb_convert_encoding()或iconv()ezmlm_hash()已移除无替代业务上一般用不到restore_include_path()已移除ini_restore(include_path)fgetss()已移除fgets()加strip_tags()read_exif_data()已移除exif_read_data()image2wbmp()/png2wbmp()/jpeg2wbmp()已移除imagewbmp()ldap_sort()已移除在 PHP 侧对结果排序ldap_control_paged_result()及其_response()已移除用 LDAP 分页对应的新接口FILTER_SANITIZE_MAGIC_QUOTES常量已移除FILTER_SANITIZE_ADD_SLASHES按出现频率排真正会撞上的其实只有三个each()、create_function()、money_format()。前两个在十年前的教程代码里到处都是第三个常见于旧的价格格式化逻辑。each()的替换要小心一点因为它常常和while搭配做手动迭代转换时需要确认数组指针语义?php declare(strict_types1); $config [host 127.0.0.1, port 3306, user root]; // ❌ PHP 7.4 写法PHP 8.0 直接报 Call to undefined function each() /* reset($config); while (list($key, $value) each($config)) { echo $key, , $value, PHP_EOL; } */ // ✅ 替换一绝大多数场景直接换成 foreach foreach ($config as $key $value) { echo $key, , $value, PHP_EOL; } // ✅ 替换二确实需要取一个再接着取下一个的场景 reset($config); while (($key key($config)) ! null) { echo $key, , current($config), PHP_EOL; next($config); }create_function()替换成箭头函数时要注意作用域差异。create_function()生成的函数体是字符串外部变量必须靠参数传进去箭头函数会自动按值捕获外层变量反而更省事?php declare(strict_types1); $multiplier 3; // ❌ PHP 7.4 写法 // $double create_function($a, return $a * 2;); // ✅ 箭头函数PHP 7.4 起可用PHP 8.0 当然也支持 $double fn(int $a): int $a * 2; // ✅ 需要捕获外层变量时箭头函数自动按值捕获不需要 use $scale fn(int $a): int $a * $multiplier; echo $double(21), PHP_EOL; // 42 echo $scale(14), PHP_EOL; // 42money_format()需要区分两种情况只是要一个两位小数的金额字符串用number_format()确实要处理货币符号和地区格式用 intl 扩展的NumberFormatter。?php declare(strict_types1); $amount 1234.5; // ❌ PHP 7.4 可用的写法注意 locale 依赖结果不稳定 // setlocale(LC_MONETARY, en_US); // echo money_format(%i, $amount), PHP_EOL; // ✅ 简单场景 echo number_format($amount, 2), PHP_EOL; // 1,234.50 echo sprintf(%.2f, $amount), PHP_EOL; // 1234.50 // ✅ 需要货币符号与本地化格式时需要 intl 扩展 if (class_exists(NumberFormatter::class)) { $formatter new NumberFormatter(zh_CN, NumberFormatter::CURRENCY); echo $formatter-formatCurrency($amount, CNY), PHP_EOL; } else { echo 未安装 intl 扩展请先启用或改用 number_format(), PHP_EOL; }二、被删掉的语法报错形式完全不同这一类比函数删除更让人抓瞎因为报的是syntax error看起来像文件坏了。PHP 7.4 写法PHP 8.0 状态替换写法$str{0}/$arr{1}语法错误$str[0]/$arr[1](unset) $var语法错误unset($var)real类型声明已移除用floatimplode($array, $glue)已移除implode($glue, $array)mb_strrpos($h, $n, UTF-8)已移除mb_strrpos($h, $n, 0, UTF-8)array_key_exists($k, $obj)已移除isset($obj-$k) \与类同名的方法当构造函数已移除改成__construct()花括号下标是最容易批量踩到的因为它藏在旧模板代码和旧 SDK 里?php declare(strict_types1); $uuid 550e8400-e29b-41d4-a716-446655440000; // ❌ PHP 8.0 里这是语法错误连 php -l 都过不去 // echo $uuid{0}; // ✅ echo $uuid[0], PHP_EOL; // 5implode()参数顺序颠倒这个坑特别隐蔽它在 7.4 里只发一条弃用提示代码照跑到 8.0 直接抛ArgumentCountError或者得到一个内容错乱的结果。而参数只是一个二维数组时两种写法都可能看起来没报错因为implode($glue, $array)遇到数组元素会转成Array字符串——症状是输出的字符串莫名其妙。array_key_exists()传对象也值得单独说它在 PHP 7.4 起弃用、8.0 移除替代要看你的意图——判断属性存在用property_exists()判断属性存在且不为 null用isset()。三、从警告升级成错误的行为变化这一类最阴险代码没有使用任何被删除的 API但运行结果变了或者运行到某一行突然抛异常。表达式PHP 7.4 结果PHP 8.0 结果0 footrue字符串被转成 0false按字符串比较abc 1警告结果1抛TypeError5 apples 1警告结果6警告结果6前导数字串仍可用1 / 0警告结果是INF抛DivisionByZeroError$undefined-method()提示被抑制致命错误提示不再被抑制读未定义变量NoticeWarning0 foo的变化是升级时最大的一片雷区。任何用比较可能是 0、可能是字符串的地方都会静默改变行为。典型受害者是那种根据 URL 参数决定走哪个分支的老代码?php declare(strict_types1); $mode $_GET[mode] ?? ; // ❌ PHP 7.4 里 $mode list 能进这个分支PHP 8.0 里进不去了 // if ($mode 0) { /* 走列表模式 */ } // ✅ 显式判断跨版本行为一致 if ($mode 0 || $mode 0) { echo 走列表模式, PHP_EOL; } // ✅ 或者用 matchPHP 8.0 新增用严格比较 $label match (true) { $mode 默认, $mode list 列表, default 未知, }; echo $label, PHP_EOL;四、只是弃用、还活着的那批别把它们和上面的删除项混为一谈。这些在 8.0 上仍然可用只是会在日志里刷弃用提示项目8.0 状态替代assert()传字符串弃用assert($expr)只传布尔表达式libxml_disable_entity_loader()弃用不再需要靠LIBXML_NONET等选项控制ReflectionParameter::getClass()弃用getType()ReflectionParameter::isArray()弃用判断getType()是否是ReflectionNamedType且名字为arrayReflectionParameter::isCallable()弃用同理检查getType()这里必须纠正一个很常见的误传把 null 传给不可为 null 的内部函数参数是 PHP 8.1 才弃用的不是 8.0。所以strlen(null)、substr($s, null)这类写法在 8.0 上不会报弃用升到 8.1 才会开始刷屏。把 8.0 和 8.1 的弃用清单混在一起背会让排查方向完全跑偏。实战一个能跑在项目目录上的静态扫描脚本人工翻代码找上面这些名字效率太低。下面这个脚本用token_get_all()做词法级扫描能避开注释和字符串里的误报。语法用 PHP 7.4 也能跑可以在升级前先扫一遍。?php declare(strict_types1); // scan-removed.php —— 用法: php scan-removed.php ./src // 需要 PHP 7.4可以在旧版本环境里先跑 $removed [ create_function 改成箭头函数 fn() ... 或普通闭包, each 改成 foreach ($arr as $k $v), money_format 改成 number_format() / sprintf() / NumberFormatter, get_magic_quotes_gpc magic quotes 从 PHP 5.4 起已移除直接删掉判断, get_magic_quotes_runtime 直接删掉调用, set_magic_quotes_runtime 直接删掉调用, hebrevc 改成 hebrev() 加 nl2br(), convert_cyr_string 改成 mb_convert_encoding() 或 iconv(), ezmlm_hash 无替代业务上用不到, restore_include_path 改成 ini_restore(include_path), fgetss 改成 fgets() 加 strip_tags(), read_exif_data 改成 exif_read_data(), image2wbmp 改成 imagewbmp(), png2wbmp 改成 imagewbmp(), jpeg2wbmp 改成 imagewbmp(), ldap_sort 在 PHP 侧排序, ]; $root $argv[1] ?? .; if (!is_dir($root)) { fwrite(STDERR, 目录不存在: {$root}\n); exit(1); } $ignoreTokens [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT]; $methodTokens [T_OBJECT_OPERATOR, T_DOUBLE_COLON]; $hits []; $scanned 0; $iterator new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { if (!$file-isFile() || strtolower($file-getExtension()) ! php) { continue; } $path $file-getPathname(); $code file_get_contents($path); if ($code false) { continue; } $scanned; $tokens token_get_all($code); $count count($tokens); for ($i 0; $i $count; $i) { $token $tokens[$i]; if (!is_array($token) || $token[0] ! T_STRING) { continue; } $name strtolower($token[1]); if (!isset($removed[$name])) { continue; } // 跳过 $obj-each() / Foo::each() 这类方法调用只报全局函数 $prev $i - 1; while ($prev 0 is_array($tokens[$prev]) in_array($tokens[$prev][0], $ignoreTokens, true)) { $prev--; } if ($prev 0 is_array($tokens[$prev]) in_array($tokens[$prev][0], $methodTokens, true)) { continue; } $hits[] sprintf(%s:%d %s() - %s, $path, $token[2], $name, $removed[$name]); } } printf(已扫描 %d 个 PHP 文件\n, $scanned); if ($hits []) { echo 没有发现 PHP 8.0 已移除的函数调用。\n; } else { printf(发现 %d 处待处理\n, count($hits)); foreach ($hits as $hit) { echo , $hit, PHP_EOL; } }扫描只能覆盖名字还在、函数没了这一类。语法错误要靠php -l# 在当前 PHP 版本下逐个文件做语法检查 find ./src -name *.php -print0 | xargs -0 -n1 php -l | grep -v No syntax errors更进一步可以上自动化重构工具。Rector 能在 AST 层面改代码比正则替换安全得多PHPCompatibility 则是一个 PHP_CodeSniffer 的标准专门报告跨版本不兼容写法。两者配合用前者负责改后者负责查。?php declare(strict_types1); // rector.php —— 放在项目根目录 use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; return RectorConfig::configure() -withPaths([__DIR__ . /app, __DIR__ . /src, __DIR__ . /public]) -withSets([LevelSetList::UP_TO_PHP_80]);先干跑看改动范围确认没有误伤再真正落盘vendor/bin/rector process --dry-run vendor/bin/rector process常见坑点坑 1用正则批量替换each(把-each(也改了❌sed -i s/each(/foreach(/g把$collection-each(...)一起改坏✅ 用上一节的 token 扫描脚本定位或让 Rector 在 AST 层改坑 2each()换成foreach后数组指针语义变了❌ 原代码依赖each()只推进一格的行为直接换成foreach后整段逻辑跑飞✅ 明确区分要遍历全部用foreach和要顺序取一个用current()next()坑 3只跑了php -l以为语法过了就没事❌php -l全绿就上线结果运行时抛Call to undefined function✅php -l只能抓语法错误花括号下标、(unset)这类函数删除要另外扫坑 4把implode的参数当成随便哪个顺序都行❌implode($array, ,)在 7.4 只是弃用提示8.0 直接报ArgumentCountError✅ 统一写成implode(,, $array)坑 5以为strlen(null)在 8.0 就会报警告❌ 照着8.0 弃用清单去改strlen(null)改了半天没看到任何提示✅ 传 null 给不可为 null 的内部函数参数是PHP 8.1才弃用的。8.0 阶段先把上面那张删除表清干净坑 6漏了0 foo这类隐式类型转换❌ 代码里满是if ($x 0)升级后分支悄悄走错还不报错✅ 全面排查把跟字符串比较的地方换成PHP 8.0 新增的match用的也是严格比较坑 7用压错误升级后发现压不住了❌$data json_decode($str)之类的写法在大范围滥用✅ PHP 8.0 起不再抑制致命错误信息。改用显式判断加异常处理别依赖抑制符坑 8升级到 8.0 就顺手把 8.1 的改动一起做了❌ 一次性把代码改成 8.2 的写法结果目标服务器还是 8.0某些语法直接语法错误✅ 一次只跨一个小版本。确认php -v与目标一致再决定用哪一套语法总结类别典型代表报错形式排查手段函数被移除each()、create_function()、money_format()Call to undefined functiontoken 扫描脚本、PHPCompatibility语法被移除$s{0}、(unset)syntax errorphp -l逐个文件过行为升级为错误0 foo、abc 1、1 / 0结果静默改变或抛异常人工排查与算术运算仅弃用未移除assert()传字符串、ReflectionParameter::getClass()日志刷Deprecated可延后处理升级到 PHP 8.0 的落地顺序建议是先php -l清掉语法错误让项目能解析再用扫描脚本清掉已删除的函数让它能跑最后用error_reporting E_ALL跑一遍测试和主要流程处理行为变化带来的差异。前两步是机械劳动第三步才是真正需要看业务逻辑的地方。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →