devtheorem/php-handlebars
Composer 安装命令:
composer require devtheorem/php-handlebars
包简介
A blazing fast, spec-compliant PHP implementation of Handlebars.
README 文档
README
A blazing fast, spec-compliant PHP implementation of Handlebars.
The syntax of Handlebars is generally a superset of Mustache, so in most cases it is possible to swap out Mustache for Handlebars and continue using the same templates.
Features
- Supports all Handlebars syntax and language features, including expressions, subexpressions, helpers,
partials, hooks,
@datavariables, whitespace control, and.lengthon arrays and strings. - Arrays and objects can both be used as context values.
{{#each}}over an object iterates its public properties. - Templates are parsed using PHP Handlebars Parser, which implements the same lexical analysis and AST grammar specification as Handlebars.js.
- Tested against the Handlebars.js spec and the Mustache spec.
Performance
PHP Handlebars started as a fork of LightnCandy, but has been rewritten with an AST-based parser and optimized runtime to enable full Handlebars.js compatibility with better performance.
PHP Handlebars compiles and executes complex templates over 40% faster than LightnCandy, with 60% lower memory usage:
| Library | Compile time | Runtime | Total time | Peak memory usage |
|---|---|---|---|---|
| LightnCandy 1.2.6 | 5.0 ms | 2.4 ms | 7.4 ms | 5.3 MB |
| PHP Handlebars 2.1 | 2.8 ms | 1.4 ms | 4.2 ms | 1.8 MB |
Tested on PHP 8.5 with the JIT enabled. See the benchmark branch to run the same test.
Installation
composer require devtheorem/php-handlebars
Usage
use DevTheorem\Handlebars\Handlebars; $source = <<<'HBS' <p>Hi {{user.name}}, you have {{notifications.length}} new notification(s):</p> <ul> {{#notifications}} <li>{{count}} {{message}} ({{time}})</li> {{/notifications}} </ul> HBS; $data = [ 'user' => ['name' => 'Jane'], 'notifications' => [ ['count' => 4, 'message' => 'new comments', 'time' => '5 min ago'], ['count' => 3, 'message' => 'new followers', 'time' => '1 hr ago'], ], ]; $template = Handlebars::compile($source); echo $template($data);
Output:
<p>Hi Jane, you have 2 new notification(s):</p> <ul> <li>4 new comments (5 min ago)</li> <li>3 new followers (1 hr ago)</li> </ul>
Precompilation
Templates and partials can be precompiled to native PHP for later execution, avoiding the overhead of parsing and compilation on each request.
Build step - compile all templates in a directory and cache the generated PHP:
use DevTheorem\Handlebars\Handlebars; $templateDir = 'templates'; $cacheDir = 'templateCache'; foreach (glob("$templateDir/*.hbs") ?: [] as $file) { $name = basename($file, '.hbs'); $code = Handlebars::precompile(file_get_contents($file)); file_put_contents("$cacheDir/$name.php", "<?php $code"); }
Runtime - load only needed templates, with precompiled partials resolved on demand:
$template = require 'templateCache/page.php'; $data = ['title' => 'My Page', 'user' => ['name' => 'Jane']]; echo $template($data, [ 'partialResolver' => fn(string $name) => require "templateCache/$name.php", ]);
Each {{> partial}} call triggers the resolver on first use, and the result is cached for
the rest of that render. Only the partials that the page actually references are ever loaded.
Important
Precompiled templates must be regenerated whenever PHP Handlebars is updated, as the generated PHP code depends on the current version of the runtime. The build step above should be part of a deployment process so that precompiled output does not need to be committed to source control.
Compile Options
You can alter the template compilation by passing an Options instance as the second argument to compile or precompile.
For example, the strict option may be set to true to generate a template which will throw an exception for missing data:
use DevTheorem\Handlebars\{Handlebars, Options}; $template = Handlebars::compile('Hi {{first}} {{last}}!', new Options( strict: true, )); echo $template(['first' => 'John']); // Error: "last" not defined
Available Options
compat: Set totrueto enable recursive field lookup. If a template variable is not found in the current scope, it will automatically be looked up in parent scopes, matching Mustache's default behavior.
Note
Recursive lookup has a runtime cost, so it is recommended that performance-sensitive
operations should avoid compat mode and instead opt for explicit path references.
-
knownHelpers: Associative array (helperName => bool) of helpers that will be registered at runtime. The compiler uses this to emit direct helper calls instead of dynamic dispatch, which is faster and required whenknownHelpersOnlyis set. Built-in helpers (if,unless,each,with,lookup,log) are pre-populated astrueand may be excluded by setting them tofalse. Settingiforunlesstofalsealso disables the inline ternary optimization and allows those helpers to be overridden at runtime. -
knownHelpersOnly: Restricts templates to only the helpers inknownHelpers, enabling further compile-time optimizations: block sections and bare{{identifier}}expressions skip the runtime helper table and use a direct context lookup, and any use of an unknown helper throws a compile-time exception instead of falling back to dynamic dispatch. -
noEscape: Set totrueto disable HTML escaping of output. -
strict: Run in strict mode. In this mode, templates will throw rather than silently ignore missing fields. This has the side effect of disabling inverse operations such as{{^foo}}{{/foo}}unless fields are explicitly included in the source object. -
assumeObjects: A looser alternative tostrictmode. A null intermediate in a path (e.g.foois null when resolvingfoo.bar) throws an exception, but a missing terminal key returns null silently. -
preventIndent: Prevents an indented partial call from indenting the entire partial output by the same amount. -
ignoreStandalone: Disables standalone tag removal. When set, blocks and partials that are on their own line will not remove the whitespace on that line. -
explicitPartialContext: Disables implicit context for partials. When enabled, partials that are not passed a context value will execute against an empty object.
Runtime Options
Handlebars::compile returns a closure which can be invoked as $template($context, $options).
The $options parameter takes an array of runtime options, accepting the following keys:
-
data: An associative array of custom@datavariables (e.g.['version' => '1.0']makes@versionavailable in the template). -
helpers: Anarray<string, Closure>of helpers to merge with the built-in helpers. Can also be used to override a built-in helper by using the same name. -
partials: Anarray<string, Closure>of partials compiled withHandlebars::compile. Useful for eagerly providing a known set of partials. -
partialResolver: AClosure(string $name): ?Closurecalled lazily when a partial is referenced but not found in thepartialsmap. Should return a compiled partial closure, ornullif the partial does not exist. The resolved closure is cached for the remainder of the render, so each partial is loaded at most once per template invocation.
Custom Helpers
Helper functions will be passed any arguments provided to the helper in the template.
If needed, a final $options parameter can be included which will be passed a HelperOptions instance.
For example, a custom #equals helper with JS equality semantics could be implemented as follows:
use DevTheorem\Handlebars\{Handlebars, HelperOptions}; $template = Handlebars::compile('{{#equals my_var false}}Equal to false{{else}}Not equal{{/equals}}'); $helpers = [ 'equals' => function (mixed $a, mixed $b, HelperOptions $options) { // In JS, null is not equal to blank string or false or zero, // and when both operands are strings no coercion is performed. $equal = ($a === null || $b === null || is_string($a) && is_string($b)) ? $a === $b : $a == $b; return $equal ? $options->fn() : $options->inverse(); }, ]; $runtimeOptions = ['helpers' => $helpers]; echo $template(['my_var' => 0], $runtimeOptions); // Equal to false echo $template(['my_var' => 1], $runtimeOptions); // Not equal echo $template(['my_var' => null], $runtimeOptions); // Not equal
HelperOptions Properties
-
name(readonlystring): The helper name as it appeared in the template. Useful inhelperMissing/blockHelperMissinghooks to identify which name was called. -
hash(readonlyarray): Key/value pairs passed as hash arguments in the template (e.g.{{helper foo=1 bar="x"}}produces['foo' => 1, 'bar' => 'x']). -
blockParams(readonlyint): The number of block parameters declared by the helper call (e.g.{{#helper as |a b|}}produces2). -
scope(mixed): The current evaluation context (equivalent tothisin a Handlebars.js helper). -
data(array): The current@dataframe. Therootkey refers to the top-level context.index,key,first, andlastare set by{{#each}}blocks. Can be read or modified inside a helper.
HelperOptions Methods
-
fn(mixed $context = <current scope>, mixed $data = null): string: Renders the block body. Pass a new context as$contextto change what the block renders against (equivalent tooptions.fn(newContext)in JS). Pass a$dataarray with a'data'key to inject@-prefixed variables into the block, and/or a'blockParams'key containing an array of values to expose as block parameters. -
inverse(mixed $context = <current scope>, mixed $data = null): string: Renders the{{else}}/ inverse block. Returns an empty string if no inverse block was provided. Accepts the same optional$contextand$dataarguments asfn(). -
hasPartial(string $name): bool: Returnstrueif a partial with the given name is registered. Useful alongsideregisterPartial()to implement dynamic partial loading. -
registerPartial(string $name, Closure $partial): void: Registers a compiled partial closure for the remainder of the render. The closure can be produced viaHandlebars::compile, or by importing a cached closure created withHandlebars::precompile.
Note
isset($options->fn) and isset($options->inverse) return true if the helper was called as a block,
and false for inline helper calls.
Hooks
If a custom helper named helperMissing is defined, it will be called when a mustache or a block-statement
is not a registered helper AND is not a property of the current evaluation context.
If a custom helper named blockHelperMissing is defined, it will be called when a block-expression calls
a helper that is not registered, even when the name matches a property in the current evaluation context.
For example:
use DevTheorem\Handlebars\{Handlebars, HelperOptions}; $template = Handlebars::compile('{{foo 2 "value"}} {{#person}}{{firstName}} {{lastName}}{{/person}}'); $helpers = [ 'helperMissing' => function (...$args) { $options = array_pop($args); return "Missing {$options->name}(" . implode(',', $args) . ')'; }, 'blockHelperMissing' => function (mixed $context, HelperOptions $options) { return "'{$options->name}' not found. Printing block: {$options->fn($context)}"; }, ]; $data = ['person' => ['firstName' => 'John', 'lastName' => 'Doe']]; echo $template($data, ['helpers' => $helpers]);
Output:
Missing foo(2,value)
'person' not found. Printing block: John Doe
String Escaping
If a custom helper is executed in a {{ }} expression, the return value will be HTML escaped.
When a helper is executed in a {{{ }}} expression, the original return value will be output directly.
Helpers may return a DevTheorem\Handlebars\SafeString instance to prevent escaping the return value.
Because SafeString bypasses the automatic HTML escaping that {{ }} applies, any user-supplied content
embedded in it must first be escaped with Handlebars::escapeExpression() to prevent XSS vulnerabilities.
Data Frames
Block helpers that inject @-prefixed variables should create a child data frame using
Handlebars::createFrame($options->data), add their variables to it, and pass it to fn() or inverse()
via the data key (e.g. $options->fn($context, ['data' => $frame])). This mirrors Handlebars.createFrame()
in Handlebars.js, isolating the helper's variables while still inheriting parent data such as @root.
Missing Features
All syntax and language features from Handlebars.js 4.7.9 should work the same in PHP Handlebars, with the following exceptions:
- Custom Decorators have not been implemented, as they are deprecated in Handlebars.js.
- The
datacompilation option has not been implemented. - The runtime options to control prototype access,
along with the
lookupProperty()helper option method have not been implemented, since they aren't relevant for PHP.
Mustache Compatibility
Handlebars is largely compatible with Mustache syntax, with a few notable differences:
- Handlebars does not perform recursive field lookup by default.
The
compatcompile option must be set to enable this behavior. - Alternative Mustache delimiters (e.g.
{{=<% %>=}}) are not supported. - Spaces are not allowed between the opening
{{and a command character such as#,/, or>. For example,{{> partial}}works but{{ > partial}}does not.
devtheorem/php-handlebars 适用场景与选型建议
devtheorem/php-handlebars 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 42.84k 次下载、GitHub Stars 达 40, 最近一次更新时间为 2025 年 03 月 18 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「template」 「mustache」 「parse」 「compile」 「render」 「handlebars」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 devtheorem/php-handlebars 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 devtheorem/php-handlebars 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 devtheorem/php-handlebars 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
yii2 xml request parser
Parse promotion arrays from the Wayne State University API
Parse use statements for a reflection object
Strongly opinionated MVC framework for rapid development
Parse, validate, manipulate, and display dates in PHP w/ i18n support. Inspired by moment.js
Parse verse textual representation into book/chapter/verse ranges
统计信息
- 总下载量: 42.84k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 40
- 点击次数: 25
- 依赖项目数: 5
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-03-18