上一篇写”十几行代码修复评论头像裂图”的时候,我盯着那行 str_replace 冒出两个疑问,顺手查了 PHP 官方文档,发现这两个问题恰好是很多初学者会卡住的地方。既然查都查了,索性单独写一篇讲清楚。
问题是这样两个:
一、如果字符串里同一个词出现了好几次,str_replace 能一次性全部替换掉吗?
二、如果字符串里同时出现了两个不同的词,能一次性都替换成同一个目标吗?

但不是”碰巧能”,而是这个函数本来就是这么设计的。PHP 官方文档里两句话把这事儿说死了:
看个最小的例子:
$text = "0.gravatar.com 和 0.gravatar.com 和 0.gravatar.com";
$new = str_replace('0.gravatar.com', 'cravatar.cn', $text, $count);
// $new = "cravatar.cn 和 cravatar.cn 和 cravatar.cn"
// $count = 3
三个全换掉了。这里还顺带用到了 str_replace 的第四个参数 $count:它会记录一共替换了几处。这个参数平时用得少,但调试时特别好用——不确定有没有替换成功,打印一下 $count 就知道了,比肉眼找快得多。
当第一个参数是数组、第二个参数是单个字符串时,意思就是”这些词我都要,全给我换成这个”:
$avatar = '<img src="https://0.gravatar.com/avatar/abc?s=96"
srcset="https://2.gravatar.com/avatar/abc?s=192 2x" />';
$avatar = str_replace(
array('0.gravatar.com', '2.gravatar.com'),
'cravatar.cn',
$avatar
);
处理方式是:按数组顺序,一个词一个词地扫描全文,每个词都替换它出现的所有位置。所以上面这段 HTML 里的两个地址,一个 0、一个 2,最后都会变成 cravatar.cn。
这一点很关键。WordPress 输出的头像 HTML,经常带着两个图片地址:
src:普通屏幕用的头像;srcset:高清屏(手机、Mac 这类 Retina 屏)用的 2 倍图。如果 str_replace 只替换第一个,结果就是:普通屏幕上头像正常,高清屏上依然裂图——修不干净,还留个隐蔽的坑。正是因为它替换全部出现的位置,两个地址才一起被换掉。
官方文档专门用了一整块篇幅警告这件事(Replacement order gotcha):数组元素是从前到后依次处理的,前面替换出来的结果,会参与后面的匹配。
具体到头像这个场景,假设哪天您想”省事”,往数组里加一个不带前缀的 gravatar.com,而且把它放在前面:
// 危险写法:笼统的排在前面
$bad = str_replace(
array('gravatar.com', 'www.gravatar.com'),
'cravatar.cn',
'https://www.gravatar.com/avatar/abc'
);
// 结果:https://www.cravatar.cn/avatar/abc ← www. 前缀残留,照样打不开
原因就是:www.gravatar.com 里本来就含 gravatar.com,第一轮就被换掉了,剩下个光秃秃的 www. 挂在前面。
正确的写法是把更具体、更长的放前面,笼统的放后面:
// 正确写法:具体的排在前面
$good = str_replace(
array('www.gravatar.com', 'gravatar.com'),
'cravatar.cn',
'https://www.gravatar.com/avatar/abc'
);
// 结果:https://cravatar.cn/avatar/abc ← 干净
顺便说一句,上一篇文章里那段头像代码是安全的:六个域名都是带前缀的完整域名,谁也不是谁的一部分,替换目标 cravatar.cn 里也不含任何搜索词,所以顺序怎么排都不会出问题。唯一的禁忌就是别往里加裸的 gravatar.com。
如果实在不想操心顺序,PHP 还提供了另一个函数 strtr()。它按”最长优先、全文只扫一遍“的方式工作,替换出来的内容不会被二次替换,天然没有上面那个坑:
$avatar = strtr($avatar, array(
'www.gravatar.com' => 'cravatar.cn',
'0.gravatar.com' => 'cravatar.cn',
'secure.gravatar.com' => 'cravatar.cn',
));
用哪个都行。str_replace 更常见、可读性更好;strtr 在搜索词互相包含时更稳。我的建议是:先搞清 str_replace 的顺序规则,需要时再换 strtr——知其然也知其所以然,比死记一个”安全写法”有用。
$count,打印出来看看替换了几处。结果是 0,说明压根没匹配上(多半是大小写或者拼写的问题);str_replace 是区分大小写的。想忽略大小写,用它的兄弟函数 str_ireplace()。这次的收获其实不在代码,而在方法:对自己写的每一行代码,都值得多问一句”如果输入不是我想的那样呢”。字符串里有两个地址怎么办、出现三次怎么办、搜索词互相包含怎么办——把这些问号一个个拉直,代码才真正站得住。