在thinkphp中,编写模板时需要用到比较,例如,假设定义了一个模板变量 $msgcount 为整数,然后,如下书写模板代码:
{gt name="msgcount" value="0"}
<div class="hx-message-num"><span>{$msgcount|raw}</span></div>
{/gt}
则thinkphp 将模板编译(所谓编译就是翻译成php代码)后将产生如下php代码:
<?php if($msgcount > '0'): ?>
<div class="hx-message-num"><span><?php echo $msgcount; ?></span></div>
<?php endif; ?>
明显是将 $msgcount 与 '0' 这个字符串进行字符串比较,虽然目前的php版本本身能够将字符串转变为数字进行比较,但不能保证php的后续版本一定会这么做(例如php8.0以后),而且这种用比较也不是很优雅。
但如果将模板的写法写成如下:
{gt name="msgcount" value=":0"}
<div class="hx-message-num"><span>{$msgcount|raw}</span></div>
{/gt}
然后编译后产生的php代码如下:
<?php if($msgcount > 0): ?>
<div class="hx-message-num"><span><?php echo $msgcount; ?></span></div>
<?php endif; ?>
生成的比较语句为 if($msgcount > 0),为数值比较,这个显然符合预期。
原因何在,通过查看thinkphp处理模板的源代码,可以知道这个是将错就错,碰巧成功。
通过查看thinkphp的模板处理代码,如下:
/**
* compare标签解析
* 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq
* 格式: {compare name="" type="eq" value="" }content{/compare}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagCompare(array $tag, string $content): string
{
$name = $tag['name'];
$value = $tag['value'];
$type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型
$name = $this->autoBuildVar($name);
$flag = substr($value, 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($value);
} else {
$value = '\'' . $value . '\'';
}
switch ($type) {
case 'equal':
$type = 'eq';
break;
case 'notequal':
$type = 'neq';
break;
}
$type = $this->parseCondition(' ' . $type . ' ');
$parseStr = '<?php if(' . $name . ' ' . $type . ' ' . $value . '): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
该函数通过判断标签的 value,如果value 以 '$' 或者 ':' 开头,则调用 autoBuildVar($value); 进行处理,否则就翻译成字符串。在thinkphp模板手册中,以 ':' 开头 的值是当作函数来处理的,thinkphp的模板编译处理函数autoBuildVar 的代码如下:
/**
* 自动识别构建变量
* @access public
* @param string $name 变量描述
* @return string
*/
public function autoBuildVar(string &$name): string
{
$flag = substr($name, 0, 1);
if (':' == $flag) {
// 以:开头为函数调用,解析前去掉:
$name = substr($name, 1);
} elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) {
// XXX: 这句的写法可能还需要改进
// 常量不需要解析
if (defined($name)) {
return $name;
}
// 不以$开头并且也不是常量,自动补上$前缀
$name = '$' . $name;
}
$this->tpl->parseVar($name);
$this->tpl->parseVarFunction($name, false);
return $name;
}
thinkphp的 TagLib 处理以':' 开头的标识时去掉其前面的':' 。
所以碰巧用到了thinkphp的模板的一个缺陷,将错就错的达到了预期。