level-2 / transphporm
模板的新方法
Requires
- php: >=7.0.0
Requires (Dev)
- phpunit/phpunit: ^5.7.20
This package is auto-updated.
Last update: 2024-08-29 03:41:58 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
)。
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允许开发者将内容写入页面上的任何HTML元素,设计师可以专注于设计而不是担心可能添加的内容。 -
任何内容都可以是部分模板。传统的模板引擎要求你必须将每个部分放入自己的文件中。这对设计师来说很糟糕,因为他们不能快速轻松地看到部分在完整布局中的样子。使用Transphporm,设计师可以与完整的HTML文件一起工作,开发者可以从任何文件中提取任何元素作为部分。
-
可重用显示逻辑。因为显示逻辑放置在自己的外部文件中,你可以用相同的显示逻辑使用多个XML文件。这是外部CSS文件与HTML中的
style=
属性之间的区别。 -
在客户端或服务器上渲染模板。TSS格式并不是像传统模板引擎那样的稍微抽象的PHP代码;它是一个自定义格式,不依赖于PHP。因此,你可以使用XML文件和TSS文件,在服务器上使用Transphporm渲染,或者在浏览器中使用JavaScript实现Tranjsform渲染。
-
如果你使用过CSS,Transphporm很容易学习。Transphporm遵循CSS语法并使用一些相同的词汇。即使你对CSS只有基本的了解,你也能轻松学会使用Transphporm!
Transphporm为设计师和开发者提供了前所未有的灵活性,这是传统模板引擎所无法实现的。
安装
安装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' ) ) )
要实际上将头部发送到浏览器,您需要手动调用头部命令
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
格式化器提供给日期,它将显示如下内容:
- "明天"
- "昨天"
- "两小时前"
- "3周前"
- "3个月后"
- "10年后"
字符串由区域设置指定。
导入其他文件
像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)添加了额外功能实现、错误修复和建议。