nbish11 / transphporm
模板的新方法
Requires
- php: ^7.3 || ^8.0
Requires (Dev)
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2024-09-19 01:35:40 UTC
README
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 是不同的
项目目标
- 完全将标记与处理逻辑分开。(模板中不包含
if
语句或循环!) - 尽可能遵循 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 个原因
-
向任意元素写入内容。在传统的模板引擎中,设计师需要在模板中放置标记,例如
{{name}}
,以在模板中注入内容的地方。在 Transphporm 中,设计师无需担心特定内容是否会替换(实际上使用str_replace
)。相反,Transphporm 允许开发者将内容写入页面上的任何 HTML 元素,设计师可以专注于设计,而不是担心可能添加的内容。 -
任何内容都可以是部分模板。传统的模板引擎强制你将每个部分模板放入自己的文件中。这对设计师来说很不好,因为他们无法快速地看到部分模板在完整布局中的样子。在 Transphporm 中,设计师可以使用完整的 HTML 文件,开发者可以从任何文件中提取任何元素作为部分模板。
-
可重用的显示逻辑。因为显示逻辑放在单独的外部文件中,你可以使用相同的显示逻辑与任意多的 XML 文件。这与外部 CSS 文件和 HTML 中的
style=
属性之间的区别。 -
在客户端或服务器上渲染模板。TSS 格式几乎不涉及 PHP 代码,类似于传统的模板引擎;它是一种自定义格式,不依赖于 PHP。因此,你可以使用 Transphporm 在服务器上或使用 JavaScript 实现 Tranjsform 在浏览器中渲染 XML 文件和 TSS 文件。
-
如果你使用过 CSS,Transphporm 很容易学习。Transphporm 与 CSS 语法非常相似,并使用了一些相同的词汇。如果你对 CSS 有基本的了解,你将能够轻松地学习使用 Transphporm!
Transphporm 为设计师和开发者提供了一种前所未有的灵活性,这在传统的模板引擎中是不可能实现的。
要求
Transphporm 至少需要 PHP 7.3 才能运行,并且完全支持到 PHP 8.1。
安装
安装 Transphporm 的首选方法是使用 Composer。Transphporm 可在 Packagist 上找到
level-2/transphporm
但是,如果你不想使用 Composer,你可以手动安装 Transphporm
- 下载并解压 Transphporm 到你的项目中
- 使用符合 PSR-0 规范的自动加载器,如 Axel
- 使用自动加载器注册 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()
的返回值是一个数组,包含一个 body
和 headers
。其中 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 有两种缓存类型,都需要启用
-
缓存 TSS 和 XML 文件。这可以防止在每次渲染模板时解析它们。即使您不打算使用
update-frequency
(见下文),启用此功能也是值得的。 -
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.tss
和 home.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)