teein / html
PHP的虚拟DOM和模板引擎
Requires
- php: ^7.1
Requires (Dev)
- phpunit/phpunit: ^6.3
This package is auto-updated.
Last update: 2021-07-13 01:39:38 UTC
README
Teein/Html是一个基于React、XHP和Elm灵感的PHP虚拟DOM模板引擎。以下是亮点:
- 无需学习新语法 模板是用普通PHP编写的,它们的语法与HTML5非常相似。
- 可组合模板 创建模板的唯一方式是将它们从更简单的模板组合而成。可组合模板可以随着应用程序的扩展而扩展,而不会增加其复杂性。
- 不可变数据 模板是不可变的,因此是可预测的。一旦创建,就无法更改模板。然而,您可以使用流畅的getter/setter-api始终从一个现有的模板派生一个新的模板。
- 自动上下文处理 忘记
htmlspecialchars
和XSS漏洞,专注于您的模板,我们为您完成脏活。 - 默认压缩 默认情况下,HTML5输出被压缩以减少网络负载。没有其他任务或构建步骤需要配置。当然,您始终可以使用我们的
beautify
-函数获取美化后的输出。
入门
要求
Teein/Html需要至少PHP 7.1和Composer。
安装
当您的系统满足上述要求时,您可以通过输入以下命令安装Teein/Html:
composer require teein/html
Hello World
Teein/Html | HTML5 |
---|---|
显示此脚本的头部分<?php declare(strict_types = 1); namespace Teein\Html\Example; use function Teein\Html\Beautifier\beautify; use function Teein\Html\ToHtml\toHtml; use function Teein\Html\Document\document; use function Teein\Html\Text\text; use function Teein\Html\Elements\{html,head,meta,title,body,h1}; use function Teein\Html\Attributes\{lang,charset}; require __DIR__ . '/vendor/autoload.php'; |
|
echo toHtml(beautify(document( html(lang('en'))( head()( meta(charset('utf8')), title()(text('Hello World!')) ), body()( h1()(text('Hello World!')) ) ) ))); |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf8"> <title>Hello World!</titel> </head> <body> <h1>Hello World!</h1> </body> </html> |
希望这个例子能让你想起普通的HTML5。
让我们看看Teein/Html的语法如何与HTML5相似
- 我们有标签名:
html
、head
、body
、h1
、... - 我们有属性:
lang
、charset
、...
请注意,与HTML5的不同之处
- 我们使用括号而不是尖括号,并且它们位于不同的位置。我们使用一对括号来包裹属性,另一对括号来包裹元素的孩子。
- 我们没有关闭标签。
- 我们可以在模板中使用PHP表达式,并且它们将被评估。
- 我们需要在PHP文件顶部导入我们的依赖项。我们已从上述示例中折叠了顶部部分,这是一个展开隐藏内容并了解其中内容的绝佳时刻。
在我们的示例中还有更多可以发现
- 模板默认压缩,我们使用
beautify
-函数缩进我们的输出。 - 我们使用
toHtml
-函数从我们的虚拟DOM获取字符串表示。
可组合模板
“Hello World”示例看起来很容易管理,但在实际应用中,模板通常要复杂得多。为了应对应用日益增长的复杂性,我们采用了一个古老的概念:可组合性。这个想法是将简单的模板组合起来,以获得更大但同样简单的模板。例如,我们示例中的许多东西看起来都像是典型的HTML5模板。让我们看看我们如何重构示例,以便我们可以重用大部分模板,并适应其他场景。
可组合模板的主要成分是函数
。以下是重构的路线图:我们将把“hello-world-template”分成两个函数:让我们称它们为boilerplate
和content
。boilerplate
函数接受一个参数$content
,并将其包装成一个与上面完全相同的HTML5骨架。content
函数不接受任何参数,只是简单地返回标题。最后,我们将把这两个函数粘合在一起,得到与原始示例相同的输出。
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use Teein\Html\VirtualDom\{Node,Document}; use function Teein\Html\{Beautifier\beautify, Document\document}; use function Teein\Html\Elements\{html,head,meta,title,body}; use function Teein\Html\Attributes\{lang,charset};
function boilerplate (Node $content) : Document { return beautify( document( html(lang('en'))( head()( meta(charset('utf8')), title()( text('Hello World!') ) ), body()($content) ) ); }
在我们继续讨论content
函数之前,让我们简要地看看这个片段中包含的新内容。你可能注意到了类型注解或类型提示,就像在PHP中经常看到的那样。这是Teein/Html的另一个出色功能。函数的签名清楚地表明它只接受一个Node
作为输入,并返回一个Document
。一个Node
可以是Element
,如h1
或span
,也可以是Text
或Comment
。我们库中的所有工厂函数都带有类型注解,并确保您不会组合不兼容的函数。现在就足够了。让我们回到我们的运行示例。到目前为止,我们已经编写了boilerplate
函数,现在是时候转向content
函数了,现在应该很简单。
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use Teein\Html\VirtualDom\Element; use function Teein\Html\Text\text; use function Teein\Html\Elements\h1;
function content () : Element { return h1()(text("Hello World!")); }
顺便说一下,我们有时将像boilerplate
和content
这样的函数称为视图助手。注意视图助手是多么简单:它们只是函数。输入到输出的映射。我们没有需要继承的抽象基类或必须实现的接口。就是这样简单:函数。现在,应该很清楚我们如何将boilerplate
和content
组合在一起,以获得与原始“Hello World”示例相同的输出。为了确保,这里是我们新生的hello-world
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use function Teein\Html\ToHtml\toHtml;
echo toHtml( boilerplate( content() ) );
... 函数。希望你现在就能欣赏它们 :)
条件分支
与其他模板引擎不同,Teein/Html不引入特殊的语法来表示条件分支或循环。相反,我们使用PHP内置的语言特性来实现这些功能。然而,Teein/Html的功能接口需要的是表达式而不是语句。例如,PHP的if () {} else {}
结构是一个语句,但三元运算符$cond ? $foo : $bar
是一个表达式(如果你不喜欢三元运算符的语法,这里是一个提示:你始终可以定义自己的视图助手并给它们起有表达力的名字)。
让我们看看这在实践中是如何工作的。以下是如何将个性化问候语添加到模板中的示例。为了简洁起见,我们将修改现有的content
函数。
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use Teein\Html\VirtualDom\Element; use function Teein\Html\Text\text; use function Teein\Html\Elements\h1;
function content () : Element { return h1() ( text( isset($_GET['name']) ? "Hello {$_GET['name']}" : "Hello World!" ) ); }
请注意,我们没有在$_GET['name']
周围放置htmlspecialchars
。在大多数其他模板引擎中,这将会是一个安全问题。然而,Teein/Html会自动转义任何特殊字符,并在模板渲染时解除炸弹。这也为您节省了一些打字,让您可以专注于内容。
循环
对于if
语句的限制也适用于for
和while
循环。我们使用更强大的map
/reduce
函数来实现循环。再次,让我们看看一些代码。然而,“Hello World”示例让我感到厌烦。所以,让我们考虑一些更复杂的事情,比如制作一个我喜欢的漫画的列表视图。为此,假设我们有一个实现以下接口的漫画模型
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example;
interface Comic { public function getTitle() : string; public function getSuperhero() : string; public function getVillain() : string; }
此外,假设提供了一个包含此类漫画的数组变量$comics
。以下是一个这样的模板可能的样子
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use Teein\Html\VirtualDom\Element; use function Teein\Html\Text\text; use function Teein\Html\Elements\{ul,li,span}; use function Teein\Html\Attributes\{class_};
function comics (array $comics) : Element { return ul () ( ...array_map(function (Comic $comic) : Element { return li () ( span(class_('title'))(text($comic->getTitle())), span(class_('superhero'))(text($comic->getSuperhero())), span(class_('villlain'))(text($comic->getVillain())) ); }, $comics) ) }
在这里必须使用展开运算符(...
),因为从ul()
函数返回的函数不接受数组,而是一个可变参数序列,其中每个参数必须是Node
类型。展开运算符接收一个数组并生成所需的参数序列。
提示:嵌套匿名函数可能会变得繁琐。从积极的一面来看,匿名函数为我们提供了一个提示,我们可以在哪里将模板拆分成单独的视图助手。以下是重构后的版本
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use Teein\Html\VirtualDom\Element; use function Teein\Html\Text\text; use function Teein\Html\Elements\{li,span}; use function Teein\Html\Attributes\{class_};
function comic (Comic $comic) : Element { return li () ( span(class_('title'))(text($comic->getTitle())), span(class_('superhero'))(text($comic->getSuperhero())), span(class_('villlain'))(text($comic->getVillain())) ); }
显示此脚本的头部分
<?php declare(strict_types = 1); namespace Teein\Html\Example; use Teein\Html\VirtualDom\Element; use function Teein\Html\Elements\ul;
function comics (array $comics) : Element { return ul () ( ...array_map('comic', $comics) ); }
命名空间
Teein/Html不会注册任何全局定义,这意味着您必须在脚本的开头明确导入将要使用的内容。今天的大多数编辑器都提供自动工具来简化此过程。然而,一些工具很难检测到您命名空间内的函数。如果您正在使用coffein并且无法等待更好的工具,您可以在文件开头包含一个完整的导入列表。以下是它
<?php use Teein\Html\VirtualDom\{Attribute,Comment,Document,Element,Node,Text}; use function Teein\Html\{Beautifier\beautify,ToHtml\toHtml,Document\document,Text\text}; use function Teein\Html\Elements\{a,abbr,address,area,article,aside,audio,b,base,bdi,bdo,blockquote,body,br,button,canvas,caption,cite,code,col,colgroup,data,datalist,dd,del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,img,input,ins,kbd,label,legend,li,link,main,map,mark,math,menu,meta,meter,nav,noscript,object_,ol,optgroup,option,output,p,param,picture,pre,progress,q,rp,rt,ruby,s,samp,script,section,select,slot,small,source,span,strong,style,sub,summary,sup,svg,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,u,ul,var_,video,wbr}; use function Teein\Html\Attributes\{abbr_,accept,accept_charset,accesskey,action,allowfullscreen,allowpaymentrequest,allowusermedia,alt,as_,async,autocomplete,autofocus,autoplay,charset,checked,cite_,class_,color,cols,colspan,content,contenteditable,controls,coords,crossorigin,data_,datetime,default_,defer,dir,dirname,disabled,download,draggable,enctype,for_,form_,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http_equiv,id,inputmode,integrity,is,ismap,itemid,itemprop,itemref,itemscope,itemtype,kind,label_,lang,list_,loop,low,manifest,max,maxlength,media,method,min,minlength,multiple,muted,name,nomodule,nonce,novalidate,objectData,open,optimum,pattern,ping,placeholder,playsinline,poster,preload,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,selected,shape,size,sizes,slot_,span_,spellcheck,src,srcdoc,srclang,srcset,start,step,style_,tabindex,target,title_,translate,type,typemustmatch,updateviacache,usemap,value,width,workertype,wrap};
命名差异
我们试图为每个HTML5元素和属性提供具有相同名称的工厂。然而,有些情况下这是不可能的。PHP保留了一些单词,这意味着我们无法将它们用作工厂的名称。此外,HTML5有时会为属性和元素使用相同的名称。以下是具有非传统名称的工厂及其输出的完整列表。
Teein/Html | HTML5 |
---|---|
object_()() var_()() th(abbr_('anyalternativelabel'))() form(accecptcharset('utf8'))() link(as_('fetch')) q(cite_('https://example.com'))() div(class_('anyclass'))() object_(odata('https://example.com'))() div(data('custom', 'anyvalue'))() track(default_('default')) output(for_('anyid'))() button(form_('anyid'))() option(label_('anylabel'))() input(list_('anyid')) div(slot_('anyslot'))() col(span_('42')) div(style_('color: pink'))() div(title_('anytitle'))() meta(httpequiv('anyhttpheader')) |
<object></object> <var></var> <th abbr="anyalternativelabel"></th> <form accept-charset="utf8"> <link as="fetch"> <q cite="https://example.com"></q> <div class="anyclass"> <object data="https://example.com"></object> <div data-custom="anyvalue"> <track default="default"></track> <output for="anyid"></output> <button form="anyid"></button> <option label="anylabel"></option> <input list="anyid"> <div slot="anyslot"></div> <col span="42"> <div style="color: pink"></div> <div title="anytitle"></div> <meta http-equiv="anyhttpheader"> |
进一步阅读和获取帮助
本指南是一个非常早期的草案,将在未来扩展。目标是记录Teein/Html的每个特性和一些最佳实践。然而,这并不是一天的工作。在此期间,您可以自由地探索代码库,它本身也得到了仔细的文档。如果您有任何具体问题,我们很乐意回答,只需创建一个问题。
贡献
为了培养一个开放和友好的环境,我们作为贡献者和维护者承诺,无论年龄、体型、残疾、种族、性别认同和表达、经验水平、国籍、个人外貌、种族、宗教或性取向和倾向,都为每个人提供一个无骚扰的项目和社区参与体验。请阅读我们的完整行为准则。