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://composer.php.ac.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进行调试。
