agashe / sigmaphp-template
PHP的强大模板引擎
Requires (Dev)
- phpunit/phpunit: ^9.5
README
PHP的强大模板引擎。您可以使用它来构建您的Web应用程序,无需配置,语法简单。
功能
- 打印变量和评估表达式
- 扩展和包含模板,支持相对路径
- 使用点表示法从子目录中加载模板
- 所有基本条件语句 if,else if,else
- 支持在所有类型的迭代器(字符串、数字和数组)上循环
- 支持块,以结构化您的模板
- 在模板中定义变量
- 在模板之间共享变量
- 注册自定义指令
- 支持单行/多行注释
- 缓存
安装
composer require agashe/sigmaphp-template
文档
基本用法
要开始使用SigmaPHP-Template,我们首先包括autoload.php
文件,然后从Engine
类创建新实例,最后调用render
方法
<?php
require 'vendor/autoload.php';
use SigmaPHP\Template\Engine;
$engine = new Engine('/templates');
$output = $engine->render('index');
Engine
构造函数接受两个参数,第一个是模板的根路径,例如views
或templates
,或者您喜欢的任何名称。如果没有提供路径,则Engine
将考虑项目的根路径作为模板目录。
$engine = new Engine('/path/to/my-html-views');
// or
$engine = new Engine();
第二个参数是缓存目录的路径,如果设置了路径,则启用缓存,否则默认禁用缓存。
模板文件
SigmaPHP-Template使用扩展名为.template.html
的模板文件,它们只是普通的HTML文件,但Engine
将识别不同类型指令的标记。
// index.template.html
<h1>Header Tag</h1>
<ul>
{% for $i in 5 %}
<li>{{ $i }}</li>
{% end_for %}
</ul>
渲染方法
render
方法负责处理您的模板并返回最终要打印或作为响应发送的HTML结果。此方法接受三个参数
$engine->render(
'admin.users.edit', // template's file path including the name
['users' => [...]], // parameters array
true // return string or print output option
);
第一个参数是模板文件的路径,点表示法代表从模板根路径(我们在构造函数中设置)开始的实际路径。
因此,在上面的示例中,Engine
将查找的实际路径是/path/to/my/project/templates/admin/users/edit.template.html
第二个参数是包含将要传递给模板进行渲染的变量的数组。在上面的示例中,我们有一个用户列表,我们需要在模板中遍历它。
/admin/users/edit.template.html
<table>
{% for $user in $users %}
<tr>
<td>{{ $user->name }}</td>
</td>
{% end_for %}
</table>
最后,我们有第三个参数,它是布尔值,用于返回结果作为字符串或仅打印最终结果。在大多数情况下,我们需要字符串表示形式以返回HTTP响应或作为邮件正文。因此,默认情况下此参数设置为false,要打印结果,请将其设置为true。
括号与引号
SigmaPHP-Template使用两组括号。基本的打印和表达式评估都使用双大括号
{{ /* expression or variable goes here */ }}
对于指令,使用成对的百分号和大括号
{% /* all directives like if, for ....etc */ %}
最后,使用成对的双短横线和括号进行注释
{-- /* comments */ --}
对于引号,单引号'
和双引号"
都可以用于接受带引参数的指令,所以以下都是有效的
{% block '...' %}
// or
{% block "..." %}
打印变量
任何模板引擎都能处理的最基本的功能是打印
$engine->render('message', [
'name' => 'Ahmed',
'age' => 15
], true);
在message.template.html
<p>{{ $name }}</p>
<p>{{ $age }}</p>
// or
<p>{{ $name . ' ' . $age }}</p>
// or
<p>{{ $name }} {{$age }}</p>
所有变量都需要使用$
符号,就像在PHP中处理变量一样。
表达式评估
就像正常打印一样,我们可以评估任何表达式并打印结果
{{ 1 + 2 + 3 }}
{{ !empty($foo) ? 'Yes' : 'No' }}
{{ date('Y-m-d') }}
通常,除了不安全的方法之外,所有PHP内置函数都将工作,没有人需要在模板中使用eval
或exit
!
另一个要点是变量分配,所以如果你想在模板中重新分配变量的值,我们可以简单地写出
// assign new value for a variable
// nothing will be shown on this line
{{ $foo = 1 + 2 }}
// we print the new value here !
{{ $foo }}
请注意,给变量赋值不会渲染任何内容!
注释
注释可以是单行或多行,并且所有包含在注释内的内容都不会被执行。
{-- {{ strtolower('ABC') }} This line will evaluate nothing --}
{--
Multiple
lines
comment !
{% if ($num > 5) %}
This condition will never be resolved
{% end_if %}
--}
扩展和包含模板
通常在开发应用程序时,我们会创建基础模板,然后在其他模板中扩展它。该Engine
提供了两个指令来扩展基础模板和包含子模板。
假设我们有base.template.html
<div class="main">
<p>Base Template</p>
</div>
我们可以在app.template.html
中轻松扩展此模板,如下所示
{% extend 'base' %}
// the rest of app template content
extend
指令接受不带扩展名的基模板名称(template.html
)。
接下来是include
指令,它允许我们在当前模板中使用其他模板,例如,我们可以在页面上部分显示或使用按钮、警告等组件。
<form class="create-post-form">
<input type="text" name="title" />
{% include 'submit-button' %}
</form>
在上面的示例中,submit-button
是另一个模板,其中包含按钮标记。
<button class="btn btn-success fade is-submitting">Submit</button>
最终结果将是
<form class="create-post-form">
<input type="text" name="title" />
<button class="btn btn-success fade is-submitting">Submit</button>
</form>
点表示法路径
为了在子目录中组织模板,Engine
使用点表示法来访问子目录,所以假设在先前的示例中,submit-button
位于components
目录下,我们可以轻松访问它。
<form class="create-post-form">
<input type="text" name="title" />
{% include 'components.submit-button' %}
</form>
相同的特性也适用于extend
指令。
{% extend 'admin.layouts.master' %}
// the rest of the template
在这两种情况下,Engine
将从模板的根目录开始搜索这些模板(我们在构造函数中设置的目录)并加载它们。
components.submit-button --> templates/components/submit-button.template.html
admin.layouts.master --> templates/admin/layouts/master.template.html
相对路径
有时我们可能会遇到两个模板在同一个目录中的情况,其中一个模板扩展/包含另一个模板,所以我们被迫写出完整的路径,即使它们都在同一个目录中!!
让我们举一个例子,假设我们有两个模板
/very/long/path/to/admin/dashboard/default.template.html
/very/long/path/to/admin/dashboard/_partial.template.html
为了在default
中包含_partial
,你需要写出
... content of default.template.html
{% include 'very.long.path.to.admin.dashboard._partial' %}
SigmaPHP-Template提供了相对路径操作符./
,如果你正在扩展或包含放置在相同目录中的模板,我们可以在模板名称前添加./
,然后Engine
将自动在当前模板的同一目录中(正在处理的模板)查找模板。
所以,在上面的示例中,我们可以写出
... content of default.template.html
{% include './_partial' %}
块
当使用扩展和包含时,我们始终需要一种方式来组织模板,以便基础模板可以从当前模板中填充内容,为此我们有了block
和show_block
指令。
block
指令定义块体,而show_block
指令调用块体并将其添加到模板。让我们举一个例子
假设我们有master.template.html
,其内容如下
<html>
<head>
{% show_block 'title' %}
</head>
<body>
{% show_block 'content' %}
</body>
</html>
然后让我们创建app.template.html
,它将扩展master
模板。app
模板必须实现master
中定义的块,否则将抛出异常
{% extend 'master' %}
{% block 'title' %}Home Page{% end_block %}
{% block 'content' %}
<article>Page Content</article>
{% end_block %}
运行上述示例的结果是
<html>
<head>
Home Page
</head>
<body>
<article>Page Content</article>
</body>
</html>
block
和show_block
指令可以在同一文件中使用,就像我们想要使用if条件来控制块的可视性一样。在某些情况下,在show_block
之前必须有块定义是强制性的,例如,在master
模板中我们可以添加一个用于js文件的块。
<html>
<head>
{% show_block 'title' %}
</head>
<body>
{% show_block 'content' %}
{-- JS Files --}
{% show_block 'js' %}
</body>
</html>
现在我们被迫在所有扩展 master
的模板中创建 js 块,而我们可以定义 js 块的默认实现
<html>
<head>
{% show_block 'title' %}
</head>
<body>
{% show_block 'content' %}
{-- JS Files --}
{% block 'js' %}{-- Could be empty --}{% end_block %}
{% show_block 'js' %}
</body>
</html>
现在不会抛出异常,应用将正常运行。另外,在 app
模板中,如果需要,可以轻松调用我们的 js 脚本
{% extend 'master' %}
{% block 'title' %}Home Page{% end_block %}
{% block 'content' %}
<article>Page Content</article>
{% end_block %}
{% block 'js' %}
<script src="main.js"></script>
{% end_block %}
请注意,子块的块内容将始终覆盖父块的块内容!所以在之前的例子中,假设 js 块在 master
中有一些内容,所有内容都将被 app
的 js 块内容覆盖。
至于块命名,所有字母大小写、数字和 _ . - 都允许,所以以下所有名称都是有效的,请注意,块名称不能只是一个数字,但可以以数字开头
{% block 'test1001' %}
{% block '123test' %}
{% block 'small_article_container' %}
{% block 'alert-message' %}
额外提示:如果您喜欢将块名称添加到 end_block
指令中,则 Engine
会接受这种行为
{% block 'my-block' %}
// ... my-block content
{% end_block 'my-block' %}
定义变量
虽然不建议在模板中定义变量,但有时我们不得不这样做,比如格式化日期或从长文本中减去字符串。无论什么情况,Engine
提供了 define
指令,用于以下语法定义变量
{% define $x = 100 %}
{-- Print $x --}
{{ $x }}
变量的命名约定与 PHP 变量命名约定相同。所有定义的变量都必须有默认值,默认值可以是标量或表达式结果
{% define $name = "John Doe" %}
// or
{% define $num = 1 + 2 + 3 %}
变量也可以相互分配,或者与模板中定义的变量一起分配
// index.php
$output = $engine->render('app', [
'user' => User::findById('123')
]);
// app.template.html
{% define $userAge = $user->age %}
{% define $age = $userAge %}
{{ "User age : " . $age }}
关于变量的两个重要注意事项
1- 变量可以被重新定义,在这种情况下,它将被视为重新分配,所以下面的例子中 $x
的最终值是 10
{% define $x = 5 %}
{% define $x = 10 %}
2- 由于变量是全局定义的,因此您不能在局部作用域内定义变量,如:if 条件、循环和块!
// All the following will throw exception upon execution :
{% if (....) %}
{% define $x = 5 %}
{% end_if %}
{% for ... in .... %}
{% define $x = 5 %}
{% end_for %}
{% block '.....' %}
{% define $x = 5 %}
{% end_block %}
共享变量
为了在多个模板之间共享变量,Engine
提供了 setSharedVariables
,它接受一个关联数组,包含您希望对所有模板可访问的变量。
例如,假设我们有像应用程序的社交媒体账户 URL 这样的值,您希望在每一页上显示。通常你会这样做
// pass the variables to each template to the render method :(
$facebook = 'https://.....';
$linkedin = 'https://.....';
$youtube = 'https://.....';
$engine->render('home_page', [
'facebook' => $facebook,
'linkedin' => $linkedin,
'youtube' => $youtube,
]);
$engine->render('contact_us', [
'facebook' => $facebook,
'linkedin' => $linkedin,
'youtube' => $youtube,
]);
相反,您可以简单地这样做
$engine->setSharedVariables([
'facebook' => 'https://.....',
'linkedin' => 'https://.....',
'youtube' => 'https://.....'
]);
$engine->render('home_page');
$engine->render('contact_us');
// much cleaner :)
条件
为了控制模板,可以使用条件指令来决定显示、隐藏或处理的部分。条件是一个正则 PHP 表达式,可以根据在 render
方法中定义的变量、模板中定义的变量或其他有效表达式进行评估,以确定其真伪。
{% if ($test1 == 'FOO') %}
Test 1 equals : Foo
{% else_if (1 + 1) %}
Test 2
{% else_if (false) %}
Test 3
{% else %}
Do something ...
{% end_if %}
if
和 else_if
条件应该用括号 (...)
括起来。所有条件指令都可以一起使用,也可以只使用简单的 if
/ end_if
对,也可以内联编写。
{% define $show_block = true %}
{% if ($show_block) %}
{% show_block 'list-block' %}
{% end_if %}
<img src="{{ $imageSrc }}" class="{% if ($haveClass) %} mx-100 {% end_if %}" />
{% if (($val > 0) && ($val < 100)) %}
<p>Do something with {{$val}}</p>
{% else %}
Invalid value : {{$val}}
{% end_if %}
最后,嵌套条件也是受欢迎的
{% if (($val > 0) && ($val < 100)) %}
<p>Do something with {{$val}}</p>
{% else %}
{% if ($val > 100) %}
<h1>The value is too large</h1>
{% else_if ($val < 0) %}
<h1>The value is too small</h1>
{% else_if ($val == 0) %}
<h1>The value can't be zero</h1>
{% end_if %}
{% end_if %}
循环
循环指令是另一种控制语句,循环是任何模板引擎的核心功能,因此您可以列出内容。
SigmaPHP-Template 具有循环指令 for .. in
,它可以循环遍历数字、字符串和数组。
{% for $litter in 'abcd' %} {{ $litter }} {% end_for %}
{% for $num in 5 %}
{{ $num }}
{% end_for %}
{% define $sum = 0 %}
{% for $i in [1, 2, 3, 4, 5] %}
{{ $sum = $sum + $i }}
{% end_for %}
{{ $sum }}
// $items => [['id' => 1, .....], ['id' => 2, .....]]
{% for $item in $items %}
{{ "{$item['id']} : {$item['name']}" }}
{% end_for %}
此外,Engine
为循环提供了两个指令 break
和 continue
,可以用来控制循环。两者都需要一个条件来评估。
// break the loop
{% for $item in $items %}
{% break ($item['id'] == 8) %}
{{ "{$item['id']} : {$item['name']}" }}
{% end_for %}
{-- Print Odd Numbers --}
{% for $num in 10 %}
{% continue ($num % 2 == 0) %}
{{ $num }}
{% end_for %}
并且,像条件一样,嵌套循环也是支持的
{% for $i in 2 %}
{% for $j in 3 %}
{% for $k in 4 %}
{{ $i * $j * $k }}
{% end_for %}
{% end_for %}
{% end_for %}
自定义指令
有时您的应用程序可能需要在模板中实现某些功能,比如处理订单状态、处理货币和货币等。
可以使用 registerCustomDirective
方法来定义自己的指令。自定义指令的格式为 {% myDirective(.... 参数) %}
。自定义指令是一个可能接受或可能不接受一些参数并返回可以被 Engine
渲染的值的函数,下面是一些示例。
// index.php
$engine->registerCustomDirective('add', function (...$numbers) {
$sum = 0;
foreach ($numbers as $number) {
$sum += $number;
}
return $sum;
});
$engine->registerCustomDirective('formatAmount', function ($amount) {
return $amount . '$';
});
$engine->registerCustomDirective('year', function () {
return date('Y');
});
$engine->render('app', ['items' => $items], true);
// app.template.html
{% add(1, 2, 3) %} // will return 6
{% formatAmount(75.25) %} // will return 75.25$
{% year() %} // will return 20XX
缓存
SigmaPHP-Template 默认支持模板缓存,通过将模板的结果保存到缓存文件中。当模板或传递给模板的数据没有发生变化时,Engine
总是返回缓存的版本,除非模板或传递给模板的数据发生变化,在这种情况下,Engine
将重新编译模板并缓存新的结果。
要启用缓存,你只需要在 Engine
构造函数中设置缓存目录的路径。
<?php
require 'vendor/autoload.php';
use SigmaPHP\Template\Engine;
$engine = new Engine('/templates', '/path/to/cache');
// the Engine will look first for the cache version to return
// if not found , the template will be compiled
$output = $engine->render('index');
启用缓存在生产环境中非常有用,但在开发过程中,缓存目录的大小可能会随着时间的推移变得很大。因此,建议通过删除其内部所有文件来清理其内容。
对于运行 Linux 的机器,你可以简单地运行以下命令:
rm /path/to/cache/*
示例
在本节中,我们可以查看如何将模板和指令一起使用来构建我们的应用程序。
// index.php , your controller or wherever place you render your templates
<?php
require 'vendor/autoload.php';
use SigmaPHP\Template\Engine;
$variables = [
'appName' => 'My Awesome App',
'message' => 'All done Successfully',
'navLinks' => [
['name' => 'home', 'url' => '/path/to/home'],
['name' => 'contact', 'url' => '/path/to/contact'],
['name' => 'about', 'url' => '/path/to/about'],
]
];
$engine = new Engine('/front/views', '/storage/cache');
$output = $engine->render('homepage', $variables);
// depending on your app return $output or send it in a http response ,
// or whatever you like , for example :
return new HttpResponse($output);
// base.template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $appName }} - {% show_block 'title' %}</title>
</head>
<body>
{% include 'full.header' %}
{% show_block 'content' %}
{% include 'full.footer' %}
{-- JS SECTION --}
{% block 'js' %}
<script src="base.js"></script>
{% end_block %}
{% show_block 'js' %}
{-- JS SECTION --}
</body>
</html>
// header.template.html
<header>
<a href="{{ $navLinks[0]['url'] }}" class="logo">{{ $appName }}</a>
<div class="navigation-links">
<ul class="list">
{% for $link in $navLinks %}
<li>
<a {%if ($link['name'] == 'home')%}class="active"{%end_if%} href="{{ $link['url'] }}">{{ $link['name'] }}</a>
</li>
{% end_for %}
</ul>
</div>
</header>
// footer.template.html
<footer>
<div class="footer-top">
{% include './button' %}
</div>
<div class="footer-content">
<ul class=”socials”>
{% for $platform in ['facebook', 'twitter', 'youtube'] %}
<li><a href="#"><i class="fa fa-{{$platform}}"></i></a></li>
{% end_for %}
</ul>
</div>
<div class="footer-bottom">
<b>{{ "{$appName} (C) All Rights Reserved " . date('Y') }}</b>
</div>
</footer>
// button.template.html
<button class="btn">A Button</button>
// button.template.html
<div class="alert">
{{ $message }}
</div>
// homepage.template.html
{% extend './base' %}
{% block 'title' %} HomePage {% end_block %}
{% block 'content' %}
<!-- Alert Message -->
{% if (!empty($message)) %}
{% include './alert' %}
{% end_if %}
<!-- Alert Message -->
<article>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed vehicula porttitor velit sollicitudin porttitor.
Phasellus sit amet euismod dolor.
</article>
{% end_block 'content' %}
{-- JS SECTION --}
{% block 'js' %}
<script src="app.js"></script>
{% end_block %}
{-- JS SECTION --}
许可证
(SigmaPHP-Template) 在 MIT 许可证的条款下发布。