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进行调试。
