contentasaurus/lightncandy

这是一个极为快速的 PHP 实现的 handlebars ( https://handlebars.node.org.cn/ ) 和 mustache ( http://mustache.github.io/ )。此包 fork 自 Zordius/LightnCandy。

v10.95 2016-10-15 04:25 UTC

README

⚡🍭 这是一个极为快速的 PHP 实现的 handlebars ( https://handlebars.node.org.cn/ ) 和 mustache ( http://mustache.github.io/ )。

Travis CI 状态: 单元测试 回归测试

Scrutinizer CI 状态: 代码覆盖率

packagist 上的包: 最新稳定版本 许可 总下载量 HHVM 状态

特性

安装

使用 Composer(https://getcomposer.org.cn/)安装 LightnCandy

composer require zordius/lightncandy:dev-master

升级通知

文档

编译选项

您可以通过运行 LightnCandy::compile($template, $options) 应用更多选项。

LightnCandy::compile($template, Array(
    'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONEPHP
));

默认情况下,将模板编译为 PHP,这可以运行得尽可能快(标志 = FLAG_BESTPERFORMANCE)。

错误处理

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

部分支持

自定义辅助函数

自定义辅助函数示例

#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
  • {{value}}:HTML转义变量
    • true作为'true'(需要FLAG_JSTRUE
    • false作为'false'(需要FLAG_JSTRUE
  • {{{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_SPVARSFLAG_PARENT
  • {{@../key}} : 访问父循环键。需要 FLAG_SPVARSFLAG_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进行调试。

框架集成

工具