Skip to content

Commit 3ad4028

Browse files
committed
Implement Handlebars::createFrame utility function
1 parent 8270d50 commit 3ad4028

3 files changed

Lines changed: 33 additions & 33 deletions

File tree

src/Handlebars.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ public static function template(string $templateSpec): \Closure
4444
return eval($templateSpec);
4545
}
4646

47+
/**
48+
* Creates a child @data frame inheriting fields from the given frame.
49+
* Use this in block helpers before passing a data array to fn() or inverse(),
50+
* equivalent to Handlebars.createFrame() in Handlebars.js.
51+
* @param array<mixed> $data
52+
* @return array<mixed>
53+
*/
54+
public static function createFrame(array $data): array
55+
{
56+
$frame = $data;
57+
$frame['_parent'] = $data;
58+
return $frame;
59+
}
60+
4761
/**
4862
* HTML escapes the passed string, making it safe for rendering as text within HTML content.
4963
* The output of all expressions except for triple-braced expressions are passed through this method.

src/HelperOptions.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ private function invokeBlock(\Closure $closure, mixed $context, mixed $data): st
102102

103103
if (isset($data['data'])) {
104104
$savedFrame = $cx->frame;
105-
// Fast path: only root in frame, no user @-data to inherit
106-
$newFrame = count($savedFrame) === 1 ? $data['data'] : array_replace($savedFrame, $data['data']);
105+
$newFrame = $data['data'];
106+
// Reference so @root lookups work and root mutations from inside the block are visible
107+
// outside it — matches HBS.js shallow-copy semantics for object properties.
107108
$newFrame['root'] = &$cx->data['root'];
108-
$newFrame['_parent'] = $savedFrame;
109109
$cx->frame = $newFrame;
110110
}
111111

@@ -169,6 +169,8 @@ public function iterate(array $items): string
169169
foreach ($items as $index => $value) {
170170
$iterData = ['key' => $index, 'index' => $i, 'first' => $i === 0, 'last' => $i === $last];
171171
$newFrame = $simpleFrame ? $iterData : array_replace($outerFrame, $iterData);
172+
// Reference so @root lookups work and root mutations from inside the block are visible
173+
// outside it — matches HBS.js shallow-copy semantics for object properties.
172174
$newFrame['root'] = &$cx->data['root'];
173175
$newFrame['_parent'] = $outerFrame;
174176
$cx->frame = $newFrame;

tests/HandlebarsSpecTest.php

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,15 @@
11
<?php
22

3+
namespace DevTheorem\Handlebars\Test;
4+
35
use DevTheorem\Handlebars\Handlebars;
46
use DevTheorem\Handlebars\Options;
57
use PHPUnit\Framework\Attributes\DataProvider;
68
use PHPUnit\Framework\TestCase;
79

8-
/**
9-
* Used by vendor/jbboehr/handlebars-spec/spec/data.json
10-
*/
11-
class Utils
12-
{
13-
public static function createFrame(mixed $data): mixed
14-
{
15-
if (is_array($data)) {
16-
$r = [];
17-
foreach ($data as $k => $v) {
18-
$r[$k] = $v;
19-
}
20-
return $r;
21-
}
22-
return $data;
23-
}
24-
}
25-
2610
/**
2711
* @phpstan-type JsonSpec array{
28-
* file: string, no: int, message: string|null, data: null|int|bool|string|array<mixed>|stdClass,
12+
* file: string, no: int, message: string|null, data: null|int|bool|string|array<mixed>|\stdClass,
2913
* it: string, description: string, expected: string|null, helpers: array<mixed>,
3014
* partials: array<mixed>, compileOptions: array<mixed>, template: string,
3115
* exception: string|null, runtimeOptions: array<mixed>, number: string|null,
@@ -82,11 +66,7 @@ public function testSpecs(array $spec): void
8266
if (!isset($func['php'])) {
8367
$this->markTestIncomplete("No PHP helper code provided for [{$spec['file']}#{$spec['description']}]#{$spec['no']}");
8468
}
85-
$helper = self::patchSafeString($func['php']);
86-
$helper = str_replace('$options[\'name\']', '$options->name', $helper);
87-
$helper = str_replace('$options[\'data\']', '$options->data', $helper);
88-
$helper = str_replace('$options[\'hash\']', '$options->hash', $helper);
89-
$helper = str_replace('$arguments[count($arguments)-1][\'name\'];', '$arguments[count($arguments)-1]->name;', $helper);
69+
$helper = self::patchHelperCode($func['php']);
9070
$helpersList .= "\n '$name' => $helper,\n";
9171
eval('$helpers[\'' . $name . '\'] = ' . $helper . ';');
9272
}
@@ -182,7 +162,7 @@ public static function jsonSpecProvider(): array
182162

183163
$files = glob('vendor/jbboehr/handlebars-spec/spec/*.json');
184164
if ($files === false) {
185-
throw new Exception("Failed to read JSON spec files");
165+
throw new \Exception("Failed to read JSON spec files");
186166
}
187167

188168
foreach ($files as $file) {
@@ -192,7 +172,7 @@ public static function jsonSpecProvider(): array
192172
}
193173
$contents = file_get_contents($file);
194174
if ($contents === false) {
195-
throw new Exception("Failed to read JSON spec file {$file}");
175+
throw new \Exception("Failed to read JSON spec file {$file}");
196176
}
197177
$i = 0;
198178
$json = json_decode($contents, true);
@@ -254,10 +234,14 @@ private static function evalNestedCode(array &$data): void
254234
}
255235
}
256236

257-
private static function patchSafeString(string $code): string
237+
private static function patchHelperCode(string $code): string
258238
{
259-
$classname = '\\DevTheorem\\Handlebars\\SafeString';
260-
return preg_replace('/ (\\\Handlebars\\\)?SafeString(\s*\(.*?\))?/', ' ' . $classname . '$2', $code)
261-
?? throw new Exception("Failed to patch SafeString in $code");
239+
$code = preg_replace('/ (\\\Handlebars\\\)?SafeString(\s*\(.*?\))?/', ' \\DevTheorem\\Handlebars\\SafeString$2', $code)
240+
?? throw new \Exception("Failed to patch SafeString in $code");
241+
$code = str_replace('Utils::createFrame(', '\DevTheorem\Handlebars\Handlebars::createFrame(', $code);
242+
$code = str_replace('$options[\'name\']', '$options->name', $code);
243+
$code = str_replace('$options[\'data\']', '$options->data', $code);
244+
$code = str_replace('$options[\'hash\']', '$options->hash', $code);
245+
return str_replace('$arguments[count($arguments)-1][\'name\'];', '$arguments[count($arguments)-1]->name;', $code);
262246
}
263247
}

0 commit comments

Comments
 (0)