模版

变量输出

数据变量可以通过定界符(双大括号)包裹来输出显示。

如:

<p>{{name}}</p>

编译后:

<p><?php echo htmlspecialchars($name);?></p>

数据变量由视图View的相关方法传递设置。

定界符可以通过配置项text_delimiter_sign自定义。

转义

模版引擎默认开启自动转义,输出的内容使用htmlspecialchars函数处理,这样可以有效的防止xss攻击。

不过在有些使用场景下(如CMS类应用,内容通常是第一方生产的)并不需要转义,这种情况下可以通过设置配置'auto_escape_text' => false来关闭自动转义。

除了设置自动转义外,用户也可以手动强制输出转义或不转义。

在右定界符后使用:符号强制输出转义

{{:name}}

在右定界符后使用!符号强制输出不转义

{{!name}}

编译后:

<?php echo $name;?>

为了简化示例,后面的示例设为默认不转义,省略htmlspecialchars

数组

使用.符号

{{user.name}}

另一种数组写法,使用[]

{{user['name']}}

编译后:

<?php echo $user['name'];?>

对象

调用对象方法或属性使用->符号

{{user->name}}
{{user->name()}}

编译后:

<?php echo $user->name;?>
<?php echo $user->name();?>

不输出

在右定界符后使用符号表示不调用echo输出内容,通常可以使用这种方法做赋值等操作。

{{%name = 'your name'}}

编译后:

<?php $name = 'your name';?>

注释

在右定界符前使用#符号可注释单行内容,被注释内容编译后会被去掉。

{{#注释内容}}

注释多行内容使用note标签

过滤器

过滤器用来处理输出。

在模版可以使用链式语法符号.连接过滤器名调用过滤器

{{name.trim()}}

另外也可以使用传统写法调用过滤器

{{trim(name)}}

第一种写法相当于.前面内容作为过滤器第一个参数,语法更符合人的阅读习惯。

编译后:

<?php echo trim($name);?>

内置过滤器

模版引擎内置了大量过滤器(默认配置),这些过滤器通常编译后会被替换成PHP原生函数。

如内置字符串分割过滤器split定义如下例,使用PHP函数explode实现,并且其使用$加数字调整参数位置。

'split' => 'explode($1, $0)',

调用示例:

{{a.split(b)}}

编译后:

<?php echo explode($b, $a);?>

另外如果有多个可选参数时,可以用...符号省略,如:

'format' => 'sprintf($0, $1, ...)',

调用示例:

{{'%s, %s, %s'.format(a, b, c)}}

编译后:

<?php echo sprintf('%s, %s, %s', $a, $b, $c);?>

用户也可以通过配置filters增加或替换内置过滤器,如下例增加了一个字符串反转过滤器strrev

'filters' => [
// 反转字符串
'strrev' => 'strrev($0)'
],

既然内置过滤器基本都是原生PHP函数的马甲为什么不直接用原生PHP函数?

  • 安全:只允许使用过滤器,可以防止设计人员使用有些危险的PHP函数如eavl exec等。
  • 易用:内置过滤器借鉴了javascript语法及其流行框架和其它语言的流行模版引擎,使其更适合设计人员使用,另外由于历史原因,一些原生PHP函数的命名和参数顺序也有些混乱,使用内置过滤器可以调整这些问题。
  • 易读:过滤器使用链式调用语法,阅读起来更加自然顺畅。

自定义过滤器

除了使用内置过滤器,用户也可以在业务代码中定义自定义过滤器供模版使用。

定义自定义过滤器使用View::filter方法,如:

View::filter('my_filter', function ($content, $tag) {
return "<$tag>$content</$tag>";
});

在模版中调用

{{content.my_filter(tag)}}

注意自定义过滤器命名不要与内置过滤器冲突,同名内置过滤器调用优先级大于自定义过滤器

魔术变量

模版内保留了少量魔术变量(默认配置),使用$符号标记语句调用。

此类变量使用方式与普通变量一样,但并不是真正的变量,最终会被编译成函数或方法调用。

调用示例:

{{$query.id}}
{{$cookie[id]}}

编译后:

<?php echo framework\core\http\Request::query('id'); ?>
<?php echo framework\core\http\Request::cookie($id); ?>

函数 容器 静态方法

在模版语法中可以用@符号标记语句调用PHP函数、静态类方法、容器实例方法(依赖框架实现)

函数

默认不允许调用任何函数,用户可通过配置项allow_php_functions来设置允许调用的函数。

'allow_php_functions' => true 表示允许调用所有可用函数。

'allow_php_functions' => ['a', 'b', 'c', ...] 表示只允许调用配置数组内的函数。

模版调用规则@函数名()

{{@foo()}}

静态方法

要调用静态类方法,用户必须设置配置项allow_static_classes来设置允许调用的静态类。

'allow_static_classes' => [
'类别名' => '类全名'
]

模版调用规则@类别名.方法名()

{{@foo.bar.baz()}}

容器方法

默认不允许调用任何容器,用户可通过配置项allow_container_providers来设置允许调用的容器。

'allow_container_providers' => true 表示允许调用所有可用容器。

'allow_container_providers' => ['a', 'b', 'c', ...] 表示只允许调用配置数组内的容器。

模版调用规则@容器名->方法名()

{{@foo->bar()}}

容器实例的调用依赖框架实现,如用户需自定义容器实例调用(如单独使用模版引擎),可设置配置项view_container_macro实现。

运算符

数学运算符 + - * / %

比较运算符 == === != !== > >= < <=

逻辑运算符 || && !

三元运算符 a ? b : c a ?: b a ?? b

赋值 =

括号 (a + b) * c

数组 [1, 2 ,3] {"a": 1, "b": 2, "c": 3}

控制结构

控制结构以标签内使用前缀符@开头的属性标识,支持if elseif else each for while

判断

<h1 @if="title">{{title}}</h1>
<h1 @elseif="title2">{{title2}}</h1>
<h1 @else>标题</h1>

编译后:

<?php if ($title) { ?>
<h1><?php echo $title; ?></h1>
<?php } elseif ($title2) { ?>
<h1><?php echo $title2; ?></h1>
<?php } else { ?>
<h1>标题</h1>
<?php } ?>

联合使用if elseif else时,其标签之间不能有其它非空内容,否则会出现异常编译失败。

循环

each语句

使用key:val in array的格式遍历数组的键值

<p @each="id:user in users" id="item_{{id}}">{{user.name}}</p>

编译后:

<?php foreach ($users as $user => $id) { ?>
<p id="item_<?php echo $id; ?>"><?php echo $user['name']; ?></p>
<?php } ?>

也可以使用忽略键值的语法

忽略键

@each="val in array"

忽略值

@each="key: in array"

忽略键值(此情况下默认可以使用keyval来获取键值)

@each="array"

for语句

<p @for="i=0; i<9; i++">{{i}}</p>

编译后:

<?php for ($i=0; $i<9; $i++) { ?>
<p><?php echo $i; ?></p>
<?php } ?>

while语句

<p $i="9" @while="i>0" $i="i-1">{{i}}</p>

编译后:

<?php $i = 9; ?>
<?php while ($i > 0) { ?>
<?php $i = $i - 1; ?>
<p><?php echo $i; ?></p>
<?php } ?>

变量赋值

模版支持在标签内使用以$开头的属性语法赋值变量

<p $name="user.name">{{name}}</p>

编译后:

<?php $name = $user['name']; ?>
<p><?php echo $name; ?></p>

双引号字符串

模版支持在标签内以*开头的属性的值使用原生的PHP双引号字符串语法,在字符串中嵌入多个变量时更加便捷。

<a *href="$a/$b/$c"></a>

编译后:

<a href ="<?php echo "$a/$b/$c"; ?>"></a>

相近的插值语法写法:

<a href="{{a}}/{{b}}/{{c}}"></a>

编译后:

<a href ="<?php echo $a; ?>/<?php echo $b; ?>/<?php echo $c; ?>"></a>

模版引用

使用模版引用标签include在当前模版中引用其它模版

<include name="header" />
{{content}}
<include name="footer" />

include标签最终会被编译成PHP原生include语句来引用加载其它模版,所以在编译当前模版时是不读取被引用模版内容的,也不会合并到当前模版一起编译

动态引用

使用:name属性动态引用模版,因为其传递是一个变量,而name传递是一个字符串。

标签内以:开头的属性为动态属性参数,其值使用模版语法解析。

<include :name="view" />

include标签中也可以使用判断循环语句

如例:循环views并判断模版是否存在,存在则引用模版。

<include @each="view in views" @if="view_exists(view)" :name="view" />

除了include标签,下面的insertextends标签不支持动态调用,因为它们是先合并后再编译。

模版插入

使用模版插入insert标签可以将要插入的模版读取合并到当前模版,然后再编译。

当前模版

<insert name="title" />
<p>{{content}}</p>

要插入的模版title

<h1>{{title}}</h1>

合并为

<h1>{{title}}</h1>
<p>{{content}}</p>

insert标签类似于而又不同于include标签,前者会合并后编译,后者会分别独立编译。

插槽

使用插槽标签slot可以将insert标签包裹的内容注入到要插入的模版内,如

当前模版

<insert name="title">title</insert>

要插入的模版title

<h1><slot/>: {{title}}</h1>

合并为

<h1>title: {{title}}</h1>

slot默认值

可以在要插入的模版中使用slot标签包裹设置默认内容,如果当前模版中insert标签没有包裹内容则使用默认内容,有则覆盖默认内容。如

当前模版

<insert name="title"/>

要插入的模版title

<h1><slot>title</slot>: {{title}}</h1>

合并为

<h1>title: {{title}}</h1>

多个slot

一个insert标签内可以指定多个slot,使用方法为用多个slot标签包裹内容。

当前模版

<insert name="title">
<slot name="slot1">title1</slot>
<slot name="slot2">title2</slot>
</insert>

要插入的模版title

<h1><slot name="slot1"/>: {{title1}}</h1>
<h1><slot name="slot2"/>: {{title2}}</h1>

合并为

<h1>title1: {{title1}}</h1>
<h1>title2: {{title2}}</h1>

插入块

insert不仅支持插入整个模版文件内容,也支持插入其中的一个模版块block,语法为使用.连接模版名与block名。

当前模版

<insert name="title.title1"/>
<insert name="title.title2">title2</insert>

要插入的模版title

<block name="title1"><h1>{{title1}}</h1></block>
<block name="title1"><h1><slot/>: {{title1}}</h1></block>

合并为

<h1>{{title1}}</h1>
<h1>title2: {{title2}}</h1>

多重插入

模版支持多重继承,如a中插入bb中插入c

模版a

<p>{{a}}</p>
<insert name="b"/>

模版b

<p>{{b}}</p>
<insert name="c"/>

模版c

<p>{{c}}</p>

合并为

<p>{{a}}</p>
<p>{{b}}</p>
<p>{{c}}</p>

多重插入不能大于9层,防止相互插入死循环。

模版继承

使用模版继承extends标签可以将当前模版block合并到其继承的父模版中。

当前模版

<extends name="layout" />
<block name="title">{{title}}</block>
<block name="content"><parent/> {{content}}</block>

父模版layout

<!DOCTYPE html>
<html>
<head>
<title>
<block name="title"> 标题 </block>
</title>
<include name="header" />
</head>
<body>
<block name="menu"> 菜单 </block>
<block name="content"> 正文 </block>
</body>
<include name="footer" />
</html>

合并为:

<!DOCTYPE html>
<html>
<head>
<title>
{{title}}
</title>
<include name="header" />
</head>
<body>
菜单
正文 {{content}}
</body>
<include name="footer" />
</html>

子模版中定义的block替换父模版中同名的block,子模版中未定义的block默认使用父模版中的block

子模版block中使用<parent/>标签可把父模版同名block的内容插入到其中。

多重继承

模版支持多重继承,如a继承bb继承cc继承da b c3个模版会合并block,同名block覆盖,优先级为a > b > c

d为基础layout父模版,a b c合并的blockd合并成最终模版。

模版a

<extends name="b" />
<block name="a">{{a}}</block>

模版b

<extends name="c" />
<block name="b">{{b}}</block>

模版c

<extends name="d" />
<block name="c">{{c}}</block>

模版d

<block name="a"/>
<block name="b"/>
<block name="c"/>

合并为:

{{a}}
{{b}}
{{c}}

多重继承不能大于9层,防止相互继承死循环。

插入与继承

模版插入insert可以与模版继承extends联合使用。

当前模版

<extends name="layout" />
<block name="a"><insert name="a"/></block>
<block name="b"><insert name="b.b"/></block>
<insert name="c"/>

模版a

{{a}}

模版b

<block name="b">{{b}}</block>

模版c

<block name="c">{{c}}</block>

模版layout

<block name="a"/>
<block name="b"/>
<block name="c"/>

合并为:

{{a}}
{{b}}
{{c}}

extends继承标签和insert插入标签同时使用时,insert标签先于extends标签合并。注意block不要出现嵌套使用。

空标签

有些情况下,模版语法不需要嵌入到实体html标签中时,可使用空标签void,编译后空标签会被去除只保留逻辑部分。

<void @each="user in users">{{user.name}}</void>

编译后:

<?php foreach ($users as $user) { ?>
<?php echo $user['name']; ?>
<?php } ?>

注释标签

使用note标签注释模版,被注释部分在模版编译后去除。

<note>
这是一条注释
</note>

原样输出

使用raw标签可以防止模板标签被解析,其包含内容会被原样输出。

通常在同时使用前后端模版时,可使用此标签防止前端模板被解析。

<raw>
{{name}}
</raw>

note extends insert等标签优先级大于raw标签,所以仍会被解析编译。

Heredoc

使用heredoc标签可以调用php原生heredoc语法,在输出大段包含变量的文本时更加便捷,也可以设置配置heredoc_tag => false禁止heredoc标签使用。

<heredoc>
My name is "$name". I am printing some $foo->foo.
Now, I am printing some {$foo->bar[1]}.
</heredoc>

编译后:

<?php print <<<EOT
My name is "$name". I am printing some $foo->foo.
Now, I am printing some {$foo->bar[1]}.
EOT; ?>

另外也可以使用name指定heredoc标识符来替换默认的标识符EOT

如:

<heredoc name="TESTEOT">...</heredoc>

编译后:

<?php print <<<TESTEOT
...
TESTEOT; ?>

heredoc标签中无法使用模版语法。

原生PHP

在需要使用原生PHP代码时可以php标签来包裹PHP代码,但是一般不建议这么做,可以设置配置php_tag => false禁止php标签使用。

<php>
$name = 'your name';
</php>

模版中不允许存在<?php ?>这类原生PHP语法,php标签中也无法使用模版语法。

附录

默认配置

// 空标签
'blank_tag' => 'tag',
// 原生标签
'raw_tag' => 'raw',
// php标签
'php_tag' => 'php',
// heredoc标签
'heredoc_tag' => 'heredoc',
// 插槽标签
'slot_tag' => 'slot',
// 块标签
'block_tag' => 'block',
// 父标签
'parent_tag' => 'parent',
// 插入标签
'insert_tag' => 'insert',
// 引用标签
'include_tag' => 'include',
// 继承标签
'extends_tag' => 'extends',
// 结构语句前缀符
'struct_attr_prefix' => '@',
// 赋值语句前缀符
'assign_attr_prefix' => '$',
// 双引号语句前缀符
'double_attr_prefix' => '*',
// 参数语句前缀符
'argument_attr_prefix' => ':',
// 文本插入左右边界符号
'text_delimiter_sign' => ['{{', '}}'],
// 文本插入是否自动转义
'auto_escape_text' => true,
// 文本转义符号与反转义符号
'text_escape_sign' => [':', '!'],
// 不输出标识符
'not_echo_text_sign' => '#',
// 原样输出标识符(不解析文本插入边界符以其内内容)
'raw_text_sign' => '!',
// 注释符号
'note_text_sign' => '#',
// 允许的函数
'allow_php_functions' => false,
// 允许的静态类
'allow_static_classes' => false,
// 允许的容器
'allow_container_providers' => false,
// 模版读取器
'view_template_reader' => View::class.'::readTemplate',
// filter宏
'view_filter_macro' => View::class.'::filterMacro',
// include宏
'view_include_macro' => View::class.'::includeMacro',
// container宏
'view_container_macro' => View::class.'::ContainerMacro',
// check expired宏
'view_check_expired_macro' => View::class.'::checkExpiredMacro',

内置过滤器

// 是否不为空
'has' => '!empty($0)',
// 是否为空
'empty' => 'empty($0)',
// 是否存在
'isset' => 'isset($0)',
// 默认值
'default' => '($0 ?? $1)',
// 转为字符串
'str' => 'strval($0)',
// 字符串拼接
'concat' => '($0.$1)',
// 字符串拼接
'format' => 'sprintf($0, $1, ...)',
// 字符串补全填充
'pad' => 'str_pad($0, $1, ...)',
// 字符串替换
'replace' => 'str_replace($1, $2, $0)',
// 字符串中字符位置
'index' => 'strpos($1, $0)',
// 字符串截取
'substr' => 'substr($0, $1, $2)',
// 字符串截取
'slice' => 'substr($0, $1 > 0 ? $1 - $0 : $1)',
// 字符串重复
'repeat' => 'str_repeat($0, $1)',
// 字符串长度
'length' => 'strlen($0)',
// 字符串大写
'lower' => 'strtolower($0)',
// 字符串小写
'upper' => 'strtoupper($0)',
// 字符串首字母大写
'ucfirst' => 'ucfirst($0)',
// 每个单词的首字母大写
'capitalize' => 'ucwords($0)',
// 字符串剔除两端空白
'trim' => 'trim($0, ...)',
// 文本换行符转换成HTML换行符
'nl2br' => 'nl2br($0)',
// 字符串md5值
'md5' => 'md5($0)',
// 字符串hash值
'hash' => 'hash($1, $0)',
// 正则匹配
'match' => 'preg_match($1, $0, ...)',
// 字符串HTML转义
'escape' => 'htmlentities($0)',
// 字符串HTML反转义
'unescape' => 'html_entity_decode($0)',
// 字符串URL转义
'urlencode' => 'urlencode($0)',
// 字符串URL反转义
'urldecode' => 'urldecode($0)',
// 数组转为JSON
'jsonencode' => 'json_encode($0, true)',
// JSON转为数组
'jsondenode' => 'json_denode($0, JSON_UNESCAPED_UNICODE)',
// 元素是否存在于数组作用
'in' => 'in_array($0, $1, true)',
// 数组长度
'count' => 'count($0)',
// 创建范围数组
'range' => 'range($0, $1)',
// 字符串分割为数组
'split' => 'explode($1, $0)',
// 数组连接成字符串
'join' => 'implode($1, $0)',
// 获取数组keys
'keys' => 'array_keys($0)',
// 获取数组values
'values' => 'array_values($0)',
// 合并数组
'merge' => 'array_merge($0, $1)',
// 最大值
'max' => 'max($0, ...)',
// 最小值
'min' => 'min($0, ...)',
// 转为数字
'num' => '($0+0)',
// 数字绝对值
'abs' => 'abs($0)',
// 数字向上取整
'ceil' => 'ceil($0)',
// 数字向下取整
'floor' => 'floor($0)',
// 数字四舍五入
'round' => 'round($0, ...)',
// 数字随机值
'rand' => 'rand($0, $1)',
// 数字格式化
'number_format' => 'number_format($0)',
// 时间戳
'time' => 'time()',
// 转为时间戳
'totime' => 'strtotime($0)',
// 时间格式化
'date' => 'date($0, $1)',
// 视图文件是否存在
'view_exists' => View::class.'::exists($0)',