nbish11/transphporm

模板的新方法

v2.0.1 2022-06-18 01:46 UTC

README

Scrutinizer-CI

Gitter

Transphporm 是 PHP 模板的新方法。让我们面对现实,PHP 模板真的很糟糕,因为它涉及到这样的代码

<ul>
<?php foreach ($users as $user) : ?>
	<li><?= $user->name; ?></li>
<?php endforeach; ?>
</ul>

或者这个混乱的变种

<ul>
{% for user in users %}
	<li>{{ user.username|e }}</li>
{% endfor %}
</ul>

或者

<ul>
	{users}
		<li>{user.name}</li>
	{/users}
</ul>

为什么这很糟糕?它将逻辑与模板混合在一起。模板中混合了处理指令。在中间的例子中,它几乎只是抽象的 PHP 代码。编写模板的人也负责编写显示逻辑和理解提供的数据结构。

这样的模板系统仍然将逻辑和标记混合在一起,这是他们试图避免的事情。

这相当于 <h1 style="font-weight:bold">标题</h1>,因为它混合了两个非常不同的关注点。

Transphporm 是不同的

项目目标

  1. 完全将标记与处理逻辑分开。(模板中不包含 if 语句或循环!)
  2. 尽可能遵循 CSS 概念和语法。(这对于任何已经了解 CSS 的人来说都很容易学习。)

使用 Transphporm,设计者只需提供一些包含一些占位符数据的原始 HTML 或 XML。(设计师更愿意看到 lorem ipsum 而不是在他们的设计中看到 {{description}}!)

<ul>
	<li>User name</li>
</ul>

它是纯标记,没有任何处理指令。然后,Transphporm 会用您想要的真实数据替换占位符数据。

但是处理指令在哪里?Transphporm 跟随 CSS 的领导。所有的处理逻辑都存储在外部的 "转换样式表" 中,这是一个完全独立的文件,包含完全可重用的处理指令。

在最基本的情况下,Transphporm 通过提供样式表和 HTML/XML 字符串来工作。

Transphporm 允许您将内容插入页面上的任何元素。传统的模板引擎迫使您在标记中放置标记,然后将在内容中使用 str_replace 替换(实际上使用 str_replace)。

Transphporm 采用不同的方法,并允许您使用类似于 CSS 的语法插入内容。您不需要在模板中提供特殊标记;模板是普通的 HTML,没有任何特殊语法。然后可以使用 CSS 风格的选择器定位页面上的元素。

例如,这个样式表

h1 {content: "My Title";}

将任何 <h1> 标签的内容设置为 "我的标题"。给定以下代码

$xml = '<h1>Original Title</h1>';

$tss = 'h1 {content: "Replaced Title"; }';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

输出将是

<h1>Replaced Title</h1>

Transphporm.Builder 的参数可以是 HTML/XML 和 TSS 字符串,或要加载的文件名。

//Load files instead of strings, the base path is the current working directory (getcwd())
$template = new \Transphporm\Builder('template.xml', 'stylesheet.tss');

这提供了前所未有的灵活性。您不必考虑内容中的哪些部分可能是动态的,并在模板中正确位置添加像 {{user.name}} 这样的事情,这些关注点可以在设计模板时忽略,并用一些静态内容代替。然后 Transphporm 可以替换页面上的任何内容。这允许您重用模板。有时您可能会替换一些内容,有时您可能会使用模板的默认内容!

使用 Transphporm 的 5 个原因

  1. 向任意元素写入内容。在传统的模板引擎中,设计师需要在模板中放置标记,例如 {{name}},以在模板中注入内容的地方。在 Transphporm 中,设计师无需担心特定内容是否会替换(实际上使用 str_replace)。相反,Transphporm 允许开发者将内容写入页面上的任何 HTML 元素,设计师可以专注于设计,而不是担心可能添加的内容。

  2. 任何内容都可以是部分模板。传统的模板引擎强制你将每个部分模板放入自己的文件中。这对设计师来说很不好,因为他们无法快速地看到部分模板在完整布局中的样子。在 Transphporm 中,设计师可以使用完整的 HTML 文件,开发者可以从任何文件中提取任何元素作为部分模板。

  3. 可重用的显示逻辑。因为显示逻辑放在单独的外部文件中,你可以使用相同的显示逻辑与任意多的 XML 文件。这与外部 CSS 文件和 HTML 中的 style= 属性之间的区别。

  4. 在客户端或服务器上渲染模板。TSS 格式几乎不涉及 PHP 代码,类似于传统的模板引擎;它是一种自定义格式,不依赖于 PHP。因此,你可以使用 Transphporm 在服务器上或使用 JavaScript 实现 Tranjsform 在浏览器中渲染 XML 文件和 TSS 文件。

  5. 如果你使用过 CSS,Transphporm 很容易学习。Transphporm 与 CSS 语法非常相似,并使用了一些相同的词汇。如果你对 CSS 有基本的了解,你将能够轻松地学习使用 Transphporm!

Transphporm 为设计师和开发者提供了一种前所未有的灵活性,这在传统的模板引擎中是不可能实现的。

要求

Transphporm 至少需要 PHP 7.3 才能运行,并且完全支持到 PHP 8.1。

安装

安装 Transphporm 的首选方法是使用 Composer。Transphporm 可在 Packagist 上找到

level-2/transphporm

但是,如果你不想使用 Composer,你可以手动安装 Transphporm

  1. 下载并解压 Transphporm 到你的项目中
  2. 使用符合 PSR-0 规范的自动加载器,如 Axel
  3. 使用自动加载器注册 Transphporm 的 src 目录。使用 Axel,这可以通过以下方式完成
require_once 'axel/axel.php';
$axel = new \Axel\Axel;
$axel->addModule(new \Axel\Module\PSR0('./path/to/Transphporm/src', '\\Transphporm'));

数据

通常无法在静态文件(如样式表)中指定内容。tss 格式还允许引用外部数据。这些数据提供给模板构建器的 output 方法,并可以使用 data() 函数在样式表中引用。这可以类似于 CSS 中的 url() 函数,因为它引用了外部资源。

$xml = '<h1>Original Title</h1>';

$data = 'My Title!';

$tss = 'h1 {content: data(); }';


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

输出

<h1>My Title!</h1>

大多数时候,你将需要与更复杂的数据结构一起工作。Transphporm 允许使用内置的数据函数从数据结构中读取数据

$data = new stdclass;

$data->title = 'My Title!';
$data->description = 'Description of the page...';


$xml = '
	<h1>Example Title</h1>
	<p>Example Description</p>
	';

$tss = '
	h1 {content: data(title);}
	p {content: data(description);}
';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

这将输出

<h1>My Title!</h1>
<p>Description of the page....</p>

内容

内容属性可以接受多个值,每个值可以是函数调用(如 data)或引号字符串,并将连接任何提供的值

$xml = '<h1>Original Title</h1>';

$data = 'My Title!'

$tss = 'h1 {content: "Title: ", data(); }';


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

输出

<h1>Title: My Title!</h1>

有关插入内容的更多信息,请参阅维基页面 基本用法:插入内容基本用法:外部数据

循环

回到用户列表示例,考虑以下数据结构

$users = [];

$user = new stdclass;
$user->name = 'Tom';
$user->email = 'tom@example.org';

$users[] = $user;


$user = new stdclass;
$user->name = 'Scott';
$user->email = 'scott@example.org';

$users[] = $user;

使用 Transphporm,用户列表可以生成如下所示

$xml = '<ul>
	<li>Name</li>
</ul>';


$tss = '
	ul li {repeat: data(users); content: iteration(name);}
';

$data = ['users' => $users];


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

代码 repeat 告诉 Transphporm 对于提供的每个数组元素重复选定的元素。

代码 data(users) 读取 PHP 中提供的 $data['users']

代码 iteration(name) 指向当前迭代的值并读取 name 属性。这段代码将输出

<ul>
	<li>Tom</li>
	<li>Scott</li>
</ul>

同样,iteration 可以读取特定值并在嵌套节点中使用

$xml = '<ul>
	<li>
		<h3>Name</h3>
		<span>email</span>
	</li>
</ul>';


$tss = '
	ul li {repeat: data(users);}
	ul li h3 {content: iteration(name);}
	ul li span {content: iteration(email);}
';

$data = ['users' => $users];


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

这将输出

<ul>
	<li>
		<h3>Tom</h3>
		<span>tom@example.org</span>
	</li>
	<li>
		<h3>Scott</h3>
		<span>scott@example.org</span>
	</li>
</ul>

有关循环的更多信息,请参阅 Wiki 页面基本用法:循环

删除块

直接从 CSS 语法中提取,Transphporm 支持 display: none,这将实际上从文档中完全删除元素。

$xml = '<ul>
	<li>
		<h3>Name</h3>
		<span>email</span>
	</li>
</ul>';


$tss = '
	ul li {repeat: data(users);}
	ul li h3 {content: iteration(name);}
	ul li span {display: none}
';

$data = ['users' => $users];


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

输出

<ul>
	<li>
		<h3>Tom</h3>
	</li>
	<li>
		<h3>Scott</h3>
	</li>
</ul>

注意,这在与迭代值伪元素一起使用时非常有用。有关条件逻辑的更多示例,请参阅 Wiki 页面基本用法:条件逻辑

CSS 选择器

Transphporm 支持以下 CSS 选择器

tagName #id .className tagName.className direct > descendant [attribute] [attribute=value] [attribute!=value]

并且这些中的任何一个都可以进行链式调用

main .post > .author[data-admin=true] 将匹配任何具有类名 author 的元素,该元素将 data-admin 属性设置为 true,并且直接位于类名为 post 的元素内部,该元素位于 <main> 元素内部。

有关支持的完整选择器列表和每个选择器的示例,请参阅 Wiki 页面基本用法:CSS 选择器

伪元素

Transphporm 还支持几个伪元素

:before:after,这允许将内容写入节点的前面或后面。

:nth-child(n) :nth-child(odd) :nth-child(even)

有关每个这些伪元素的示例,请参阅 Wiki 页面基本用法:伪元素

迭代值

Transphporm 还可以检查元素的迭代数据。当您想要根据迭代值的特定内容隐藏特定块时,这尤其有用

格式为

element:iteration[name=value] {}

这将选择任何迭代内容的 name 属性等于 value 的元素。

以下代码将隐藏任何类型为 'Admin' 的用户。

$users = [];

$user = new stdclass;
$user->name = 'Tom';
$user->email = 'tom@example.org';
$user->type = 'Admin';
$users[] = $user;


$user = new stdclass;
$user->name = 'Scott';
$user->email = 'scott@example.org';
$user->type = 'Standard';
$users[] = $user;

$user = new stdclass;
$user->name = 'Jo';
$user->email = 'jo@example.org';
$user->type = 'Standard';
$users[] = $user;



$xml = '
<ul>
	<li>
		<h3>Name</h3>
		<span>email</span>
	</li>
</ul>';


$tss = '
	ul li {repeat: data(users);}
	ul li:iteration[type='Admin'] {display: none;}
	ul li h3 {content: iteration(name);}
	ul li span {content: iteration(email);}
';

$data = ['users' => $users];


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

输出

<ul>
	<li>
		<h3>Scott</h3>
		<span>scott@example.org</span>
	</li>
	<li>
		<h3>Jo</h3>
		<span>jo@example.org</span>
	</li>
</ul>

向属性写入

与 CSS 不同,Transphporm 选择器允许直接选择单个属性以设置其值。这可以通过使用伪元素 :attr(name) 来完成,它选择匹配元素上的属性。

element:attr(id)

将选择元素的 ID 属性。

工作示例

$users = [];

$user = new stdclass;
$user->name = 'Tom';
$user->email = 'tom@example.org';
$users[] = $user;


$user = new stdclass;
$user->name = 'Scott';
$user->email = 'scott@example.org';
$users[] = $user;



$xml = '
<ul>
	<li>
		<h3>Name</h3>
		<a href="mailto:email">email</a>
	</li>
</ul>';


$tss = '
	ul li {repeat: data(users);}
	ul li a {content: iteration(email);}
	ul li a:attr(href) {content: "mailto:", iteration(email);}
';

$data = ['users' => $users];


$template = new \Transphporm\Builder($xml, $tss);

echo $template->output($data)->body;

注意这使用了多个 content 属性值来连接完整的 URL 和 mailto

输出

<ul>
	<li>
		<h3>Tom</h3>
		<a href="mailto:Tom@example.org">Tom@example.org</a>
	</li>
	<li>
		<h3>Scott</h3>
		<a href="mailto:scott@example.org">scott@example.org</a>
	</li>
</ul>

从属性读取

也可以使用内容属性内的 attr(name) 从属性读取。

$xml = '
<h1 class="foo">bar</h1>
';

$tss = 'h1 {content: attr(class);}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

输出

<h1 class="foo">foo</h1>

HTTP 头部

Transphporm 支持设置 HTTP 头部。您必须针对页面上的元素进行操作,例如 HTML,并且可以使用 :header 伪元素来设置 HTTP 头部。例如,可以这样做来执行重定向

html:header[location] {content: "/redirect-url"; }

Transphporm 不会直接写入 HTTP 头部。函数 output() 的返回值是一个数组,包含一个 bodyheaders。其中 body 是渲染的 HTML 代码,而 headers 包含已设置的任何 HTTP 头部。

$xml = '<html><div>Foo</div></html>';
$tss = 'html:header[location] {content: "/redirect-url"; }';

$template = new \Transphporm\Builder($xml, $tss);

print_r($template->output());

将打印

Array (
	'body' => '<html><div>foo</div></html>',
	'headers' => Array (
		Array (
			[0] => 'location',
			[1] => '/redirect-url'
		)
	)
)

要实际上将头部发送到浏览器,您需要手动调用 header 命令

foreach ($template->output()->headers as $header) {
	header($header[0] . ': ' . $header[1]);
}

有条件地应用 HTTP 头部

在大多数情况下,您可能希望有条件地显示头部。例如

  • 成功时进行重定向。
  • 当找不到记录时发送 404 头部。

要执行此操作,您可以使用条件数据查找

class Model {
	public function getProduct() {
		return false;
	}
}



$tss = 'html:data[getProduct='']:header[status] {content: '404'}

$xml = '<html></html>';

$data = new Model;

$template = new \Transphporm\Builder($xml, $tss);

$output = $template->output($data);

print_r($output->headers)

打印

Array (
	[0] => 'status',
	[1] => '404'
)

要使用此功能,您应该使用此状态调用内置的 PHP http_response_code 函数

foreach ($template->output()->headers as $header) {
	if ($header[0] === 'status') http_response_code($header[1]);
	else header($header[0] . ': ' . $header[1]);
}

Transphporm 不会发送任何头部

Transphporm 默认不会向浏览器发送任何输出。这是为了最大程度地提高灵活性。您仍然需要手动发送头信息和回显主体。

数据格式化

Transphporm 支持将其输出数据进行格式化。格式化的语法如下

h1 {content: "content of element"; format: [NAME-OF-FORMAT] [OPTIONAL ARGUMENT OF FORMAT];}

字符串格式化

Transphporm 目前支持以下字符串格式

  • 大写
  • 小写
  • 首字母大写

示例

字符串格式:大写

$xml = '
<h1> </h1>
';

$tss = 'h1 {content: "TeSt"; format: uppercase}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

打印

<h1>TEST</h1>

字符串格式:小写

$xml = '
<h1> </h1>
';

$tss = 'h1 {content: "TeSt"; format: lowercase}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

打印

<h1>test</h1>

字符串格式:首字母大写

$xml = '
<h1> </h1>
';

$tss = 'h1 {content: "test"; format: titlecase}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

打印

<h1>Test</h1>

数字格式

Transphporm 支持使用 decimal 格式将数字格式化为指定的小数位数。您可以指定小数位数

数字格式:decimal

$xml = '
<h1> </h1>
';

$tss = 'h1 {content: "11.234567"; format: decimal 2}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

打印

<h1>1.23</h1>

区域设置

对于日期、时间和货币格式化,Transphporm 支持区域设置。目前只提供了 enGB,但您可以编写自己的区域设置。

要设置区域设置,请使用 builder::setLocale 方法。这可以接受一个区域设置名称,例如位于 Formatter/Locale/{name}.json 内的区域设置,例如

$template = new \Transphporm\Builder($xml, $tss);
$template->setLocale('enGB');

目前只支持 enGB。或者,您也可以提供一个与 Formatter/Locale/enGB.json 中使用的格式匹配的数组。

日期格式

Transphporm 支持格式化日期。您可以选择引用一个 \DateTime 对象或字符串。如果可能,字符串将被自动转换为日期

$xml = '
<div> </div>
';

$tss = 'div {content: "2015-12-22"; format: date}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

这将使用区域设置中指定的日期格式来格式化日期。对于 enGB,这是 d/m/Y

<div>22/12/2015</div>

或者,您也可以指定格式作为格式化器的第二个参数

$xml = '
<div> </div>
';

$tss = 'div {content: "2015-12-22"; format: date "jS M Y"}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;
<div>22nd Dec 2015</div>

您也可以使用 time 来格式化,默认为区域设置中的 H:i

$xml = '
<div> </div>
';

$tss = 'div {content: "2015-12-22 14:34"; format: time}';

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;
<div>14:34</div>

相对时间

您可以向日期提供一个 relative 格式化器,这将显示如下内容

  • "明天"
  • "昨天"
  • "两个小时前"
  • "三个星期前"
  • "三个月后"
  • "十年后"

这些字符串由区域设置指定。

导入其他文件

类似于 CSS,Transphporm 支持导入其他 TSS 文件的 @import

imported.tss

h1 {content: "From imported tss"}
$xml = '
<h1> </h1>
<div> </div>
';

$tss = "
	@import 'imported.tss';
	div {content: 'From main tss'}
";

$template = new \Transphporm\Builder($xml, $tss);

echo $template->output()->body;

输出

<h1>From imported tss</h1>
<div>From main tss</div>

缓存

Transphporm 有两种缓存类型,都需要启用

  1. 缓存 TSS 和 XML 文件。这可以防止在每次渲染模板时解析它们。即使您不打算使用 update-frequency(见下文),启用此功能也是值得的。

  2. update-frequency 这是一个属性,允许您在指定的时间间隔内更新元素。

要启用缓存,您必须创建(或使用)一个实现 PHP 内置 \ArrayAccess 接口的缓存类,例如 Level-2/SimpleCache。然后将实例分配给构建器

$cache = new \SimpleCache\SimpleCache('/tmp');

$template = new \Transphporm\Builder($xml, $tss);
$template->setCache($cache);

echo $template->output($data)->body;

这样做将自动启用文件缓存。一旦分配了缓存,TSS 文件仅在更新时才会解析。这可以节省在页面加载时解析 TSS 文件的时间,即使您不使用 update-frequency,这也是有价值的。

update-frequency

update-frequency 是 TSS 指令,用于描述给定 TSS 规则应运行的多频繁。在幕后,每次渲染模板时,Transphporm 都会将最终输出保存在缓存中,并根据 update-frequency 进行更改。例如

ul li {repeat: data(users); update-frequency: 10m}

这将每 10 分钟运行一次 TSS 规则。在幕后它是这样工作的

  • 渲染的模板被存储在缓存中。
  • 下次页面加载时,加载之前渲染的模板。
  • 如果计时器已过期,将在模板的缓存版本上再次运行重复/内容等指令,并更新模板。

这允许页面的不同部分以不同的频率更新。

MVC 中的缓存

如果您使用的是真正的 MVC(《不是 PAC,大多数框架实际使用的是 PAC)并且您正在将模型传递到视图中,如果模型作为 data 参数传递并且具有 getUsers 函数,Transphporm 可以调用它,并且只有在模板更新时才会执行查询。

ul li {repeat: data(getUsers); update-frequency: 10m}

大多数框架不会将模型传递到视图中,然而对于这样做的人来说,这允许两级缓存。只有在根据视图的超时更新视图时才会运行查询。

构建整个页面

Transphporm 采用自顶向下的方法来构建页面。大多数框架需要编写布局模板,然后将内容拉入其中。这使得根据每个页面更改布局变得非常困难。(至少您需要将一些代码添加到布局 HTML 中)。Transphporm 采用自顶向下的方法,而不是流行的自底向上的方法,其中子模板在布局中的特定位置插入。

您仍然有两个文件,一个是布局文件,另一个是内容文件,但 TSS 应用于 布局,这意味着 TSS 可以更改布局中的任何内容(添加脚本标签、添加 CSS、更改页面标题和元标签等)。

layout.xml

<!DOCTYPE HTML>
<html>
	<head>
		<title>My Website</title>
	</head>
	<body>
		<header>
			<img src="logo.png" />
		</header>

		<nav>
			<ul>
				<li><a href="/">Home</a></li>
				<li><a href="about.html">About</a></li>
				<li><a href="contact.html">Contact</a></li>
			</ul>
		</nav>

		<main>
			Main content

		</main>

		<footer>
			Copyright <span>year</span>
		</footer>
	</body>
</html>

然后是 home.xml

<?xml version="1.0"?>
<template>
	<p>Welcome to my website</p>
</template>

然后可以使用 TSS 文件来包含一个在另一个内部

home.tss

title {content: "My Site"}

main {content: template("home.xml")}

footer span {content: "now"; format: date "Y"}

然后使用以下代码将 <main> 元素的内容设置为存储在 home.xml 中的模板内容:

$template = new \Transphporm\Builder('layout.xml', 'home.tss');

echo $template->output()->body;

显然,您可以通过添加相关的 about.xml 和 TSS 来添加一个关于页面。

title {content: "About me"}

main {content: template("about.xml")}

footer span {content: "now"; format: date "Y"}
$template = new \Transphporm\Builder('layout.xml', 'about.tss');

echo $template->output()->body;

这里有一点重复,可以通过两种方式解决。

1) 将布局规则放在自己的文件中,例如 base.tss

footer span {content: "now"; format: date "Y"}

然后在 about.tsshome.tss 中导入它,例如:

@import "base.tss";

title {content: "About me"}

main {content: template("about.xml")}

2) 使用数据来外部描述相关部分

page.tss

title {content: data(title);}

main {content: template(data(page));}

footer span {content: "now"; format: date "Y"}
//Home template:

$template = new \Transphporm\Builder('layout.xml', 'page.tss');

$template->output(['title' => 'My Website', 'page' => 'home.xml'])->body;



//About template:
$template = new \Transphporm\Builder('layout.xml', 'page.tss');

$template->output(['title' => 'About Me', 'page' => 'about.xml'])->body;

这允许采用自顶向下的方法。大多数框架采用自底向上的方法,其中您首先构建布局,然后构建内容,并将一个的输出放入另一个中。这带来一个问题:如何为每个页面设置标题?或者也许在每个页面上包含不同的侧边栏?框架通常使用本质上全局变量来存储页面标题和任何布局选项。TSS 一次性构建整个页面,因此任何页面都可以更改布局的任何部分。

鸣谢

Transphporm 最初由 Tom Butler(@TomBZombie)开发。额外的功能、错误修复和建议来自

  • Richard Sollee(@solleer)
  • Nathan Bishop(@nbish11)