mier / template
轻量级、安全的 PHP 模板引擎,支持布局和自定义标签库
v1.0.1
2026-01-30 09:26 UTC
Requires
- php: >=8.0
This package is not auto-updated.
Last update: 2026-01-31 02:40:07 UTC
README
轻量级、安全的 PHP 模板引擎,支持布局和自定义标签库。
特性
- 轻量级:无外部依赖,编译快速
- 安全:内置 XSS 防护、敏感数据脱敏、PHP 代码安全检查
- 灵活:支持自定义标签库、过滤器、布局
- 兼容:适用于任何 PHP 框架,内置 Webman 适配器
- 协程安全:支持 Swoole/Workerman 协程环境
安装
通过 Composer 安装
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}
$loop 变量
在 {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
{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 | 是否启用安全检查 |
Webman 集成
- 在
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 配置:
Security::setConfig()会修改全局静态配置,只应在应用启动时调用一次 - Engine 实例共享:如果多个协程共享同一个 Engine 实例,避免使用
assign()方法 - ViewHandler 适配器:已做完整的协程安全处理,推荐在 Webman 中使用
作者
- 橘子味的心 - x_mier@qq.com
许可证
MIT License