contentasaurus / lightncandy
这是一个极为快速的 PHP 实现的 handlebars ( https://handlebars.node.org.cn/ ) 和 mustache ( http://mustache.github.io/ )。此包 fork 自 Zordius/LightnCandy。
Requires
- php: >=5.4.0
Requires (Dev)
- phpunit/phpunit: 4.0.17
README
⚡🍭 这是一个极为快速的 PHP 实现的 handlebars ( https://handlebars.node.org.cn/ ) 和 mustache ( http://mustache.github.io/ )。
特性
- 无逻辑模板:mustache ( http://mustache.github.com/ ) 或 handlebars ( https://handlebars.node.org.cn/ )。
- 将模板编译成 纯 PHP 代码。示例
- 非常快!
- 比 mustache.php (Justin Hileman/bobthecow 实现) 快 2~7 倍。
- 比 mustache-php (Dave Ingram 实现) 快 2~7 倍。
- 比 handlebars.php 快 10~50 倍。
- 详细性能测试报告可以在 这里 找到,访问 http://zordius.github.io/HandlebarsTest/ 查看图表。
- 小巧! 所有 PHP 文件总大小 147K
- 健壮!
- 100% 支持 mustache 规范 v1.1.3。对于可选的 lambda 模块,支持 10 个规范中的 4 个。
- 几乎支持所有 handlebars.js 规范
- 输出与 handlebars.js 相同的 SAME
- 灵活!
- 许多 选项 可用于更改功能和行为。
- 上下文生成
- 从您的模板中分析使用功能(执行
LightnCandy::getContext()
获取)。
- 从您的模板中分析使用功能(执行
- 调试
- 生成调试版模板
- 在渲染模板时找出缺失的数据。
- 生成可视调试模板。
- 生成调试版模板
- 独立模板
- 编译后的 PHP 代码可以在不安装任何 PHP 库的情况下运行。在执行渲染函数时,无需包含 LightnCandy。
安装
使用 Composer(https://getcomposer.org.cn/)安装 LightnCandy
composer require zordius/lightncandy:dev-master
升级通知
- 请检查 HISTORY.md 以查看版本历史。
- 请检查 UPGRADE.md 以查看升级通知。
文档
编译选项
您可以通过运行 LightnCandy::compile($template, $options)
应用更多选项。
LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONEPHP ));
默认情况下,将模板编译为 PHP,这可以运行得尽可能快(标志 = FLAG_BESTPERFORMANCE)。
错误处理
- FLAG_ERROR_LOG
- FLAG_ERROR_EXCEPTION
- FLAG_ERROR_SKIPPARTIAL
FLAG_RENDER_DEBUG
:在渲染时生成调试模板以显示错误。使用此标志,渲染性能可能会降低。
JavaScript 兼容性
Mustache 兼容性
FLAG_MUSTACHELOOKUP
:将递归查找行为与 mustache 规范对齐。此外,渲染性能会变差。FLAG_MUSTACHELAMBDA
:支持 mustache 规范中的简单 lambda 逻辑。此外,渲染性能会变差。FLAG_NOHBHELPERS
:不编译 handlebars.js 内置助手。使用此选项,{{#with}}
、{{#if}}
、{{#unless}}
、{{#each}}
表示普通部分,而{{#with foo}}
、{{#if foo}}
、{{#unless foo}}
、{{#each foo}}
将导致编译错误。FLAG_MUSTACHE
:支持所有 mustache 规范但性能下降,与FLAG_ERROR_SKIPPARTIAL
+FLAG_MUSTACHELOOKUP
+FLAG_MUSTACHELAMBDA
+FLAG_NOHBHELPERS
+FLAG_RUNTIMEPARTIAL
+FLAG_JS
相同。
Handlebars 兼容性
- FLAG_THIS
- FLAG_PARENT
- FLAG_HBESCAPE
- FLAG_ADVARNAME
- FLAG_NAMEDARG
- FLAG_SLASH
- FLAG_ELSE
FLAG_RAWBLOCK
:支持{{{{raw_block}}}}任何字符或_作为原始字符串{{{{/raw_block}}}}
。FLAG_SPACECTL
:支持模板中的空格控制{{~ }}
或{{ ~}}
。否则,{{~ }}
或{{ ~}}
将导致模板错误。FLAG_HANDLEBARSLAMBDA
:支持 lambda 逻辑作为 handlebars.js 规范。此外,渲染性能会变差。FLAG_SPVARS
:支持特殊变量,包括 @root、@index、@key、@first、@last。否则,使用默认解析逻辑编译这些变量名。FLAG_HANDLEBARS
:支持大多数 handlebars 扩展,同时保持良好的性能,与FLAG_THIS
+FLAG_PARENT
+FLAG_HBESCAPE
+FLAG_ADVARNAME
+FLAG_SPACECTL
+FLAG_NAMEDARG
+FLAG_SPVARS
+FLAG_SLASH
+FLAG_ELSE
+FLAG_RAWBLOCK
相同。FLAG_HANDLEBARSJS
:支持大多数 handlebars.js + JavaScript 行为,同时保持良好的性能,与FLAG_JS
+FLAG_HANDLEBARS
相同。FLAG_HANDLEBARSJS_FULL
:启用所有支持的 handlebars.js 行为,但性能下降,与FLAG_HANDLEBARSJS
+FLAG_INSTANCE
+FLAG_RUNTIMEPARTIAL
+FLAG_MUSTACHELOOKUP
+FLAG_HANDLEBARSLAMBDA
相同。
Handlebars 选项
- FLAG_NOESCAPE
- FLAG_PARTIALNEWCONTEXT
FLAG_IGNORESTANDALONE
:防止在{{#foo}}
、{{/foo}}
或{{^}}
上进行独立检测,行为与 handlebars.js 忽略独立编译时选项相同。FLAG_STRINGPARAMS
:将变量名作为字符串传递给辅助函数,行为与 handlebars.js stringParams 编译时选项相同。FLAG_KNOWNHELPERSONLY
:仅传递当前上下文到 lambda,行为与 handlebars.js knownHelpersOnly 编译时选项相同。FLAG_PREVENTINDENT
:使部分缩进行为与 mustache 规范对齐。这与 handlebars.js preventIndent 编译时选项相同。
PHP
- FLAG_STANDALONEPHP
- FLAG_EXTHELPER
- FLAG_RUNTIMEPARTIAL
- FLAG_PROPERTY
- FLAG_METHOD
- FLAG_INSTANCE
- FLAG_ECHO
- FLAG_BESTPERFORMANCE
部分支持
自定义辅助函数
自定义辅助函数示例
#mywith(上下文更改)
- LightnCandy
// LightnCandy sample, #mywith works same with #with $php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => Array( 'mywith' => function ($context, $options) { return $options['fn']($context); } ) ));
- Handlebars.js
// Handlebars.js sample, #mywith works same with #with Handlebars.registerHelper('mywith', function(context, options) { return options.fn(context); });
#myeach(上下文更改)
- LightnCandy
// LightnCandy sample, #myeach works same with #each $php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => Array( 'myeach' => function ($context, $options) { $ret = ''; foreach ($context as $cx) { $ret .= $options['fn']($cx); } return $ret; } ) ));
- Handlebars.js
// Handlebars.js sample, #myeach works same with #each Handlebars.registerHelper('myeach', function(context, options) { var ret = '', i, j = context.length; for (i = 0; i < j; i++) { ret = ret + options.fn(context[i]); } return ret; });
#myif(无上下文更改)
- LightnCandy
// LightnCandy sample, #myif works same with #if $php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => Array( 'myif' => function ($conditional, $options) { if ($conditional) { return $options['fn'](); } else { return $options['inverse'](); } } ) ));
- Handlebars.js
// Handlebars.js sample, #myif works same with #if Handlebars.registerHelper('myif', function(conditional, options) { if (conditional) { return options.fn(this); } else { return options.inverse(this); } });
您可以使用 isset($options['fn'])
来检测您的自定义辅助函数是否为块;您还可以使用 isset($options['inverse'])
来检测 {{else}}
的存在。
数据变量和上下文
您可以从 $options['data']
获取特殊的数据变量。使用 $options['_this']
接收当前上下文。
$php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => Array( 'getRoot' => function ($options) { print_r($options['_this']); // dump current context return $options['data']['root']; // same as {{@root}} } ) ));
- Handlebars.js
Handlebars.registerHelper('getRoot', function(options) { console.log(this); // dump current context return options.data.root; // same as {{@root}} });
私有变量
在执行子块时,您可以通过第二个参数将私有变量注入到内部块中。示例代码与 {{#each}}
类似,它为子块设置索引,可以使用 {{@index}}
访问。
- LightnCandy
$php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'helpers' => Array( 'list' => function ($context, $options) { $out = ''; $data = $options['data']; foreach ($context as $idx => $cx) { $data['index'] = $idx; $out .= $options['fn']($cx, Array('data' => $data)); } return $out; } ) ));
- Handlebars.js
Handlebars.registerHelper('list', function(context, options) { var out = ''; var data = options.data ? Handlebars.createFrame(options.data) : undefined; for (var i=0; i<context.length; i++) { if (data) { data.index = i; } out += options.fn(context[i], {data: data}); } return out; });
更改定界符
您可以将定界符从 {{
和 }}
更改为其他字符串。在模板中,您可以使用 {{=<% %>=}}
将定界符更改为 <%
和 %>
,但更改不会影响包含的部分。
如果您想更改模板及其所有包含部分的默认定界符,您可以在 compile()
时使用 delimiters
选项。
LightnCandy::compile('I wanna use <% foo %> as delimiters!', Array( 'delimiters' => array('<%', '%>') ));
模板调试
当模板错误发生时,LightnCandy::compile() 将返回 false。您可以使用 FLAG_ERROR_LOG
编译以查看更多错误信息,或使用 FLAG_ERROR_EXCEPTION
编译以捕获异常。
您可以在编译时使用 FLAG_RENDER_DEBUG
生成模板的调试版本。调试模板包含更多调试信息且较慢(TBD:性能结果),您可以将额外的 LightnCandy\Runtime 选项传递给 render 函数以了解更多渲染错误(缺少数据)。例如
$template = "Hello! {{name}} is {{gender}}. Test1: {{@root.name}} Test2: {{@root.gender}} Test3: {{../test3}} Test4: {{../../test4}} Test5: {{../../.}} Test6: {{../../[test'6]}} {{#each .}} each Value: {{.}} {{/each}} {{#.}} section Value: {{.}} {{/.}} {{#if .}}IF OK!{{/if}} {{#unless .}}Unless not OK!{{/unless}} "; // compile to debug version $php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_HANDLEBARSJS )); // Get the render function $renderer = LightnCandy::prepare($php); // error_log() when missing data: // LightnCandy\Runtime: [gender] is not exist // LightnCandy\Runtime: ../[test] is not exist $renderer(Array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_ERROR_LOG)); // Output visual debug template with ANSI color: echo $renderer(Array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_TAGS_ANSI)); // Output debug template with HTML comments: echo $renderer(Array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_TAGS_HTML));
ANSI 输出将是
以下是 LightnCandy\Runtime render 函数的调试选项列表
DEBUG_ERROR_LOG
:缺少所需数据时调用 error_log()DEBUG_ERROR_EXCEPTION
:缺少所需数据时抛出异常DEBUG_TAGS
:将 render 函数的返回值转换为规范化的 mustache 标签DEBUG_TAGS_ANSI
:将 render 函数的返回值转换为带有 ANSI 颜色的规范化 mustache 标签DEBUG_TAGS_HTML
:将 render 函数的返回值转换为带有 HTML 注释的规范化 mustache 标签
预处理部分
如果您想在编译部分之前进行额外的处理,您可以在编译时使用 prepartial
。例如,此示例通过名称标识部分添加 HTML 注释。
$php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'prepartial' => function ($context, $template, $name) { return "<!-- partial start: $name -->$template<!-- partial end: $name -->"; } ));
您还可以通过重写LightnCandy\Partial类中的prePartial()静态方法来扩展您的预处理器功能,使其成为一个内置功能。
自定义渲染函数
如果您想在渲染函数中执行额外任务或添加更多注释,可以在调用compile()
时使用renderex
。例如,以下示例将编译时间注释嵌入到模板中。
$php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'renderex' => '// Compiled at ' . date('Y-m-d h:i:s') ));
您的渲染函数将是
function ($in) { $cx = array(...); // compiled at 1999-12-31 00:00:00 return ..... }
请确保传入的renderex
是有效的PHP代码,LightnCandy不会进行检查。
自定义渲染运行时类
如果您想扩展LightnCandy\Runtime
类并替换默认的运行时库,可以在调用compile()
时使用runtime
。例如,以下示例将基于您扩展的MyRunTime
生成渲染函数。
// Customized runtime library to debug {{{foo}}} class MyRunTime extends LightnCandy\Runtime { public static function raw($cx, $v) { return '[[DEBUG:raw()=>' . var_export($v, true) . ']]'; } } // Use MyRunTime as runtime library $php = LightnCandy::compile($template, Array( 'flags' => LightnCandy::FLAG_HANDLEBARSJS, 'runtime' => 'MyRunTime' ));
请确保在调用compile()
或基于您的FLAG_STANDALONEPHP
进行渲染时MyRunTime
存在。
不支持的功能
- 使用
{{foo/bar}}
风格的变量名已弃用,请使用这种风格:{{foo.bar}}
。
建议的Handlebars模板实践
- 避免使用
{{#with}}
。我认为{{path.to.val}}
比{{#with path.to}}{{val}}{{/with}}
更易读;当使用{{#with}}
时,您会混淆作用域的变化。{{#with}}
只在访问同一路径下的多个变量时节省您很少的时间,但在需要理解并维护模板时,会花费您很多时间。 - 当您不需要对值进行HTML转义输出时,请使用
{{{val}}}
。它也具有更好的性能。 - 如果您想显示
{{
,请使用这个:{{#with "{{"}}{{.}}{{/with}}
。 - 如果您想在不同的语言中重复使用模板,请避免使用自定义助手。或者,您可能需要在不同语言中实现不同版本的助手。
- 为了获得最佳性能,您应该只在开发阶段使用'按需编译'模式。在生产之前,您可以对所有模板调用
LightnCandy::compile()
,保存所有生成的PHP代码,并部署这些生成的文件(您可能需要维护一个构建过程)。请勿在生产环境中编译,这也有利于安全。为'按需编译'添加缓存不是最佳解决方案。如果您想基于LightnCandy构建一些库或框架,请考虑这个场景。 - 每次升级LightnCandy时,请重新编译您的模板。
详细功能列表
访问https://handlebars.node.org.cn/以查看有关handlebars.js的更多功能描述。所有功能都与它一致。
- 与handlebars.js具有相同的CR/LF行为
- 与mustache规范具有相同的CR/LF行为
- 与handlebars.js具有相同的'true'或'false'输出(需要
FLAG_JSTRUE
) - 与handlebars.js具有相同的'[object Object]'输出或join(',' array)输出(需要
FLAG_JSOBJECT
) - 可以在
{{ var }}
或{{{ var }}}
内放置开头/结尾空格、制表符、CR/LF - 部分缩进行为与mustache规范相同
- 递归变量查找到父上下文的行为与mustache规范相同(需要
FLAG_MUSTACHELOOKUP
) {{{value}}}
或{{&value}}
:原始变量- true作为'true'(需要
FLAG_JSTRUE
) - false作为'false'(需要
FLAG_TRUE
)
- true作为'true'(需要
{{value}}
:HTML转义变量- true作为'true'(需要
FLAG_JSTRUE
) - false作为'false'(需要
FLAG_JSTRUE
)
- true作为'true'(需要
{{{path.to.value}}}
:点表示法,原始数据{{path.to.value}}
:点表示法,HTML转义数据{{.}}
:当前上下文,HTML转义{{{.}}}
:当前上下文,原始数据{{this}}
:当前上下文,HTML转义(需要FLAG_THIS
){{{this}}}
: 当前上下文,原始的(需要FLAG_THIS
){{#value}}
: 段落- false, undefined 和 null 将跳过该段落
- true 将以原始作用域运行该段落
- 所有其他情况将以新作用域运行该段落(包括 0, 1, -1, '', '1', '0', '-1', 'false', Array 等)
{{/value}}
: 段落结束{{^value}}
: 反向段落- false, undefined 和 null 将以原始作用域运行该段落
- 所有其他情况将跳过该段落(包括 0, 1, -1, '', '1', '0', '-1', 'false', Array 等)
{{! comment}}
: 注释{{!-- comment or {{ or }} --}}
: 可以包含 }} 或 {{ 的扩展注释{{=<% %>=}}
: 将分隔符设置为自定义字符串,自定义字符串不能包含=
。更多信息请查看 http://mustache.github.io/mustache.5.html{{#each var}}
: 每次循环{{#each}}
: 在 {{.}} 上进行循环{{/each}}
: 循环结束{{#if var}}
: 以原始作用域运行 if 逻辑(null, false, 空数组和 '' 将跳过此块){{#if foo includeZero=true}}
: 当 foo === 0 时结果为 true(需要FLAG_NAMEDARG
){{/if}}
: if 结束{{else}}
或{{^}}
: 在{{#if var}}
和{{/if}}
之间运行 else 逻辑;或在{{#unless var}}
和{{/unless}}
之间;或在{{#foo}}
和{{/foo}}
之间;或在{{#each var}}
和{{/each}}
之间;或在{{#with var}}
和{{/with}}
之间。(需要FLAG_ELSE
){{#if foo}} ... {{else if bar}} ... {{/if}}
: 连接 if else 块{{#unless var}}
: 以原始作用域运行 unless 逻辑(null, false, 空数组和 '' 将渲染此块){{#unless foo}} ... {{else if bar}} ... {{/unless}}
: 连接 unless else 块{{#unless foo}} ... {{else unless bar}} ... {{/unless}}
: 连接 unless else 块{{#foo}} ... {{else bar}} ... {{/foo}}
: 连接自定义助手 else 块{{#with var}}
: 更改上下文作用域。如果 var 为 false 或空数组,则跳过包含的段落。{{#with bar as |foo|}}
: 将上下文更改为 bar 并将值设置为 foo。(需要FLAG_ADVARNAME
){{lookup foo bar}}
: 通过 bar 的值作为键查找 foo。{{../var}}
: 父模板作用域。(需要FLAG_PARENT
){{>file}}
: 部分内容;在模板内部包含另一个模板。{{>file foo}}
: 带新上下文的部分内容(需要FLAG_RUNTIMEPARTIAL
){{>file foo bar=another}}
: 混合跟随键值的新上下文部分内容(需要FLAG_RUNTIMEPARTIAL
){{>(helper) foo}}
: 通过助手提供名称包含动态部分内容(需要FLAG_RUNTIMEPARTIAL
){{@index}}
: 在数组中{{#each}}
循环的当前索引。需要FLAG_SPVARS
{{@key}}
: 在对象中{{#each}}
循环的当前键。需要FLAG_SPVARS
{{@root}}
: 根上下文。需要FLAG_SPVARS
{{@first}}
: 当循环第一个项目时为 true。需要FLAG_SPVARS
{{@last}}
: 当循环到最后一个项目时为 true。需要FLAG_SPVARS
{{@root.path.to.value}}
: 遵循路径引用根上下文。需要FLAG_SPVARS
{{@../index}}
: 访问父循环索引。需要FLAG_SPVARS
和FLAG_PARENT
{{@../key}}
: 访问父循环键。需要FLAG_SPVARS
和FLAG_PARENT
{{foo.[ba.r].[#spec].0.ok}}
: 引用 $CurrentConext['foo']['ba.r']['#spec'][0]['ok']。需要FLAG_ADVARNAME
{{~any_valid_tag}}
: 空格控制,删除所有之前的空格(包括回车/换行符、制表符、空格;遇到任何非空格字符时停止)(需要FLAG_SPACECTL
){{any_valid_tag~}}
: 空格控制,删除所有后续空格(包括回车/换行符、制表符、空格;遇到任何非空格字符时停止)(需要FLAG_SPACECTL
){{{helper var}}}
: 执行自定义助手然后渲染结果{{helper var}}
: 执行自定义助手然后渲染转义的HTML结果{{helper "str"}}
或{{helper 'str'}}
: 使用字符串参数执行自定义助手(需要FLAG_ADVARNAME
){{helper 123 null true false undefined}}
: 将数字、true、false、null或undefined传递给助手{{helper name1=var name2=var2}}
: 使用命名参数执行自定义助手(需要FLAG_NAMEDARG
){{#helper ...}}...{{/helper}}
: 执行块自定义助手{{helper (helper2 foo) bar}}
: 将自定义助手作为子表达式执行(需要FLAG_ADVARNAME
){{{{raw_block}}}} {{will_not_parsed}} {{{{/raw_block}}}}
: 原始块(需要FLAG_RAWBLOCK
){{#> foo}}block{{/foo}}
: 部分块,提供foo
部分的默认内容(需要FLAG_RUNTIMEPARTIAL
){{#> @partial-block}}
: 在部分内部访问部分块内容{{#*inline "partial_name"}}...{{/inline}}
: 内联部分,提供部分并覆盖原始部分。{{log foo}}
: 将值输出到stderr进行调试。