Skip to content

Commit 7948cd3

Browse files
committed
Start drafting changelog (unfinished)
1 parent 9cf0b73 commit 7948cd3

2 files changed

Lines changed: 94 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,71 @@ All notable changes to this project will be documented in this file.
33

44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
55

6+
## [1.0.0] AST Compiler - 2026-03-22
7+
8+
Rewrote the parser and compiler to use an AST, based on the same lexical analysis and grammar specification as Handlebars.js. This eliminates a large class of edge cases and parsing bugs that the old regex-based approach failed to handle correctly.
9+
10+
### Added
11+
- Support for nested inline partials.
12+
- Support for closures in data and helper arguments.
13+
- `helperMissing` and `blockHelperMissing` hooks: handle calls to unknown helpers with the same API as in Handlebars.js, replacing the old `helperResolver` option.
14+
- `knownHelpers` compile option: tell the compiler which helpers will be available at runtime for more efficient execution (helper existence checks can be skipped).
15+
- `assumeObjects` compile option: a subset of `strict` mode that generates optimized templates when the data inputs are known to be safe.
16+
- Support for deprecated `{{person/firstname}}` path expressions for parity with Handlebars.js (please don't use this syntax in new code, though).
17+
18+
### Changed
19+
- Custom helpers must now be passed when invoking a template (via the `helpers` runtime option key),
20+
rather than at compile time. This is a significant optimization, since PHP files no longer have to be read and
21+
tokenized to extract helper functions. It also enables sharing helper closures across multiple templates and renders,
22+
and removes limitations on what they can access and do. Resolves https://github.com/zordius/lightncandy/issues/342.
23+
- Exceptions thrown by custom helpers are no longer caught and re-thrown, so the original exception can now be caught
24+
in your own code for easier debugging (https://github.com/devtheorem/php-handlebars/issues/13).
25+
- `knownHelpersOnly` now works as in Handlebars.js: an exception will be thrown if the template uses a helper which is not in the `knownHelpers` list.
26+
- Updated various error messages to align with those output by Handlebars.js.
27+
- The `partialResolver` closure signature no longer receives a `Context` argument. Now only the partial name is passed.
28+
29+
### Removed
30+
- `Options::$helpers`: pass custom helpers at runtime instead using the `helpers` key in the runtime options array (the second argument to the template closure).
31+
- `Options::$helperResolver`: use the `helperMissing` / `blockHelperMissing` runtime helpers instead.
32+
33+
### Fixed
34+
- Fatal error with deeply nested `else if` using custom helper (https://github.com/devtheorem/php-handlebars/issues/2).
35+
- Incorrect rendering of float values (https://github.com/devtheorem/php-handlebars/issues/11).
36+
- Conditional `@partial-block` expressions.
37+
- Support for `@partial-block` in nested partials (https://github.com/zordius/lightncandy/issues/292).
38+
- Ability to precompile partials and pass them at runtime (https://github.com/zordius/lightncandy/issues/341).
39+
- Fatal error when a string parameter to a partial includes curly braces (https://github.com/zordius/lightncandy/issues/316).
40+
- Behavior when modifying root context in a custom helper (https://github.com/zordius/lightncandy/issues/350).
41+
- Escaping of block params and partial names.
42+
- Inline partials defined inside a `{{#with}}` or other block leaking out of that block's scope after it closes.
43+
- Numerous other bugs related to scoping, block params, inverted block helpers, section iteration, and depth-relative paths.
44+
45+
46+
Double-check every LightnCandy and PHP Handlebars issue where I left a comment, to see if any need to be updated following the helpers changes.
47+
48+
Comment with fixed after release:
49+
- https://github.com/zordius/lightncandy/issues/342
50+
- https://github.com/zordius/lightncandy/issues/316
51+
- https://github.com/zordius/lightncandy/issues/350
52+
- https://github.com/zordius/lightncandy/issues/292
53+
- https://github.com/zordius/lightncandy/issues/341
54+
55+
56+
[Valid use for Lightncandy::prepare()](https://github.com/zordius/lightncandy/issues/346)
57+
- Comment that PHP Handlebars supports compile() method just like Handlebars.js
58+
59+
60+
[SafeString fails with FLAG\_EXTHELPER | FLAG\_BESTPERFORMANCE](https://github.com/zordius/lightncandy/issues/354)
61+
- Comment suggesting to try PHP Handlebars, which can often outperform LightnCandy without needing any special flags.
62+
63+
- Support callable for helper functions: https://github.com/zordius/lightncandy/issues/228
64+
65+
66+
[Unable to resolve partial when using sub expression and partial resolver](https://github.com/devtheorem/php-handlebars/issues/5)
67+
- Maybe comment with approach using helper rather than partial?
68+
69+
70+
671
## [0.9.9] Stringable Conditions - 2025-10-15
772
### Added
873
- Allow `Stringable` variables in `if` statements ([#8](https://github.com/devtheorem/php-handlebars/pull/8)).
@@ -96,6 +161,7 @@ Initial release after forking from LightnCandy 1.2.6.
96161
- HTML documentation.
97162
- Dozens of unnecessary feature flags.
98163

164+
[1.0.0]: https://github.com/devtheorem/php-handlebars/compare/v0.9.9...v1.0.0
99165
[0.9.9]: https://github.com/devtheorem/php-handlebars/compare/v0.9.8...v0.9.9
100166
[0.9.8]: https://github.com/devtheorem/php-handlebars/compare/v0.9.7...v0.9.8
101167
[0.9.7]: https://github.com/devtheorem/php-handlebars/compare/v0.9.6...v0.9.7

README.md

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
# PHP Handlebars
22

3-
A blazing fast, spec-compliant PHP implementation of [Handlebars](https://handlebarsjs.com/).
3+
A blazing fast, spec-compliant PHP implementation of [Handlebars](https://handlebarsjs.com).
44

5-
Originally based on [LightnCandy](https://github.com/zordius/lightncandy), but rewritten to focus on
6-
more robust Handlebars.js compatibility without the need for excessive feature flags.
5+
Originally based on [LightnCandy](https://github.com/zordius/lightncandy), but rewritten to enable
6+
full Handlebars.js compatibility without excessive feature flags or performance tradeoffs.
7+
8+
PHP Handlebars compiles and executes complex templates up to 40% faster than LightnCandy:
9+
10+
| Library | Compile time | Runtime | Total time | Peak memory usage |
11+
|----------------|--------------|---------|------------|-------------------|
12+
| LightnCandy | 5.7 ms | 2.8 ms | 8.5 ms | 5.3 MB |
13+
| PHP Handlebars | 3.5 ms | 1.6 ms | 5.1 ms | 3.6 MB |
14+
15+
_Tested on PHP 8.5 with the JIT enabled. See the `benchmark` branch to run the same test._
716

817
## Features
918

10-
* Compile templates to pure PHP code.
19+
* Supports all Handlebars syntax and language features, including expressions, subexpressions, helpers,
20+
partials, hooks, and `@data` variables.
1121
* Templates are parsed using [PHP Handlebars Parser](https://github.com/devtheorem/php-handlebars-parser),
12-
which implements the same lexical analysis and grammar specification as Handlebars.js.
13-
* Tested against the [Handlebars.js spec](https://github.com/jbboehr/handlebars-spec).
22+
which implements the same lexical analysis and AST grammar specification as Handlebars.js.
23+
* Tested against the full [Handlebars.js spec](https://github.com/jbboehr/handlebars-spec).
1424

1525
## Installation
1626
```
@@ -55,7 +65,7 @@ $template = Handlebars::compile('Hi {{first}} {{last}}!', new Options(
5565
strict: true,
5666
));
5767

58-
echo $template(['first' => 'John']); // Error: Runtime: last does not exist
68+
echo $template(['first' => 'John']); // Error: "last" not defined
5969
```
6070

6171
### Available Options
@@ -82,8 +92,8 @@ echo $template(['first' => 'John']); // Error: Runtime: last does not exist
8292

8393
Helper functions will be passed any arguments provided to the helper in the template.
8494
If needed, a final `$options` parameter can be included which will be passed a `HelperOptions` instance.
85-
This object contains properties for accessing `hash` arguments, `data`, and the current `scope`, as well as
86-
`fn()` and `inverse()` methods to render the block and else contents, respectively.
95+
This object contains properties for accessing `hash` arguments, `data`, and the current `scope`, `name`,
96+
as well as `fn()` and `inverse()` methods to render the block and else contents, respectively.
8797

8898
For example, a custom `#equals` helper with JS equality semantics could be implemented as follows:
8999

@@ -94,17 +104,13 @@ $template = Handlebars::compile('{{#equals my_var false}}Equal to false{{else}}N
94104
$options = [
95105
'helpers' => [
96106
'equals' => function (mixed $a, mixed $b, HelperOptions $options) {
97-
$jsEquals = function (mixed $a, mixed $b): bool {
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.
101-
return $a === $b;
102-
}
103-
104-
return $a == $b;
105-
};
106-
107-
return $jsEquals($a, $b) ? $options->fn() : $options->inverse();
107+
// In JS, null is not equal to blank string or false or zero,
108+
// and when both operands are strings no coercion is performed.
109+
$equal = ($a === null || $b === null || is_string($a) && is_string($b))
110+
? $a === $b
111+
: $a == $b;
112+
113+
return $equal ? $options->fn() : $options->inverse();
108114
},
109115
],
110116
];
@@ -164,3 +170,5 @@ with the following exceptions:
164170

165171
* 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).
166172
* The `data` and `compat` compilation options have not been implemented.
173+
* The [runtime options to control prototype access](https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access),
174+
along with the `lookupProperty()` helper option method have not been implemented, since they aren't relevant for PHP.

0 commit comments

Comments
 (0)