lubosdz/html-templating-engine

一个快速且灵活的PHP HTML模板引擎,无需配置和依赖。

1.0.5 2023-12-29 09:03 UTC

This package is auto-updated.

Last update: 2024-08-29 10:34:48 UTC


README

简单、快速且灵活的HTML模板引擎,无需配置和依赖。支持基本控制结构(IF、FOR、SET、IMPORT)、动态指令和公共对象属性。它与TwigBlade类似,但开销更小,没有依赖,且没有高级功能。

它可以用来渲染HTML标记,例如发票或合同,这些标记是在所见即所得编辑器中编辑的,并且可以选择将其转换为PDF或MS Word文件 - 请见下面的示例。针对Yii2框架的特定功能版本可以在lubosdz/yii2-template-engine找到。

安装

$ composer require "lubosdz/html-templating-engine"

或者通过 composer.json

"require": {
	...
	"lubosdz/html-templating-engine": "^1.0",
	...
},

基本用法

初始化模板引擎

use lubosdz\html\TemplatingEngine;
$engine = new TemplatingEngine();

// optionally adjust date or time formatting
$engine->defaultDateFormat = "d/m/Y";
$engine->defaultTimeFormat = "H:i";

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

  • $html = 带有占位符的源HTML标记,例如 <h1>{{ processMe }}</h1>
  • $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: "Generated on 31/12/2023 at 23:59."

echo $engine->render('Meet me at {{ now(7200) | time }}.');
// output example, if now is 8:30: "Meet me at 10:30." (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...."

引擎还支持访问对象中的公共属性。例如,我们可以访问对象 "Customer" 中的公共属性 "name"。

$customer = Customer::find(123); // this will find some customer ID. 123
$customer->name = 'John Doe';

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

其他内置指令

// 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>"

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

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 }}
";

$orders = $customer->findOrders(); // will return array of orders
$values = [
	'countOrders' => count($orders);
];

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)内。这允许有效地构建和维护模板集。尝试从模板目录之外加载模板将引发错误。

首先设置模板目录,然后通过提供加载的HTML内容或模板文件的绝对路径来处理模板

// set abs. path to template file
$pathInvoice = "/app/templates/invoice.html";

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

// A/ process the template by supplying loaded content
$htmlInvoice = $engine->render(file_get_contents($pathInvoice));

// B/ or process template by supplying abs. path to template inside template directory
$htmlInvoice = $engine->render($pathInvoice);

然后在处理后的模板中添加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 .. 占位符和已处理值的对数组
  • $placeholders .. 占位符和指令的对数组
  • $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/TemplatingEngineTest.php以获取更多示例
  • NuSphere的PhpEd调试模式下通过phpunit运行测试

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

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

变更日志

1.0.5 - 发布于2023-12-29

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

1.0.4 - 发布于2023-12-11

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

1.0.3 - 发布于2023-11-22

  • 修复了以零开头的数字值的eval错误(应转换为字符串)
  • 支持通过{{ import file }}导入子模板
  • 支持通过提供模板文件的绝对路径来加载HTML内容
  • 改进了文档

1.0.2 - 发布于2023-09-18

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

1.0.1 - 发布于2023-09-05

  • 支持PHP 8.2
  • 正确检测内置指令中的有效Datetime字符串
  • forceReplace现在除了布尔值外还接受字符串作为替换值
  • 修复了IF测试

1.0.0,发布于2022-02-01

  • 初始发布