Skip to content

Commit 97e6bdb

Browse files
committed
Replace compile-time helpers with runtime helpers
The `helpers` compilation option has been removed, and all helpers must now be passed at runtime when executing the template (just like in Handlebars.js). This can significantly reduce compile time, since PHP files no longer have to be read and tokenized to extract helper functions. It also enables sharing helper closures across multiple templates, and removes limitations on what they can access and do. Additionally, implemented `knownHelpers` option and updated `knownHelpersOnly` to work the same as in Handlebars.js. This now makes it possible to disable individual built-in helpers. Fixed multiple bugs related to inverted block helpers and section scoping/iteration. Also unified partial closures and improved param types. Compiled templates and partials now have the same function signature to avoid duplicated logic.
1 parent 7b96a68 commit 97e6bdb

13 files changed

Lines changed: 988 additions & 1003 deletions

README.md

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,24 @@ $template = Handlebars::compile('Hi {{first}} {{last}}!', new Options(
5858
echo $template(['first' => 'John']); // Error: Runtime: last does not exist
5959
```
6060

61-
**Available Options:**
61+
### Available Options
62+
63+
* `knownHelpers`: Associative array (`helperName => bool`) of helpers known to exist at template execution time.
64+
Passing this allows the compiler to optimize a number of cases.
65+
Builtin helpers are automatically included in this list and may be omitted by setting that value to `false`.
6266
* `knownHelpersOnly`: Enable to allow further optimizations based on the known helpers list.
6367
* `noEscape`: Enable to not HTML escape any content.
6468
* `strict`: Run in strict mode. In this mode, templates will throw rather than silently ignore missing fields.
65-
* `assumeObjects`: Removes object existence checks when traversing paths. This is a subset of strict mode that generates optimized templates when the data inputs are known to be safe.
69+
This has the side effect of disabling inverse operations such as `{{^foo}}{{/foo}}`
70+
unless fields are explicitly included in the source object.
71+
* `assumeObjects`: Removes object existence checks when traversing paths.
72+
This is a subset of strict mode that generates optimized templates when the data inputs are known to be safe.
6673
* `preventIndent`: Prevent indented partial-call from indenting the entire partial output by the same amount.
67-
* `ignoreStandalone`: Disables standalone tag removal. When set, blocks and partials that are on their own line will not remove the whitespace on that line.
68-
* `explicitPartialContext`: Disables implicit context for partials. When enabled, partials that are not passed a context value will execute against an empty object.
69-
* `helpers`: Provide a key => value array of custom helper functions.
70-
* `partials`: Provide a key => value array of custom partial templates.
74+
* `ignoreStandalone`: Disables standalone tag removal.
75+
When set, blocks and partials that are on their own line will not remove the whitespace on that line.
76+
* `explicitPartialContext`: Disables implicit context for partials.
77+
When enabled, partials that are not passed a context value will execute against an empty object.
78+
* `partials`: Provide a `name => value` array of custom partial templates.
7179
* `partialResolver`: A closure which will be called for any partial not in the `partials` array to return a template for it.
7280

7381
## Custom Helpers
@@ -80,28 +88,30 @@ This object contains properties for accessing `hash` arguments, `data`, and the
8088
For example, a custom `#equals` helper with JS equality semantics could be implemented as follows:
8189

8290
```php
83-
use DevTheorem\Handlebars\{Handlebars, HelperOptions, Options};
91+
use DevTheorem\Handlebars\{Handlebars, HelperOptions};
8492

85-
$template = Handlebars::compile('{{#equals my_var false}}Equal to false{{else}}Not equal{{/equals}}', new Options(
86-
helpers: [
93+
$template = Handlebars::compile('{{#equals my_var false}}Equal to false{{else}}Not equal{{/equals}}');
94+
$options = [
95+
'helpers' => [
8796
'equals' => function (mixed $a, mixed $b, HelperOptions $options) {
8897
$jsEquals = function (mixed $a, mixed $b): bool {
89-
if ($a === null || $b === null) {
90-
// in JS, null is not equal to blank string or false or zero
98+
if ($a === null || $b === null || is_string($a) && is_string($b)) {
99+
// In JS, null is not equal to blank string or false or zero,
100+
// and when both operands are strings no coercion is performed.
91101
return $a === $b;
92102
}
93-
103+
94104
return $a == $b;
95105
};
96-
106+
97107
return $jsEquals($a, $b) ? $options->fn() : $options->inverse();
98108
},
99109
],
100-
));
110+
];
101111

102-
echo $template(['my_var' => 0]); // Equal to false
103-
echo $template(['my_var' => 1]); // Not equal
104-
echo $template(['my_var' => null]); // Not equal
112+
echo $template(['my_var' => 0], $options); // Equal to false
113+
echo $template(['my_var' => 1], $options); // Not equal
114+
echo $template(['my_var' => null], $options); // Not equal
105115
```
106116

107117
## Hooks
@@ -115,13 +125,13 @@ a helper that is not registered, even when the name matches a property in the cu
115125
For example:
116126

117127
```php
118-
use DevTheorem\Handlebars\{Handlebars, HelperOptions, Options};
128+
use DevTheorem\Handlebars\{Handlebars, HelperOptions};
119129

120-
$templateStr = '{{foo 2 "value"}}
121-
{{#person}}{{firstName}} {{lastName}}{{/person}}';
130+
$template = Handlebars::compile('{{foo 2 "value"}}
131+
{{#person}}{{firstName}} {{lastName}}{{/person}}');
122132

123-
$template = Handlebars::compile($templateStr, new Options(
124-
helpers: [
133+
$options = [
134+
'helpers' => [
125135
'helperMissing' => function (...$args) {
126136
$options = array_pop($args);
127137
return "Missing {$options->name}(" . implode(',', $args) . ')';
@@ -130,9 +140,9 @@ $template = Handlebars::compile($templateStr, new Options(
130140
return "'{$options->name}' not found. Printing block: {$options->fn($context)}";
131141
},
132142
],
133-
));
143+
];
134144

135-
echo $template(['person' => ['firstName' => 'John', 'lastName' => 'Doe']]);
145+
echo $template(['person' => ['firstName' => 'John', 'lastName' => 'Doe']], $options);
136146
```
137147
Output:
138148
> Missing foo(2,value)
@@ -147,7 +157,10 @@ Helpers may return a `DevTheorem\Handlebars\SafeString` instance to prevent esca
147157
When constructing the string that will be marked as safe, any external content should be properly escaped
148158
using the `Handlebars::escapeExpression()` method to avoid potential security concerns.
149159

150-
## Missing features
160+
## Missing Features
161+
162+
All syntax and language features from Handlebars.js 4.7.8 should work the same in PHP Handlebars,
163+
with the following exceptions:
151164

152-
All syntax from Handlebars.js 4.7.8 should work the same in this implementation, with the following exception:
153-
* Decorators ([deprecated in Handlebars.js](https://github.com/handlebars-lang/handlebars.js/blob/master/docs/decorators-api.md)) have not been implemented.
165+
* Custom Decorators have not been implemented, as they are [deprecated in Handlebars.js](https://github.com/handlebars-lang/handlebars.js/blob/master/docs/decorators-api.md).
166+
* The `data` and `compat` compilation options have not been implemented.

0 commit comments

Comments
 (0)