为描述方便,我们简化下问题。
{assign var="star" value="胡哥;吴秀波;王宝强;三小只"}
{$star|regex_replace:'/;/':'/'}
在smarty模板中,将“;”(半角分号)替换为“/”。在看这段代码时,第一反应是用replace替代regex_replace,效率会高些。于是动手改了一行代码:
{assign var="star" value="胡哥;吴秀波;王宝强;三小只"}
{$star|replace:';':'/'}
测试无误,上线!
上线后问题来了,线上环境中的”;”居然没有被替换为”/”!无奈回滚。
smarty手册说到:replace等同与php函数的str_replace。所以首先怀疑是php版本问题,但一个replace,真会和php版本有关系么?于是分别在两个环境上直接尝试用php的str_replace做上文的字符替换,都没有问题。
看来smarty的replace实现并不是直接调用了php的str_replace,只能读smarty源码定位问题了。
replace的实现位于Smarty/plugins/modifier.replace.php
function smarty_modifier_replace($string, $search, $replace)
{
if (Smarty::$_MBSTRING) {
require_once(SMARTY_PLUGINS_DIR . 'shared.mb_str_replace.php');
return smarty_mb_str_replace($search, $replace, $string);
}
return str_replace($search, $replace, $string);
}
其中Smarty :: $_MBSTRING在./Smarty.class.php中定义
define('SMARTY_MBSTRING', function_exists('mb_split'))
逻辑很清晰了,当安装了mbstring扩展时,使用smarty_mb_str_replace进行替换,否则用php的str_replace进行替换(是谁说equivalent to the PHP’s str_replace() function来着…可以枪毙一会儿)。
我的php有mbstring扩展,只能继续跟进了。 smarty_mb_str_replace核心逻辑可以简化如下:
function smarty_mb_str_replace($search, $replace, $subject)
{
$parts = mb_split($search, $subject);
$subject = implode($replace, $parts);
return $subject;
}
先用待替换字符切分源串,再用目标字符拼接得到结果串。不得不说这个实现思路有点…好吧,吐槽的事暂时放放,先追问题。 debug发现,问题出在mb_split,在线上环境(出问题的环境)中,此处我们得到的$parts结果为
array(1) { [0]=> string(36) "胡哥;吴秀波;王宝强;三小只" }
字串没有被切为预期的四部分。what’s wrong!
受php手册mb_split例子的启发(还是php手册靠谱),想到可能是编码问题导致。在问题环境测试
echo mb_internal_encoding();
echo mb_regex_encoding();
得到的结果居然是EUC-JP!一个日文字符集。我堂堂天朝公司的线上php版本居然默认字符集是日文…好在哥不是反日愤青,不然必格盘而后快。
知道问题所在就好解决了。 - 方法1:在php执行smarty前设置
mb_regex_encoding('UTF-8');
mbstring.internal_encoding = UTF-8
然后重启php-fpm让配置生效。
继续看smarty源码,regex_replace最终是使用php的preg_replace实现。介于replace的无语实现方法,二者哪个快还真不一定,实测下吧。我们每次测试者渲染模板1000次,测5次取均值,实验结果如下:
modifier | 耗时 |
---|---|
regex_replace | 0.183s |
replace | 0.191s |
regex_replace胜出了!
直接用php的str_replace,自己实现一个modifier会怎么样呢? 采用上面同样的测试方法,得到的结果是0.179s,比regex_replace只是略有提高。
综合考虑,regex_replace不依赖环境,不用额外代码,速度也还好,性价比最高。