)
前言strstr()的定位是「查找子串的首次出现并返回从该处开始的剩余部分」。它属于 PHP 最早的字符串函数之一几乎每一本入门教程都会讲但正因为它简单误用的方式也特别多。一个典型的错误认识是把它当成「判断子串是否存在」的函数。str_contains()在 PHP 8.0 才加入在那之前的代码里确实常见if (strstr($s, $needle))这种写法——而这种写法本身是有 bug 的只要子串恰好出现在末尾且内容是0返回值就是字符串0在布尔上下文里为假判断方向完全反了。第二个误认是把strstr()和strpos()搞混。strpos()返回位置整数strstr()返回子串字符串两者的失败返回值恰好都是false但用错之后报出来的错完全不同。本文按官方手册讲清strstr()的签名与参数含义、它和stristr()/strrchr()/strpos()/str_contains()的分工以及多字节字符串下必须换函数的原因。一、函数签名与参数语义官方手册给出的签名是strstr(string $haystack, string $needle, bool $before_needle false): string | false参数顺序要记牢第一个是被搜索的字符串haystack第二个是要找的东西needle和str_replace($search, $replace, $subject)那种「被处理对象放最后」的函数正好相反。参数含义haystack输入字符串needle要搜索的字符串before_needle为true时返回needle之前的部分不含needle返回值找到了就返回从needle开始before_needle为true时是needle之前到字符串末尾的那一段找不到返回false。这一点直接决定了正确用法只有一种用恒等比较判断结果。?php // 适用于 PHP 8.0$email nameexample.com;$domain strstr($email, );var_dump($domain); // string(12) example.com$user strstr($email, , true);var_dump($user); // string(4) name关于needle参数有两处版本行为必须点明PHP 8.0.0 起允许needle为空字符串此时会返回整个haystack因为首次出现的位置是 0。这类边界行为不建议写进业务逻辑。PHP 7.3.0 起把整数当作needle的用法被废弃PHP 8.0.0 起不再支持。老代码里strstr($s, 65)的本意是「查找 ASCII 码 65 对应的字符A」现在的行为是按普通字符串处理相当于找65。正确写法是strstr($s, chr(65))或直接写strstr($s, A)。还有一点务必记住strstr()区分大小写。手册在本函数页面上专门写了一句提示——需要不区分大小写请用stristr()。二、和周边函数的分工PHP 里和「找子串」相关的函数不止一个选错不会报错只会静默地返回你不想要的东西函数返回内容大小写失败返回strstr()从 needle 起到末尾的子串敏感falsestristr()同上不区分大小写不敏感falsestrrchr()从最后一次出现该字符起到末尾的子串敏感falsestrpos()位置整数的字节偏移敏感falsestripos()位置不区分大小写不敏感falsestr_contains()PHP 8.0布尔值是否存在敏感无永远返回 boolstr_starts_with()/str_ends_with()PHP 8.0布尔值前缀/后缀判断敏感无两个容易忽略的细节strrchr()的needle只取第一个字符。手册写着「如果needle包含多个字符只会使用第一个字符」。所以strrchr(a.b.c, .c)找的是.返回.c。它是用来「取文件扩展名」「取 URL 最后一段」的不是通用的反向查找函数。str_contains()比strstr()更适合做判断。手册在strstr()页面明确推荐如果只是想知道 needle 是否出现在 haystack 中应该用更快、更省内存的str_contains()。?php // 适用于 PHP 8.0$log GET /index.php HTTP/1.1;// 判断用专用函数var_dump(str_contains($log, /index.php)); // bool(true)// 取值用 strstr()$path strstr($log, /);var_dump($path); // string(20) /index.php HTTP/1.1// 取「/」之前的部分HTTP 方法$method strstr($log, , true);var_dump($method); // string(3) GET三、实战示例1. 拆分key: value形式的配置行?php // 适用于 PHP 8.0declare(strict_types1);function parseConfigLine(string $line): ?array{$line trim($line);if ($line || str_starts_with($line, #)) {return null;}$key strstr($line, , true);if ($key false) { // 没有等号整行都是键return [$line ];}$name trim($key);$value trim(substr($line, strlen($key) 1)); // 跳过等号本身return [$name $value];}var_dump(parseConfigLine(timeout 30));var_dump(parseConfigLine(debug));2. 大小写不敏感地取后缀?php // 适用于 PHP 8.0function hasSuffix(string $name, string $suffix): bool{// stristr() 不区分大小写返回从后缀开始的部分$tail stristr($name, $suffix);// 必须判断 ! false否则 false 参与比较会静默出错return $tail ! false strcasecmp($tail, $suffix) 0;}var_dump(hasSuffix(Report.PDF, .pdf)); // bool(true)var_dump(hasSuffix(Report.zip, .pdf)); // bool(false)这里判断的是「结尾」而不是「出现过」所以额外用strcasecmp($tail, $suffix) 0确认取到的部分正好等于后缀。如果只需要判断结尾PHP 8.0 直接用str_ends_with($name, $suffix)更清楚需要忽略大小写时先用strtolower()归一。3. 遇到多字节字符串不要用strstr()截取strstr()按字节工作。对 UTF-8 的中文文本按字节截取可能把一个汉字切成半个产生乱码。mbstring 扩展提供了对应的多字节版本?php // 适用于 PHP 8.0需要 mbstring 扩展$text 订单编号: A1001;if (function_exists(mb_strstr)) {$label mb_strstr($text, :, true);var_dump($label); // string(12) 订单编号}// 判断位置用 mb_strpos()截取长度用 mb_substr()$pos mb_strpos($text, :);if ($pos ! false) {$rest mb_substr($text, $pos 1);var_dump(trim($rest)); // string(5) A1001}注意mb_strstr()需要显式指定编码或依赖mb_internal_encoding()的设置实际项目里最好显式传第四个参数编码名不要依赖全局状态。常见坑点❌ 用strstr()做存在性判断if (strstr($s, $needle))—— 当返回值恰好是字符串0例如strstr(x0, 0)时它在布尔上下文里为假判断失效。✅ PHP 8.0 用str_contains()8.0 以下用strpos($s, $needle) ! false。❌ 把strstr()当成strpos()的替代品拿返回值去和数字比较或做算术 —— 它返回的是字符串不是位置。✅ 要位置用strpos()/strrpos()要子串才用strstr()。❌ 用整数当needlestrstr($s, 65)以为在找字符A—— 该用法在 PHP 7.3.0 被废弃、PHP 8.0.0 起不再支持现在会在字符串里找65。✅strstr($s, chr(65))或直接写strstr($s, A)。❌ 以为strstr()不区分大小写 —— 它是区分大小写的strstr(Report.PDF, .pdf)返回false。✅ 用stristr()或者先把两边都归一成小写再比。❌ 用strstr($path, /)去取「目录部分」—— 返回的是从第一个斜杠到结尾的整段通常是整条路径。✅ 取目录用dirname($path)、pathinfo($path, PATHINFO_DIRNAME)或者取最后一段用strrchr($path, /)。❌ 用$result strstr($a, $b, true)取前缀然后直接拼接或比较没有处理needle不存在时返回的false——false被当成空字符串参与字符串运算错误被静默吞掉。✅ 先写if ($result false) { ... }分支。❌ 用str_ends_with()之外的老写法判断文件扩展名 —— 例如用strstr()判断「包含.pdf」就认定是 PDFreport.pdf.exe也会通过。✅str_ends_with(strtolower($name), .pdf)PHP 8.0并配合白名单校验。❌ 在多字节字符串上做strstr()并按字符数计算长度 ——strstr()/strpos()都是字节级的UTF-8 下一个汉字占 3 字节位置和长度都会算错截取还可能切出半个字符。✅ 位置用mb_strpos()、截取用mb_substr()、查找子串用mb_strstr()需要 mbstring 扩展。总结要点结论签名strstr(string $haystack, string $needle, bool $before_needle false)返回值从 needle 起到末尾的子串或 needle 之前的部分找不到返回false参数顺序被搜索的字符串在前要查找的字符串在后第四个参数before_needle true取 needle 之前的一段大小写区分大小写忽略大小写用stristr()判断存在性用str_contains()PHP 8.0不要用strstr()的返回值当真值整数 needlePHP 7.3.0 废弃、PHP 8.0.0 起不再支持用chr()显式转换多字节换成mb_strstr()/mb_strpos()/mb_substr()一句话总结strstr()只负责「取子串」判断存在性该用str_contains()、取位置该用strpos()只要记住它的失败值是false、比较一律用!再避开整数 needle 和多字节截取这两个历史陷阱用法就不会出问题。