lubosdz/yii2-template-engine

为 Yii 2 PHP 框架提供的零配置模板引擎。

安装: 99

依赖项: 0

建议者: 0

安全: 0

星级: 5

关注者: 3

分支: 1

开放问题: 0

类型:yii2-extension

1.0.9 2023-12-29 09:03 UTC

This package is auto-updated.

Last update: 2024-08-29 10:32:10 UTC


README

这是一个为 Yii2 PHP 框架提供的简单、快速且灵活的零配置 HTML 模板引擎。支持基本的控制结构(IF、FOR、SET)、导入子模板(IMPORT)、管道和动态指令、活动记录(AR)关系以及深层嵌套数组。它类似于 TwigBlade,但具有更少的开销、无依赖项且没有高级功能。

它可以用来渲染例如从 HTML 标记(在 WYSIWYG 编辑器中编辑)生成发票,并将其转换为 PDF 或 MS Word 文件 - 请参阅下面的示例。

无框架依赖的通用版本可在 lubosdz/html-templating-engine 找到。

安装

$ composer require "lubosdz/yii2-template-engine"

或通过 composer.json

"require": {
	...
	"lubosdz/yii2-template-engine": "^1.0",
	...
},

基本用法

在 Yii 应用程序内部初始化模板引擎

use lubosdz\yii2\TemplateEngine;
$engine = new TemplateEngine();

或在 app/config/main.php 中注册为组件

...
'components' => [
	'engine' => [
		'class' => 'lubosdz\yii2\TemplateEngine',
	]
]
...

$engine = Yii::$app->template;

使用方法 $engine->render($html [, $values]) 生成 HTML 输出

  • $html = 带有占位符(如 <h1>{{ processMe }}</h1>)的源 HTML 标记。此参数也可以是路径别名,以 @ 开头,用于加载现有 HTML 文件,例如 @app/templates/invoice.html
  • $values = 像对 [processMe => value] 这样的值数组,将注入到或评估在占位符内部

一旦生成输出,可以将其提供给例如 PDFMS Word 渲染器,以生成相应的 PDF 或 MS Word 文件。

简单占位符

模板引擎收集提供的 HTML 标记中的所有 占位符,并尝试用匹配的 $values 替换它们或作为控制结构评估。默认情况下,未处理的占位符保持未更改。此行为适合开发。在生产中,设置 $engine->setForceReplace(true) 可以将未处理的占位符替换为空字符串。

$html = 'Hello <b>{{ who }}</b>!';
$values = ['who' => 'world'];
echo $engine->render($html, $values);
// output: "Hello <b>world</b>!"

内置指令

模板引擎提供了一些通用的方法,用于基本的日期、时间和字符串操作 - 指令。这些可以在提供的 HTML 标记中直接引用。指令使用管道操作符 | 在占位符内链式操作。

$html = $engine->render('Generated on {{ today }} at {{ time }}.');
// output - respecting EN locale: "Generated on Dec 31, 2021 at 11:59pm."
// output - respecting SK/CS locale: "Generated on 31. 12. 2021 at 23:59."

echo $engine->render('Meet me at {{ now(7200) | time }}.');
// output example, if now is 8:30am: "Meet me at 10:30am." (shift +2 hours = 7200 secs)

echo $engine->render('My name is {{ user.name | escape }}.', ['user' => [
	'name' => '<John>',
]]);
// filtered output: "My name is &lt;John&gt;."

echo $engine->render('Hello {{ user.name | truncate(5) | e }}.', ['user' => [
	'name' => '<John>',
]]);
// truncated and filtered output: "Hello &lt;John...."

针对 Yii 2 框架特定,如果占位符中引用了对象属性以及相关的 模型,它们将自动收集。

$html = $engine->render('Hello {{ customer.name }}.', [
	'customer' => Customer::findOne($id), // find active record
]);
// output e.g.: "Hello John Doe."

$html = $engine->render('Address is {{ customer.address.city }} {{ customer.address.zip }}.', [
	'customer' => Customer::findOne($id), // Customer has defined relation to object `Address`
]);
// output e.g.: "Address is Prague 10000."

其他内置指令

// trim - standard PHP trim function
$html = $engine->render('Hello {{ username | trim }} ', [
	'username' => '   John Doe!   ',
]);
// output "Hello John Doe!"

$html = $engine->render('Hello {{ username | trim(" !eo") }}', [
	'username' => '   John Doe!   ',
]);
// output "Hello John D"

// replace - match simple string or regular expression (REGEX)
$html = $engine->render('HELLO {{ name | replace (BADBOY, GOODBOY) }}!', [
	'name' => 'BADBOY'
]);
// output "HELLO GOODBOY"

// nl2br - new lines to brackets
$html = $engine->render('NOTES: {{ notes_textarea | nl2br }}', [
	'notes_textarea' => "first line ...\nsecond line\n- last line -"
]);
// output:
"NOTES: first line ...
<br>second line
<br>- last line -
"

// concatenation - joining strings
$html = $engine->render('Order #{{ order_id | concat("by user"; " - ") | concat(customer.name) }}', [
	'order_id' => "123",
	'customer' => [
		'name' => 'John Doe',
	],
]);
// output "Order #123 by customer - John Doe"

动态指令

动态指令允许将自定义的 匿名函数 绑定到占位符。它们可以在运行时通过两个步骤添加,极大地扩展了引擎的灵活性。

  • 定义指令,例如 $engine->setDirective(directiveName, function($arg){ ... })
  • 在占位符内调用指令,例如 {{ user.name | directiveName($arg) }}

在下面的示例中,我们将附加一个名为 coloredText 的动态指令,并使用自定义内联 CSS 渲染输出。

// attach dynamic directive (anonymous function) accepting 2 arguments
$engine->setDirective('coloredText', function($text, $color){
	return "<span style='color: {$color}'>{$text}</span>";
});

// process template - we can set different color in each call
echo $engine->render("This is {{ output | coloredText(yellow) }}", [
	'output' => 'colored text',
]);
// output: "This is <span style='color: yellow'>colored text</span>"

注意:动态指令(如上例中的$text)的第一个参数总是来自上一个管道操作的值。

IF ... ELSEIF ... ELSE ... ENDIF

支持控制结构 IF ... ELSEIF ... ELSE ... ENDIF

$templateHtml = "
{{ if countOrders > 0 }}
	<h3>Thank you!</h3>
{{ else }}
	<h3>Sure you want to leave?</h3>
{{ endif }}
";

$values = [
	'countOrders' => count(Customer::findOne($id)->orders); // e.g. 3
];

echo $engine->render($templateHtml, $values);
// output e.g.: "<h3>Thank you!</h3>" - if some order found

FOR ... ELSEFOR ... ENDFOR

结构 FOR ... ELSEFOR ... ENDFOR 将创建循环

$templateHtml = "
<table>
{{ for item in items }}
	{{ SET subtotal = item.qty * item.price * (100 + item.vat) / 100 }}
	<tr>
		<td>#{{ loop.index }}</td>
		<td>{{ item.description }}</td>
		<td>{{ item.qty }}</td>
		<td>{{ item.price | round(2) }}</td>
		<td>{{ item.vat | round(2) }}%</td>
		<td>{{ subtotal | round(2) }} &euro;</td>
	</tr>
	{{ SET total = total + subtotal }}
{{ endfor }}
</table>
<p>Amount due: <b> {{ total | round(2) }} Eur</b></p>
";

$values = [
	'items' => [
		['description' => 'Item one', 'qty' => 1, 'price' => 1, 'vat' => 10],
		['description' => 'Item two', 'qty' => 2, 'price' => 2, 'vat' => 20],
		['description' => 'Item three', 'qty' => 3, 'price' => 3, 'vat' => 30],
		['description' => 'Item four', 'qty' => 4, 'price' => 4, 'vat' => 40],
	]
];

$html = $engine->render($templateHtml, $values);
// outputs valid HTML table with items e.g.: "<table><tr><td>#1</td><td> ..."

以下辅助变量在每个循环内部可访问

  • loop.index ... (int) 从1开始的迭代计数器
  • loop.index0 ... (int) 从0开始的迭代计数器
  • loop.length ... (int) 总的项目/迭代次数
  • loop.first ... (bool) 在第一次迭代时为true
  • loop.last ... (bool) 在最后一次迭代时为true

SET命令

允许操作局部模板变量,如总计数

{{ SET subtotal = item.qty * item.price * (100 + item.vat) / 100 }}
{{ SET total = total + subtotal }}

请参阅FOR下的示例。

注意:简写语法+=,例如SET total += subtotal是不支持的。

IMPORT命令

允许导入其他模板(子模板)。也支持从子模板中导入子模板。出于安全原因,导入的子模板必须位于模板目录(例如../templates/header.html)或子目录(例如../templates/invoice/header.html)内。这允许有效地构建和维护模板集。尝试从模板目录外部加载模板将引发错误。

首先,明确设置模板目录或通过通过加载模板别名进行设置

// set the template directory root explicitly
$engine->setDirTemplates('/abs/path/to/templates');

// template directory will be set implicitly as the parent of `invoice.html`
// note: engine's method "render()" supports Yii's aliases, so if supplied string
//       starts with ampersand `@` it is assuming template absolute path
$htmlInvoice = $engine->render('@templates/invoice.html');

然后在处理后的模板中添加import命令

<h3>Invoice No. 20230123</h3>
{{ import invoice_header.html }}
{{ import invoice_body.html }}
{{ import _partial/version_2/invoice_footer.html }}
<p>Generated on ...</p>

配置模板引擎

模板引擎自带大多数典型预配置设置。在许多情况下,可能需要更改默认行为。该引擎允许更改

  • 指令中的参数分隔符
  • 启用/禁用错误日志记录
  • 配置空或未处理的占位符的替换

设置指令中的参数分隔符

默认情况下,该引擎使用分号;,这是较不常见且不太可能与应用文本冲突。它可以设置为更常见的逗号,,如下设置

$engine->setArgSeparator(",");

// then use it also in placeholders and directives
$engine->render("{{ user | truncate(5, '..') }}", ["user" => "John Doe"]);

请注意,对于解析失败的占位符,引擎将忽略它们。请参阅测试以获取详细行为。

启用/禁用错误日志记录

默认情况下,引擎将错误记录到系统日志中。通常,这些可能是例如未处理的占位符(表示未提供值)或占位符解析失败。在开发过程中,强烈建议启用此日志记录。然而,在生产中,可能更希望通过设置将其关闭

$engine->setLogErrors(false);

替换空或未处理的占位符

默认情况下,引擎不替换任何未处理的或空的占位符。这允许在开发期间快速发现模板中的问题。通过定义替换为字符串,我们可以强制引擎将此字符串插入到所有空或未处理的占位符的输出中。以下是一些典型和有效的替换选项

// default - do not process any empty placeholders
// generated map will set for missed placeholders NULL
$engine->setForceReplace(false);

// yes, replace empty placeholders with empty string
$engine->setForceReplace(true);
$engine->setForceReplace("");

// yes, replace empty placeholders with 5 dots
$engine->setForceReplace(".....");

// yes, replace empty placeholders with HTML entity to retain spacing
$engine->setForceReplace("&nbsp;");

读取模板资源

处理整个模板后,可能需要存储一些处理后的数据。通常,我们可能希望在未来用相同的值重新填充模板并将当前数据存储到数据库中。该引擎允许读取生成的资源。

list($map, $placeholders, $values, $html) = $engine->getResources();

这将返回重建模板所需的所有数据

  • $map ... placeholder => processed value的数组对
  • $placeholders ... placeholder => directive的数组对
  • $values ... 参数的数组
  • $html ... 处理前的原始HTML字符串

渲染PDF或MS Word文件

渲染引擎允许用户在不具备任何编程知识的情况下安全地更改输出。为了增加额外价值,我们还可以将渲染的HTML输出转换为看起来专业的PDFMS Word文件。

// first process HTML template and input values
$htmlOutput = $engine->render($htmlMarkup, $values);

// then generate PDF file:
$pdf = new \TCPDF();
$pdf->writeHTML($htmlOutput);
$path = $pdf->Output('/save/path/my-invoice.pdf');

// or generate MS Word file:
$word = new \PhpOffice\PhpWord\PhpWord();
$section = $word->addSection();
\PhpOffice\PhpWord\Shared\Html::addHtml($section, $htmlOutput);
$writer = \PhpOffice\PhpWord\IOFactory::createWriter($word, 'Word2007');
$writer->save('/save/path/my-invoice.docx');

运行测试

  • 在根目录中运行phpunit

技巧与注意事项

  • 更多示例,请参阅tests/TemplateEngineTest.php
  • NuSphere的PhpEd的调试模式下通过phpunit运行测试

使用-d参数注入调试参数,例如:> phpunit -d DBGSESSID=12655478@127.0.0.1:7869 %*

  • 添加功能 - 简单地扩展TemplateEngine
class MyRenderer extends \lubosdz\yii2\TemplateEngine
{
	// your code, custom directives, override parent methods ..
}

变更日志

1.0.9 - 发布于2023-12-29

  • 从表达式翻译器中排除部分解析的占位符

1.0.8 - 发布于2023-12-11

  • 重构ELSEFOR行为 - 如果没有项目可循环,则条件将适用
  • 改进解析REGEX表达式以实现更精确的匹配
  • 占位符键的翻译现在使用REGEX边界\b以避免命名冲突
  • 添加了内置指令replace,例如:"{{ name | replace(WHAT, REPLACE) }}"
  • IF条件中支持原子布尔值,例如:{{ if cars }} ... {{ endif }}
  • 添加了测试,并改进了文档

1.0.7 - 发布于2023-11-14

  • 修复了以零开始的数值的eval错误(应转换为字符串)

1.0.6 - 发布于2023-10-22

  • 支持通过{{ import file }}导入子模板
  • 修复了在指令dir_round中将格式化数字转换为有效的PHP数字的问题

1.0.5 - 发布于2023-09-18

  • 支持配置参数分隔符(除默认的分号";"之外)
  • 解决深层树参数键冲突
  • 添加了内置指令concat,trim
  • 添加了测试,改进了文档,并添加了类型提示

1.0.4 - 发布于2023-09-05

  • 支持PHP 8.2
  • 正确检测内置指令中的有效Datetime字符串
  • forceReplace现在除了布尔值之外还可以接受字符串作为替换值
  • 修复了查找小写AR模型的问题
  • 修复了IF测试
  • 改进了文档

1.0.3 - 发布于2022-08-04

  • 添加了对通过路径别名加载HTML的支持,例如:$engine->render('@app/templates/invoice.html', $data)
  • 微小的文档改进

1.0.2 - 发布于2022-02-01

  • 修复了与PHP 8.1的兼容性问题

1.0.1 - 发布于2021-12-30

  • 修复了PHP 7.0 - 8.0的测试
  • 改进了文档

1.0.0 - 发布于2021-12-13

  • 初始发布