agashe/sigmaphp-template

PHP的强大模板引擎

0.1.7 2023-11-27 09:15 UTC

This package is auto-updated.

Last update: 2024-08-27 11:06:00 UTC


README

PHP的强大模板引擎。您可以使用它来构建您的Web应用程序,无需配置,语法简单。

功能

  • 打印变量和评估表达式
  • 扩展和包含模板,支持相对路径
  • 使用点表示法从子目录中加载模板
  • 所有基本条件语句 ifelse ifelse
  • 支持在所有类型的迭代器(字符串、数字和数组)上循环
  • 支持块,以结构化您的模板
  • 在模板中定义变量
  • 在模板之间共享变量
  • 注册自定义指令
  • 支持单行/多行注释
  • 缓存

安装

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构造函数接受两个参数,第一个是模板的根路径,例如viewstemplates,或者您喜欢的任何名称。如果没有提供路径,则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内置函数都将工作,没有人需要在模板中使用evalexit

另一个要点是变量分配,所以如果你想在模板中重新分配变量的值,我们可以简单地写出

// 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' %}

当使用扩展和包含时,我们始终需要一种方式来组织模板,以便基础模板可以从当前模板中填充内容,为此我们有了blockshow_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>

blockshow_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 %}

ifelse_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 为循环提供了两个指令 breakcontinue,可以用来控制循环。两者都需要一个条件来评估。

// 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 许可证的条款下发布。