PHP7.3和7.2字符串功能有什么不同

前言

做版本评估时经常有人问:从 7.2 升到 7.3,字符串处理这块到底多了什么?把升级说明翻一遍,看到的净是 hrtime()、is_countable()、函数调用尾逗号这些条目,好像跟字符串一点关系都没有,于是判断"字符串功能没变",放心升级,结果上线当天出现语法错误导致的空白页。

真实情况有两点需要先纠正。第一,7.2 到 7.3 之间 PHP 没有新增任何字符串处理函数 。大家常说的 mb_ord()、mb_chr()、mb_scrub() 这三个多字节(multibyte)函数,是 PHP 7.2.0 加进来的,不是 7.3。第二,7.3 在字符串方向上真正的改动是 heredoc 与 nowdoc 的灵活语法(Flexible Heredoc and Nowdoc Syntax),它是一套解析规则而不是函数,写得不合规时报的是编译期语法错误(ParseError),症状比"函数不存在"更难定位。

本文先把"函数增量究竟发生在哪个版本"讲清楚,再拆开 7.3 的 heredoc 新解析规则,最后给出从 7.2 升到 7.3 时的自查点和几个真会踩的坑。

一、结论先行:字符串函数的增量发生在 7.2

下面这张表是这条时间线上真正该记住的东西。注意"与字符串是否相关"这一列------7.3 新增的东西里,语法层的 heredoc 才是重点。

特性 引入版本 类型 与字符串处理的关系
mb_ord() 7.2.0 函数 直接相关:字符转码点
mb_chr() 7.2.0 函数 直接相关:码点转字符
mb_scrub() 7.2.0 函数 直接相关:清洗非法字节序列
灵活 heredoc / nowdoc 语法 7.3.0 语法 直接相关:字符串字面量的写法与解析
大小写不敏感常量废弃 7.3.0 废弃 间接相关:define() 常量的名字比较
is_countable() 7.3.0 函数 无关(数组)
hrtime() 7.3.0 函数 无关(时间)

所以如果有人告诉你"7.3 加了一批字符串函数",那是记错了版本号。反过来,如果你在 7.2 上写 mb_ord($s) 报 Call to undefined function,原因不是"这个函数要 7.3",而是:要么你的 mbstring 扩展没启用,要么你实际跑的其实是 7.1 或更早的版本。

二、7.2 补上的三个多字节函数

2.1 mb_ord 与 mb_chr:码点和字符互转

在 7.2 之前,要拿一个 UTF-8 字符的码点(code point),标准做法是先转成 UTF-32BE 再用 unpack('N') 取整数,绕一大圈。7.2 直接给了两个函数:

php 复制代码
<?php

// 需要 PHP 7.2+ 且启用 mbstring 扩展

var_dump(mb_ord('A', 'UTF-8'));       // int(65)

var_dump(mb_ord('中', 'UTF-8'));      // int(20013)

var_dump(mb_chr(20013, 'UTF-8'));     // string(3) "中"



// 和 chr()/ord() 的区别:后两个只认单字节

var_dump(ord('中'));                  // int(228),只是第一个字节

var_dump(strlen('中'));               // int(3),字节数,不是字符数

下面这段是 7.2 之前的手工替代实现,如果你维护的代码要同时兼容 5.x 和 7.x,可以把它作为兜底:

php 复制代码
<?php

// 兼容 PHP 5.4 及以上;mb_convert_encoding + unpack 实现取码点

function legacy_mb_ord(string $s, string $encoding = 'UTF-8')

{

    $utf32 = mb_convert_encoding($s, 'UTF-32BE', $encoding);

    $arr = unpack('N', $utf32);

    return $arr === false ? false : $arr[1];

}



var_dump(legacy_mb_ord('中'));   // int(20013)

要留意一个语义差别:mb_ord() 在失败时返回 false,而手工实现的 unpack 版本在输入是空串时会因为长度不足直接报错,所以调用前该做长度判断就做。

2.2 mb_scrub:把非法字节替换掉

mb_scrub() 的用途是"洗"字符串:把目标编码里不合法的字节序列,替换成 mbstring 设定的替换字符(由 mb_substitute_character() 决定,默认是问号)。典型场景是从老库、老日志里读出来的混合编码文本,直接塞进 json_encode(),只要有一个坏字节,整个 json_encode() 就返回 false,接口直接吐 null。

php 复制代码
<?php

// 需要 PHP 7.2+

$raw = "正常\xB2\xE2\xCA\xD4";   // 后面是 GBK 字节,在 UTF-8 里非法



$clean = mb_scrub($raw, 'UTF-8');

var_dump($clean);                       // 坏字节变成替换字符



$json = json_encode(['msg' => $clean], JSON_UNESCAPED_UNICODE);

var_dump($json);                        // 这次能编码成功



// 检查 mb_substitute_character 的当前设置

var_dump(mb_substitute_character());    // 默认 int(63),即问号

这里的关键认知是:mb_scrub() 是破坏性操作,它不报错、不抛异常,只是把看不懂的字节换掉。所以它只适合"先保证链路能跑通、后面再治理数据"的场景,不能当数据校验用。

三、7.3 真正的字符串改动:heredoc / nowdoc 灵活语法

3.1 旧规则有多死板

在 7.3 之前,heredoc 的结束标记(closing marker)必须顶格写在第 0 列,而且标记后面除了分号或换行,不能有别的东西。这导致两个很难受的写法限制:

php 复制代码
<?php

// 7.2 及以前:结束标记必须顶格

// 想缩进对齐?不行,这样写是语法错误

// $sql = <<<SQL

//         SELECT * FROM users

//         SQL;

第二种限制更疼:因为结束标记后必须换行,你没法把 heredoc 直接当函数参数写。

3.2 7.3 改了哪两条

PHP 7.3 的灵活 heredoc 语法放开了两条限制:

  1. 结束标记允许缩进,缩进量会从 heredoc 的每一行内容里剥掉。
  2. 结束标记后面不必再换行,可以直接跟逗号、分号、右括号等后续记号。
php 复制代码
<?php

// 需要 PHP 7.3+



// 1. 结束标记可以缩进,内容里的对应缩进会被剥掉

function buildSql(): string

{

    $sql = <<<SQL

        SELECT id, name

        FROM users

        WHERE status = 1

        SQL;

    return $sql;

}

echo buildSql();

// 输出:

// SELECT id, name

// FROM users

// WHERE status = 1



// 2. 结束标记后可以直接接逗号和右括号

function logLine(string $level, string $msg): void

{

    echo "[{$level}] {$msg}\n";

}



logLine('info', <<<TEXT

    第一行

    第二行

    TEXT);

3.3 代价:缩进是"剥掉"的,不是"忽略"的

这一条是升级时最容易出事的地方。结束标记的缩进量,会从每一行内容里原样减掉。也就是说,如果你的 heredoc 内容里本来就有有意义的行首空格(比如要拼一段 YAML、一段固定宽度文本、或者代码片段),剥掉缩进后内容就变了:

php 复制代码
<?php

// 需要 PHP 7.3+

// 内容本意是每行前面要留 4 个空格,但结束标记缩进了 4 格,这 4 格被当成公共缩进剥掉了

$yaml = <<<YAML

    server:

      port: 8080

    YAML;



var_dump($yaml);

// 输出变成:

// "server:\n  port: 8080"

// 第一层缩进没了,嵌套层级看起来就错了

要看清楚"剥掉了多少",一个办法是把结果 var_dump 出来看转义后的 \n 和空格,不要靠肉眼看终端输出。

3.4 新规则下的两类编译期错误

7.3 之后,下面两种情况会直接是 ParseError,脚本一行都跑不了:

  • 内容里某一行的缩进比结束标记还浅。解析器不知道要剥掉几格,只能报错。
  • 同一段 heredoc 里混用制表符和空格做缩进。缩进量没法统一比较,同样报错。

排查建议:全项目统一用空格缩进,并在编辑器里打开不可见字符显示。这类错误在本地开发时通常会被提前发现,怕的是从别处拷来的代码文件在保存时被自动转换了缩进。

四、升级前值得顺手检查的两件事

第一件是 mbstring.func_overload。这个 ini 设置在 PHP 7.2 里被标记为废弃(deprecated),到 PHP 8.0 被彻底移除。它一旦被打开,strlen()、substr()、strpos() 这些单字节函数会被替换成 mb_* 版本,于是同一个字符串在开着它的机器上"长度是 6",在没开的机器上"长度是 18"。跨版本迁移时这类环境差异比代码差异更难查,建议无论升不升级都先把它关掉,需要字符数就显式写 mb_strlen()。

第二件是大小写不敏感常量。define('FOO', 1, true) 这种第三个参数为真的写法在 PHP 7.3 里被废弃,PHP 8.0 移除了该参数。检索引擎里搜一遍带第三个参数的 define( 调用,成本很低。

代码实战:一份自检脚本

保存成 check_env.php,在开发机和生产预发环境各跑一遍,可以直接看出两个版本环境到底差在哪:

php 复制代码
<?php

// 兼容 PHP 7.2 与 7.3,用于对比两个环境的实际能力

$report = [];



$report['PHP 版本'] = PHP_VERSION;

$report['mbstring 已加载'] = extension_loaded('mbstring') ? 'yes' : 'no';

$report['func_overload'] = (string) ini_get('mbstring.func_overload');



$s = '中文abc';



// 字节长度与字符长度的差距,是最直观的"环境是否被 func_overload 改过"的信号

$report['strlen'] = strlen($s);

$report['mb_strlen'] = mb_strlen($s, 'UTF-8');



// 7.2+ 才有的函数

$report['mb_ord'] = function_exists('mb_ord') ? mb_ord('中', 'UTF-8') : '不存在(7.2 以下)';

$report['mb_chr'] = function_exists('mb_chr') ? mb_chr(20013, 'UTF-8') : '不存在(7.2 以下)';

$report['mb_scrub'] = function_exists('mb_scrub') ? mb_scrub("ok\xB2\xE2", 'UTF-8') : '不存在(7.2 以下)';



foreach ($report as $k => $v) {

    printf("%-16s : %s\n", $k, var_export($v, true));

}

在两个环境里分别执行 php check_env.php,把两份输出并排放在一起,差异点一目了然。比如 mb_ord 这一行如果显示"不存在",说明那台机器上的 PHP 低于 7.2,谈"7.3 的新字符串函数"就没有意义了。

常见坑点

1. 把 7.2 引入的函数当成 7.3 的新特性

  • ❌ 升级文档里写"7.3 新增 mb_ord/mb_chr/mb_scrub,7.2 用不了"
  • ✅ 这三个函数是 PHP 7.2.0 引入的。7.3 在函数层面没有字符串新增,改的是 heredoc 语法

2. 以为 heredoc 缩进只是"好看",不影响内容

  • ❌ 缩进后直接上线,不检查字符串内容是否被剥掉了行首空格
  • ✅ 结束标记的缩进量会从每一行剥掉,拼 YAML、固定宽度文本、代码片段时要 var_dump 确认实际内容

3. 内容行缩进比结束标记浅

  • ❌ <<<SQL 的内容从第 0 列开始,结束标记却缩进了 8 格
  • ✅ 所有内容行的缩进必须大于等于结束标记的缩进,否则是编译期 ParseError

4. 同一段 heredoc 里制表符和空格混用

  • ❌ 编辑器自动缩进用 Tab,手写的内容行用空格
  • ✅ 全项目统一空格缩进,并打开不可见字符显示检查;混用会直接报错

5. 用 mb_scrub 当数据校验

  • ❌ $clean = mb_scrub($input, 'UTF-8'); 然后认为输入合法了
  • ✅ mb_scrub() 静默替换坏字节,不报错。要用 mb_check_encoding($input, 'UTF-8') 先判断合法性,再决定拒绝还是清洗

6. 升级后 strlen 的语义悄悄变了

  • ❌ 换机器后没注意 mbstring.func_overload,substr() 切中文的边界变了却没改代码
  • ✅ 关掉 mbstring.func_overload(7.2 起已废弃,8.0 移除),需要按字符处理时显式写 mb_substr()、mb_strlen()

7. 把 mb_ord 的结果当成字节序列用

  • ❌ 拿到 mb_ord('中') 得到 20013,就直接塞进需要原始字节的地方
  • ✅ 它返回的是 Unicode 码点,不是 UTF-8 编码后的字节。要字节序列用 bin2hex('中') 或编码转换

8. 拿 7.1 的环境当 7.2 验证

  • ❌ 在老机器上验证"7.2 的函数能不能用",报未定义函数就以为版本说明写错了
  • ✅ 先跑一遍上面的自检脚本确认 PHP_VERSION,再谈特性对应关系

总结

问题 答案
7.3 新增了哪些字符串函数 没有新增。mb_ord()、mb_chr()、mb_scrub() 是 7.2.0 引入的
7.3 在字符串上的真实改动 heredoc / nowdoc 灵活语法:结束标记可缩进、后面可接其他记号
灵活语法的副作用 结束标记的缩进会被逐行剥掉,有意义的行首空格会消失
什么时候会报语法错误 内容行缩进比结束标记浅,或制表符与空格混用
跨版本还要查什么 mbstring.func_overload(7.2 废弃、8.0 移除)、大小写不敏感常量(7.3 废弃)

一句话概括:7.2 到 7.3 在函数 上几乎没有字符串层面的增量,真正的变化在语法 上,而语法变化带来的风险恰恰更大------函数缺失会立刻报错,缩进被剥掉却只会安静地改变字符串内容。升级前把 heredoc 里的每一段多行文本 var_dump 一遍,比读十遍升级说明都有用。

相关推荐
m0_380743872 小时前
PHP 8.4的新语法怎么用才规范
开发语言·php
阿狗童鞋2 小时前
Java并发编程实战指南
java·开发语言
phltxy2 小时前
C 语言自定义类型:结构体、位段、枚举与联合体
c语言·开发语言
Sarvartha3 小时前
对象创建与引用
java·开发语言
hasty3 小时前
限制写了却没生效:OpenTelemetry Go 的 Unicode 截断边界
开发语言·后端·golang
编码者卢布4 小时前
【App Service 】WebJobs 多实例实验:谁在运行,什么时候运行?
开发语言·python
kaixin_learn_qt_ing4 小时前
Qt抽屉窗体实现demo
开发语言·qt
quantdash_cc4 小时前
Python 获取实时行情后如何进行批量筛选?从全市场快照到策略候选池
开发语言·python·数据分析·量化交易·股票数据·quantdash
论文复现现场4 小时前
论文复现看到“RTX 3090 or higher”:显存、CUDA、batch size 与 OOM 怎么判断?
开发语言·pytorch·batch·cuda·rtx3090