轻量级、安全的 PHP 模板引擎

待更新 版本
待更新 版本更新时间
待更新 安装
0 star

mier Template Engine

轻量级、安全的 PHP 模板引擎,支持布局和自定义标签库。

特性

  • 轻量级:无外部依赖,编译快速
  • 安全:内置 XSS 防护、敏感数据脱敏、PHP 代码安全检查
  • 灵活:支持自定义标签库、过滤器、布局
  • 兼容:适用于任何 PHP 框架,内置 Webman 适配器
  • 协程安全:支持 Swoole/Workerman 协程环境

安装

通过 Composer 安装

composer 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 集成

  1. config/view.php 中配置:
<?php

/**
 * 视图配置
 *
 * @author 橘子味的心
 *
 * @date 2026-01-28
 */

use mier\template\adapter\webman\ViewHandler;

return [
    // 使用自定义轻量级视图引擎
    'handler' => ViewHandler::class,

    'options' => [
        // 模板标签定界符
        'tpl_begin' => '{',
        'tpl_end' => '}',

        // 布局配置
        'layout_on' => true,
        'layout_name' => 'layout',
        'layout_item' => '{__CONTENT__}',
        // 布局文件
        'layout_path' => base_path() . '/plugin/system/app/view/',
        // 预加载标签库
        'taglib_pre_load' => \mier\template\tags\Form::class,

        // 缓存配置
        'cache_enable' => true,

        // 安全配置
        'security_check' => true,
    ],
];
  1. 在控制器中使用:
return uview('index/index', ['title' => 'Hello']);
/**
 * 渲染视图
 *
 * @param string      $template 模板名
 * @param array       $vars     变量
 * @param string|null $app      应用名
 *
 * @return Response
 */
function uview(string $template = '', array $vars = [], ?string $app = null): Response
{
    // 解析模板路径
    $template = str_replace('app/', '', url($template, [], false));
    $request = request();
    $plugin = $request->plugin ?? '';

    // 获取视图处理器
    $handler = config($plugin ? "plugin.{$plugin}.view.handler" : 'view.handler');
    if (! $handler) {
        $handler = config('view.handler');
    }

    // 处理插件模板路径
    if ($plugin) {
        $template = str_replace_once($plugin . '/', '', $template);
    }

    // 渲染并返回响应
    return new Response(200, [], $handler::render($template, $vars, $app, $plugin));
}

协程安全

本模板引擎支持在 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');

注意事项

  1. Security 配置Security::setConfig() 会修改全局静态配置,只应在应用启动时调用一次
  2. Engine 实例共享:如果多个协程共享同一个 Engine 实例,避免使用 assign() 方法
  3. ViewHandler 适配器:已做完整的协程安全处理,推荐在 Webman 中使用

作者

  • 橘子味的心 - x_mier@qq.com

许可证

MIT License

赞助商