轻量级、安全的 PHP 模板引擎,支持布局和自定义标签库。
composer require mier/template
在 composer.json 中添加:
{
"repositories": [
{
"type": "path",
"url": "./extend/orangeheart/template"
}
],
"require": {
"mier/template": "*"
}
}
use mier\Template\Engine;
$engine = new Engine([
'view_path' => '/path/to/templates',
'cache_path' => '/path/to/cache',
'layout_on' => true,
'debug' => true,
]);
// 赋值变量
$engine->assign('title', 'Hello World');
$engine->assign(['user' => $user, 'list' => $items]);
// 渲染模板
echo $engine->render('index/index');
{$name} <!-- 输出变量 -->
{$user.name} <!-- 点语法访问数组 -->
{$name|default='Guest'} <!-- 默认值过滤器 -->
{$id ?? ''} <!-- PHP null 合并运算符 -->
{$user.name ?? 'Guest'} <!-- 点语法 + null 合并 -->
{$content|raw} <!-- 原样输出(不转义) -->
{$name|upper} <!-- PHP 函数过滤器 -->
{$phone|mask_phone} <!-- 安全过滤器 -->
{$list|implode=','} <!-- 数组转字符串 -->
{$data|json_encode|default='[]'|raw} <!-- 过滤器链 -->
{if $user.role eq 'admin'}
<p>管理员面板</p>
{elseif $user.role eq 'user'}
<p>用户面板</p>
{else}
<p>访客</p>
{/if}
{if isset $data}...{/if}
{if empty $list}...{/if}
<!-- switch 语句 -->
{switch $user.role}
{case 'admin'}管理员{/case}
{case 'user'}普通用户{/case}
{default}访客{/default}
{/switch}
<!-- 非空判断标签 -->
{notempty name="$list"}
<p>列表有数据</p>
{else}
<p>列表为空</p>
{/notempty}
<!-- 空判断标签 -->
{empty name="$list"}
<p>列表为空</p>
{/empty}
<!-- volist 循环 -->
{volist name="list" id="item"}
<li>{$key}: {$item.title}</li> <!-- key 默认为 $key -->
{/volist}
<!-- 自定义 key 变量名 -->
{volist name="list" id="item" key="k"}
<li>{$k}: {$item.title}</li>
{/volist}
<!-- foreach 循环 -->
{foreach $users as $key => $user}
<p>{$user.name}</p>
{/foreach}
<!-- foreach 支持点语法 -->
{foreach $config.items as $item}
<p>{$item.name}</p>
{/foreach}
<!-- for 循环 -->
{for start="1" end="10" name="i"}
<span>{$i}</span>
{/for}
在 {foreach} 循环中,可以使用 $loop 变量获取循环状态信息:
| 属性 | 说明 |
|---|---|
$loop.index |
当前索引(从0开始) |
$loop.iteration |
当前迭代次数(从1开始) |
$loop.first |
是否是第一个元素 |
$loop.last |
是否是最后一个元素 |
$loop.count |
数组总数 |
$loop.remaining |
剩余元素数 |
$loop.parent |
嵌套循环时访问父级 loop |
示例:
{foreach $items as $item}
<li class="{if $loop.first}first{/if} {if $loop.last}last{/if}">
{$loop.iteration}. {$item.name}
(还剩 {$loop.remaining} 个)
</li>
{/foreach}
<!-- 嵌套循环 -->
{foreach $categories as $category}
<h3>{$category.name}</h3>
{foreach $category.items as $item}
<p>分类 {$loop.parent.iteration} - 项目 {$loop.iteration}</p>
{/foreach}
{/foreach}
{:url('index/index')}
{:date('Y-m-d')}
{:strtoupper($name)}
{include file="header" /}
{include file="common/sidebar" /}
在 layout.html 中:
<!DOCTYPE html>
<html>
<head><title>{$title}</title></head>
<body>
{__CONTENT__}
</body>
</html>
在 index.html 中:
<div class="content">
<h1>欢迎</h1>
</div>
{php}
$result = calculate();
echo $result;
{/php}
{literal}
<script>
var tpl = '{$name}'; // 不会被解析
</script>
{/literal}
use mier\Template\TagLib;
class MyTags extends TagLib
{
protected string $name = 'my';
protected array $tags = [
'hello' => ['attr' => 'name', 'close' => false],
];
public function tagHello(array $attrs, string $content): string
{
$name = $this->attr($attrs, 'name', 'World');
return '<p>Hello, ' . $this->escape($name) . '!</p>';
}
}
$engine->getCompiler()->registerTagLib('my', new MyTags());
在模板中:
{my:hello name="John" /}
| 过滤器 | 说明 |
|---|---|
xss |
基础 XSS 过滤 |
xss_strict |
严格 XSS(移除所有 HTML) |
xss_clean |
XSS 过滤(保留允许的标签) |
xss_rich |
富文本 XSS 过滤 |
mask_phone |
手机号脱敏 |
mask_email |
邮箱脱敏 |
mask_idcard |
身份证脱敏 |
mask_name |
姓名脱敏 |
safe_url |
URL 安全检查 |
filter_sql |
SQL 关键字过滤 |
示例:
{$phone|mask_phone} <!-- 138****1234 -->
{$email|mask_email} <!-- ab***@example.com -->
{$content|xss_rich} <!-- 安全的富文本 -->
| 选项 | 默认值 | 说明 |
|---|---|---|
view_path |
'' |
模板目录 |
cache_path |
'' |
缓存目录 |
view_suffix |
html |
模板文件后缀 |
cache_suffix |
php |
缓存文件后缀 |
tpl_begin |
{ |
左定界符 |
tpl_end |
} |
右定界符 |
layout_on |
false |
是否启用布局 |
layout_name |
layout |
布局文件名 |
layout_item |
{__CONTENT__} |
内容占位符 |
taglib_pre_load |
'' |
预加载标签库 |
cache_enable |
true |
是否启用缓存 |
debug |
false |
调试模式(每次都重新编译) |
security_check |
true |
是否启用安全检查 |
config/view.php 中配置:return [
'handler' => \mier\Template\Adapter\Webman\ViewHandler::class,
'options' => [
'layout_on' => true,
'layout_name' => 'layout',
'taglib_pre_load' => \mier\Template\Tags\Form::class,
],
];
return view('index/index', ['title' => 'Hello']);
本模板引擎支持在 Swoole/Workerman 协程环境中安全使用。
| 组件 | 安全性 | 说明 |
|---|---|---|
ViewHandler::assign() |
✅ | 变量存储在 request() 对象中,协程隔离 |
ViewHandler::render() |
✅ | 渲染后自动清理请求变量 |
Engine::render($tpl, $vars) |
✅ | 通过参数传递变量,无状态 |
Engine::assign() |
⚠️ | 修改实例属性,共享实例时不安全 |
Compiler::compile() |
✅ | 使用局部变量,无状态 |
Security::* |
✅ | 过滤方法无状态 |
Security::setConfig() |
⚠️ | 修改静态配置,只应在启动时调用 |
通过 ViewHandler(推荐)
// ✅ 协程安全:变量存储在 request 对象中
View::assign('title', '页面标题');
return view('index/home', ['data' => $data]);
// ✅ 协程安全:直接传递所有变量
return view('index/home', [
'title' => '页面标题',
'data' => $data
]);
直接使用 Engine
// ✅ 协程安全:通过参数传递变量
$engine = new Engine($config);
echo $engine->render('template', [
'title' => '页面标题',
'data' => $data
]);
// ⚠️ 不推荐:共享实例时 assign 可能造成协程间数据污染
$engine->assign('key', 'value');
Security::setConfig() 会修改全局静态配置,只应在应用启动时调用一次assign() 方法MIT License