PHP8.0字符串查找怎么更精准
前言日常业务代码里「这段字符串里有没有某个内容」几乎是最常见的判断之一。很多人沿用很老的写法strpos、strstr、substr_compare再配上一堆! false。这些函数本身没有问题问题出在它们的返回值语义上——strpos()返回的是位置或者false而位置0在布尔上下文里是假值strstr()返回的是子串或者false你只想判断存在性却拿到了一大段字符串。典型症状是这样的本地用「abcdef」这种子串在中间的例子测得好好的上线后遇到以关键字开头的字符串判断突然失效程序走进了错误分支或者反过来中文内容被字节函数切过之后得到半截乱码后续匹配永远不成立日志里连个报错都没有。还有一种更隐蔽的strpos返回0被写进if里代码「有时对有时错」排查半小时才发现是0与false的区别。PHP 8.0 引入了三个语义清晰的函数str_contains()、str_starts_with()、str_ends_with()。它们只回答「是 / 不是」让代码意图和返回值对得上。需要点明的是这三个函数确实是PHP 8.0引入的标题里的版号与官方事实一致本文按 8.0 讲同时在最后给出低版本可用的等价写法。本文先讲清旧函数的坑在哪里再给出按语义挑选 API 的对照办法最后是一个可以直接保存成.php运行、自带断言自检的完整示例。一、老函数的根因返回值携带了「位置」而不是「结论」strpos()的原型是strpos(string $haystack, string $needle, int $offset 0): int|false。它返回首个匹配的字节偏移未命中返回false。用布尔判断时0和false会同时变成假值这是所有strpos事故的根源。strstr()的原型是strstr(string $haystack, string $needle, bool $before_needle false): string|false返回从匹配处开始的剩余字符串。用它做判断等于把一个可能很长的字符串搬进内存纯粹浪费。substr_compare()可以用来做后缀比较但参数是三个位置加一个长度读代码的人得在脑子里算一遍下标。这些函数的共同点是返回值类型不等于判断结论必须由调用者二次解释解释错了就出 bug。?php // 反例三种看起来对的写法 $s php8.0; var_dump((bool) strpos($s, php)); // false —— 明明匹配了 var_dump(strstr($s, php)); // string(5) php8.0 —— 拿到整串判断要再加工 var_dump(substr($s, -3) 8.0); // true但下标 -3 是硬编码的魔法数二、PHP 8.0 三个新函数返回值就是结论函数原型PHP 8.0返回语义str_containsstr_contains(string $haystack, string $needle): booltrue / false包含str_starts_withstr_starts_with(string $haystack, string $needle): booltrue / false以……开头str_ends_withstr_ends_with(string $haystack, string $needle): booltrue / false以……结尾三个函数都是大小写敏感的字节级比较内部就是一次线性扫描不会额外复制字符串。返回值直接是bool所以if (str_contains(...))就是最终结论不存在0与false的歧义。需要注意PHP 8.0 并没有同时提供「不区分大小写」的版本不存在str_icontains这类函数。不区分大小写的场景要回到stripos、strncasecmp或者多字节函数。?php $ua Mozilla/5.0 (Windows NT 10.0); if (str_starts_with($ua, Mozilla/)) { echo 浏览器 UA\n; } if (str_contains($ua, Windows)) { echo Windows 平台\n; }三、按语义挑 API才是「更精准」「精准」分四层位置层命中在哪、结论层有没有、结构层是不是一个完整词、是不是纯数字、编码层按字节还是按字符。逐层挑对函数代码自然准。你要问的问题推荐 API注意事项有没有这个子串str_contains空针返回 true见坑点是不是以它开头 / 结尾str_starts_with/str_ends_with针比原串长返回 false不会报错出现在第几字节strpos/strrpos必须和false全等比较忽略大小写找位置stripos与false全等比较不要与0混用中文、多字节按字符找mb_strpos必须先确定mb_internal_encoding建议显式传编码必须是独立单词preg_match加\b词边界记得加u修正符处理 UTF-8整串是不是纯数字 / 十六进制ctype_digit/ctype_xdigit空串和null都要先挡掉只要比较前 N 个字节strncmp返回差值而非布尔仍要和0比较str_contains传入的 needle 为空字符串时返回truestr_starts_with和str_ends_with同样如此。这是因为「空串是所有字符串的子串」在数学上成立但在业务里几乎一定是参数没准备好。所以在接收外部输入时先挡空值是个好习惯。四、完整可运行示例下面这个脚本需要PHP 8.0 或更高。它把常见的判断场景都跑一遍并用自定义的断言函数在失败时立刻退出可以直接保存为check.php后执行php check.php看到输出。?php declare(strict_types1); // 简易断言失败即抛出便于直接观察结果 function check(bool $ok, string $label): void { echo ($ok ? [OK] : [FAIL] ) . $label . \n; if (!$ok) { throw new RuntimeException(断言失败: . $label); } } $logLine GET /api/user?id42 200; // 1. 结论层直接判断包含关系 check(str_contains($logLine, /api/user), 包含 /api/user); check(!str_contains($logLine, /api/order), 不包含 /api/order); // 2. 前缀 / 后缀老写法要靠 substr 猜长度这里直接表达 check(str_starts_with($logLine, GET ), 以 GET 开头); check(str_ends_with($logLine, 200), 以 200 结尾); check(!str_ends_with($logLine, 404), 不以 404 结尾); // 3. 空针的行为三种函数都返回 true业务里通常要主动拦截 check(str_contains($logLine, ) true, 空针返回 true需要自己挡); check(str_starts_with(, ) true, 双空串也为 true); // 4. 针比原串长不会报错直接 false check(str_ends_with(ab, abcdef) false, 针过长返回 false); // 5. 位置层必须与 false 全等比较 $pos strpos($logLine, 43); check($pos false, 未命中时 strpos 返回 false); check(strpos($logLine, GET) 0, 开头命中返回 0); // 6. 不区分大小写8.0 没有 str_icontains用 stripos check(stripos($logLine, get ) 0, stripos 忽略大小写命中开头); // 7. 多字节按字符定位 $title PHP 中文标题; $charPos mb_strpos($title, 中, 0, UTF-8); $bytePos strpos($title, 中); check($charPos 4, mb_strpos 按字符计数); check($bytePos ! $charPos, strpos 按字节计数结果不同); // 8. 结构层整串必须是纯数字 $id 0042; check(ctype_digit($id), 整串为数字); check(!ctype_digit(42a), 含字母不算纯数字); // 9. 结构层词边界匹配避免 order 命中 order_detail check( preg_match(/\border\b/u, select * from order_detail) 0, 词边界让 order 不命中 order_detail ); check( preg_match(/\border\b/u, select * from order) 1, 词边界命中独立单词 order ); echo 全部检查通过\n;五、低版本环境的等价写法如果项目暂时升不到 8.0可以定义一个兜底实现代码里照样用新函数名升级时删掉这段即可?php if (!function_exists(str_contains)) { function str_contains(string $haystack, string $needle): bool { return $needle || strpos($haystack, $needle) ! false; } } if (!function_exists(str_starts_with)) { function str_starts_with(string $haystack, string $needle): bool { return strncmp($haystack, $needle, strlen($needle)) 0; } } if (!function_exists(str_ends_with)) { function str_ends_with(string $haystack, string $needle): bool { return $needle || substr($haystack, -strlen($needle)) $needle; } }注意str_starts_with的兜底里用strncmp而不是substr当 needle 比 haystack 长时substr会返回false而false $needle恰好也是假结果虽然对但多绕了一圈。strncmp的语义更直接。常见坑点❌if (strpos($haystack, $needle))✅if (strpos($haystack, $needle) ! false)命中位置为 0 时前者判为假这是历史上出现频率最高的字符串 bug。❌if (str_contains($s, $needle))直接用在用户输入上 ✅ 先判$needle ! 空针永远返回 true。搜索词来自表单且用户没填时逻辑会静默地「匹配一切」。❌strtolower($a) strtolower($b)处理中文或带重音的文本 ✅mb_strtolower($a, UTF-8)字节级转换会破坏多字节字符产生乱码后比较必然不等。❌ 用preg_match(/^v1.0/, $ver)判断固定前缀 ✅str_starts_with($ver, v1.0)正则里的.是通配符v1x0、v1_0都会被匹配上属于典型的「看起来更灵活其实更不精准」。❌if (str_starts_with($h, $n) 1)✅if (str_starts_with($h, $n))返回的是布尔值用 1比较恒为假而且开declare(strict_types1)时也不会报错非常难发现。❌ 先用strpos找位置、再用str_contains判存在 ✅ 一次strpos后全等比较两次线性扫描长文本上是白白的开销。❌str_ends_with($path, .tar.gz)寄希望于它做后缀「列表」匹配 ✅ 逐个调用或先strtolower归一它只比较一个固定串不会把逗号分隔的多个后缀当选项。❌ 用strstr的返回值做判断 ✅str_containsstrstr会把匹配后的整段内容复制出来只判存在时纯属浪费内存。总结判断需求推荐函数返回类型关键提醒包含子串str_containsbool空针返回 true前缀 / 后缀str_starts_with/str_ends_withbool针更长返回 false命中位置strpos/striposint 或 false必须全等比较 false多字节定位mb_strposint 或 false显式传编码独立单词preg_match加词边界int需要u修正符整串数字ctype_digitbool先挡空串与 null字符串查找要「精准」关键不是换一个更快的函数而是让函数的返回值语义和你的判断意图对齐。PHP 8.0 的str_contains、str_starts_with、str_ends_with把「结论」直接作为返回值消除了0与false的歧义剩下的精准度问题——大小写、编码、词边界——则要靠stripos、mb_*系列和带修正符的正则分层解决。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →