ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PHP7.0字符串进阶技巧有哪些

PHP7.0字符串进阶技巧有哪些

前言


PHP 7.0 发布于 2015 年 12 月,它不是一个"字符串版本",但恰恰在字符串处理上带来了几个影响深远的变化:一个是新增了 Unicode 码点转义\u{},另一个是把一大批古老的字符串函数(ereg、split、mysql_*)直接移除。前者常被忽略,后者是老项目迁移时最痛的一刀。


典型症状有三类:升级到 7.0 后老代码大面积报Call to undefined function ereg();在 7.0 环境里照着新文章写str_contains(),同样报未定义函数(那是 PHP 8.0 才有的);用strlen()统计中文长度,得到的数字永远比实际字数大两倍。


本文梳理 PHP 7.0 上真正可用、且值得用的字符串技巧:码点转义、随机令牌、批量正则回调、多字节处理的正确姿势、??与字符串下标,以及declare(strict_types=1)对字符串参数的副作用。文中的示例最低版本 PHP 7.0,可直接运行。


一、\u{}码点转义:7.0 新增的字符串能力


PHP 7.0 之前,想在字符串里写一个 Unicode 码点,只能先算出 UTF-8 字节再用\x拼,或者干脆把字符复制进去。7.0 加入了\u{十六进制}语法:


$hello = "\u{4F60}\u{597D}"; // 你好
$emoji = "\u{1F600}";

两条必须记住的规则:只能用在双引号字符串和 heredoc 里(单引号和 nowdoc 中会原样输出\u{4F60}这七个字符);大括号是必须的,写你不会被识别。它和json_decode处理的\uXXXX(固定四位、不需要大括号)是两套不同语法,别混用。


二、7.0 新增的几件趁手工具


除了码点转义,7.0 还给字符串相关任务补了几块短板:




函数 / 语法用途备注



random_bytes()/random_int()生成密码学安全的随机字节 / 整数7.0 新增,替代mt_rand()做令牌

preg_replace_callback_array()一次配置多组"正则 => 回调"7.0 新增,替代多个preg_replace_callback

??空合并运算符$conf['k'] ?? 'default'7.0 新增,对未定义下标不报 Notice

<=>太空船运算符排序比较7.0 新增,常用于usort

unserialize($s, ['allowed_classes' => false])限制反序列化可实例化的类7.0 新增第二参数

intdiv()整除7.0 新增,与字符串无关但常一起用



要特别提醒两个不属于7.0 的常见误传:hash_equals()和password_hash()分别是 5.6 和 5.5 就有的,不是 7.0 新特性;而str_contains()/str_starts_with()/str_ends_with()要等到PHP 8.0,在 7.0 上必须用strpos()/strncmp()/substr_compare()手工实现。


三、多字节字符串:7.0 上的正确写法


PHP 的字符串是字节数组,strlen()数的是字节数。"你好"是 6 字节,strlen()返回 6 而不是 2。7.0 上处理 UTF-8 字符串的正确工具是mb_*系列与带/u修饰符的 PCRE:



  • 取长度:mb_strlen($s, 'UTF-8');

  • 截取:mb_substr($s, 0, 10, 'UTF-8');

  • 拆成单字符数组:不要用mb_str_split(),它要 PHP 7.4 才有。7.0 上的写法是preg_split('//u', $s, -1, PREG_SPLIT_NO_EMPTY);

  • 大小写:mb_strtolower()/mb_strtoupper(),注意它们不做完整的区域化折叠(如德语 ß);

  • 正则:给模式加/u,否则.会按字节匹配,中文会被拆成半个字符。


四、完整可运行示例


<?php
// 最低版本:PHP 7.0
declare(strict_types=1);

// 1) 码点转义:只能写在双引号或 heredoc 中
$greeting = "\u{4F60}\u{597D}";
$emoji = "\u{1F600}";
echo $greeting, $emoji, PHP_EOL; // 你好😀

// 2) 生成令牌:不要再自己拼 rand()
$token = bin2hex(random_bytes(32)); // 64 个十六进制字符
echo strlen($token), PHP_EOL; // 64

// 3) 一次配置多条替换规则
$text = '联系方式:138-0000-0001,邮箱 ada@example.com';
$masked = preg_replace_callback_array([
'/\b(\d{3})-(\d{4})-(\d{4})\b/' => static function (array $m): string {
return $m[1] . '-****-' . $m[3];
},
'/\b([\w.]+)@([\w.]+)\b/' => static function (array $m): string {
return substr($m[1], 0, 1) . '***@' . $m[2];
},
], $text);
echo $masked, PHP_EOL; // 联系方式:138-****-0001,邮箱 a***@example.com

// 4) 多字节:长度、截取、拆字符
$cn = '你好世界';
echo strlen($cn), PHP_EOL; // 12(字节)
echo mb_strlen($cn, 'UTF-8'), PHP_EOL; // 4(字符)
echo mb_substr($cn, 1, 2, 'UTF-8'), PHP_EOL; // 好世

// mb_str_split() 要 PHP 7.4,7.0 用 preg_split
$chars = preg_split('//u', $cn, -1, PREG_SPLIT_NO_EMPTY);
echo count($chars), PHP_EOL; // 4
echo implode('/', $chars), PHP_EOL; // 你/好/世/界

// 5) ?? 让配置读取不再报 Notice
$conf = [];
$env = $conf['env'] ?? 'production';
$port = (int) ($conf['port'] ?? 8080);
echo $env, ':', $port, PHP_EOL; // production:8080

// 6) 限制反序列化可实例化的类
$payload = serialize(new stdClass());
$restored = unserialize($payload, ['allowed_classes' => false]);
var_dump($restored instanceof __PHP_Incomplete_Class); // bool(true)

// 7) 8.0 才有的函数在 7.0 上必须手工替代
$haystack = 'php-7.0-string-tips';
$needle = 'string';
var_dump(strpos($haystack, $needle) !== false); // bool(true)
var_dump(strncmp($haystack, 'php', 3) === 0); // bool(true)

这段代码覆盖了 7.0 上最实用的几个点。注意第 7 项:str_contains()在 7.0 上会直接Fatal error,而strpos()的返回值在"匹配位置为 0"时是0,用if (strpos(...))判断会把"开头就匹配"误判为没找到——这是最经典的坑,必须用!== false。


常见坑点


1. 在 7.0 上调用 8.0 才有的字符串函数


❌str_contains($h, $n)/str_starts_with($h, $n)——Call to undefined function,整站 500。抄来的代码片段常常不标注版本。 ✅ 7.0 写法:strpos($h, $n) !== false、strncmp($h, $n, strlen($n)) === 0、substr_compare($h, $n, -strlen($n)) === 0。


2. 用strpos()的返回值直接做布尔判断


❌if (strpos($h, $n)) { ... }——匹配位置是 0 时返回0,判断为假,于是"以关键词开头的记录全部漏掉"。 ✅ 一律!== false。


3. 在 7.0 上用mb_str_split()


❌ 那是 PHP 7.4 才加入的函数,7.0/7.1/7.2/7.3 都报未定义。 ✅preg_split('//u', $s, -1, PREG_SPLIT_NO_EMPTY);记得加/u,空字符串会返回空数组。


4. 还在用 7.0 已经移除的老函数


❌ereg('^[0-9]+$', $s)、split(',', $s)、mysql_query()——PHP 7.0 中这些已被删除,不是"弃用",而是彻底不存在。 ✅preg_match('/^[0-9]+$/D', $s)、explode(',', $s)、mysqli/PDO。


5. 用mt_rand()生成密码重置令牌


❌md5(mt_rand())——mt_rand是可预测的伪随机数,令牌可被枚举。 ✅bin2hex(random_bytes(32))(7.0 新增),要用整型随机数则random_int(1, 100)。


6. 用strlen()判断字符数


❌if (strlen($nickname) > 10)想限制"十个字",中文昵称三个字就被拒绝。 ✅mb_strlen($nickname, 'UTF-8')。反过来,做存储长度校验(如VARCHAR(255))时要用strlen()数字节。


7. 在单引号里写\u{}


❌'\u{4F60}'——单引号字符串不做转义解析,页面上直接显示出这七个字符。 ✅ 用双引号或 heredoc;同时注意双引号里的$需要转义。


8.htmlspecialchars()不传编码


❌htmlspecialchars($input)——依赖default_charset与默认 flags,输出编码不确定;另外要记住 PHP 8.1 起默认 flags 变成了ENT_QUOTES | ENT_SUBSTITUTE,7.0 上默认是ENT_COMPAT(不转义单引号)。 ✅ 在 7.0 上显式写全:htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')。


总结




技巧引入版本关键点



\u{}码点转义PHP 7.0只能在双引号 / heredoc 中

random_bytes()/random_int()PHP 7.0替代mt_rand生成令牌

preg_replace_callback_array()PHP 7.0一组规则一次替换

??空合并PHP 7.0读数组下标不再报 Notice

preg_split('//u', ...)拆字符一直可用7.4 之前没有mb_str_split()

str_contains()/str_starts_with()PHP 8.07.0 上必须用strpos/strncmp替代

ereg/split/mysql_*7.0 已移除迁移时最容易集中爆发



在 PHP 7.0 上写字符串代码,核心就两句话:用mb_*和/u处理多字节,用!== false处理查找。把版本边界记清楚(\u{}与random_bytes是 7.0 的、mb_str_split是 7.4 的、str_contains是 8.0 的),就不会再被"函数不存在"和"中文长度不对"这两类问题反复绊住。




RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进