Skip to content

Commit 1bc1289

Browse files
committed
Simplify partial block handling
1 parent cf3b6cb commit 1bc1289

3 files changed

Lines changed: 17 additions & 32 deletions

File tree

src/Compiler.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,6 @@ private function PathExpression(PathExpression $path): string
582582
return $base;
583583
}
584584

585-
// @partial-block as variable: truthy when an active partial block exists
586-
if ($data && $depth === 0 && count($stringParts) === 1 && $stringParts[0] === 'partial-block') {
587-
return "\$cx->partialBlock !== null ? true : null";
588-
}
589-
590585
$isLength = end($stringParts) === 'length';
591586
$isCurrentContextPath = !$hasSubExprHead && !$data && $depth === 0;
592587
$scoped = $isCurrentContextPath && self::scopedId($path);

src/Runtime.php

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ final class Runtime
1414
{
1515
/** @var array<string, Closure>|null */
1616
private static ?array $defaultHelpers = null;
17-
/** Parent RuntimeContext during a user-partial invocation, null at top level. */
18-
private static ?RuntimeContext $partialContext = null;
1917

2018
/**
2119
* Default implementations of the built-in Handlebars helpers.
@@ -165,20 +163,20 @@ public static function lookupLength(mixed $base, bool $strict = false): mixed
165163
/**
166164
* Build a RuntimeContext from raw render options and compile-time partial closures.
167165
*
168-
* @param RenderOptions $options
166+
* @param RenderOptions|array{_cx?: RuntimeContext} $options
169167
* @param array<string, Closure> $compiledPartials
170168
*/
171169
public static function createContext(mixed $context, array $options, array $compiledPartials): RuntimeContext
172170
{
173-
$parentCx = self::$partialContext;
171+
$parentCx = $options['_cx'] ?? null;
174172

175173
if ($parentCx !== null) {
176174
// Partial context: reuse the parent's already-merged helpers and partials directly.
177175
// PHP copy-on-write ensures inlinePartials is only copied if the partial registers a new {{#* inline}} partial.
178-
// Inherit the parent's current data so @index, @key, etc. remain accessible inside partials.
176+
// p() always passes the caller's current data frame via options, so partials inherit @index, @key, etc.
179177
// Unset 'root' first to break the reference established by `$in = &$cx->data['root']` in the
180178
// calling template; a direct assignment would write through it and corrupt the caller's $in.
181-
$data = $parentCx->data;
179+
$data = $options['data'] ?? [];
182180
unset($data['root']);
183181
$data['root'] = $context;
184182
return new RuntimeContext(
@@ -187,12 +185,11 @@ public static function createContext(mixed $context, array $options, array $comp
187185
inlinePartials: $parentCx->inlinePartials,
188186
depths: $parentCx->depths,
189187
data: $data,
190-
partialBlock: $parentCx->partialBlock,
191188
);
192189
}
193190

194191
$data = $options['data'] ?? [];
195-
$data['root'] = $data['root'] ?? $context;
192+
$data['root'] ??= $context;
196193
$extraHelpers = $options['helpers'] ?? [];
197194
return new RuntimeContext(
198195
helpers: $extraHelpers ? array_replace(Runtime::defaultHelpers(), $extraHelpers) : Runtime::defaultHelpers(),
@@ -392,7 +389,7 @@ public static function merge(mixed $a, mixed $b): mixed
392389
}
393390

394391
/**
395-
* Call {{> partial}}
392+
* Equivalent to invokePartial in the Handlebars.js runtime.
396393
* @param array<string, mixed> $hash named hash overrides merged into the context
397394
* @param string $indent whitespace to prepend to each line of the partial's output
398395
* @param mixed $callerIn When compat mode is enabled, the caller's current $in pushed onto depths so
@@ -401,7 +398,8 @@ public static function merge(mixed $a, mixed $b): mixed
401398
public static function p(RuntimeContext $cx, ?string $name, mixed $context, array $hash, string $indent, ?Closure $partialBlock = null, mixed $callerIn = null): string
402399
{
403400
$fn = match ($name) {
404-
'@partial-block' => $cx->partialBlock,
401+
// @partial-block is resolved from data, mirroring HBS.js resolvePartial
402+
'@partial-block' => $cx->data['partial-block'] ?? null,
405403
// name can be null if a dynamic partial doesn't resolve to anything
406404
null => null,
407405
// inlinePartials (block-scoped {{#* inline}}) take precedence over partials (persistent),
@@ -415,18 +413,14 @@ public static function p(RuntimeContext $cx, ?string $name, mixed $context, arra
415413
}
416414

417415
// Install a wrapper as the active @partial-block so the partial can invoke it via {{> @partial-block}}.
418-
// The wrapper temporarily restores the previously active block before calling $partialBlock,
419-
// allowing nested partial blocks to correctly resolve their own @partial-block.
416+
// Mirrors HBS.js invokePartial: the wrapper receives the current data frame, restores partial-block to
417+
// currentPartialBlock (captured from the closure), then calls the block content with that frame.
420418
if ($partialBlock !== null) {
421-
$currentBlock = $cx->partialBlock;
422-
$cx->partialBlock = static function (mixed $blockContext) use ($partialBlock, $currentBlock): string {
423-
$callingCx = self::$partialContext;
424-
assert($callingCx !== null);
425-
$saved = $callingCx->partialBlock;
426-
$callingCx->partialBlock = $currentBlock;
427-
$result = $partialBlock($blockContext);
428-
$callingCx->partialBlock = $saved;
429-
return $result;
419+
$currentBlock = $cx->data['partial-block'] ?? null;
420+
$cx->data['partial-block'] = static function (mixed $context = null, array $options = []) use ($partialBlock, $currentBlock): string {
421+
$options['data'] ??= [];
422+
$options['data']['partial-block'] = $currentBlock;
423+
return $partialBlock($context, $options);
430424
};
431425
}
432426

@@ -437,17 +431,14 @@ public static function p(RuntimeContext $cx, ?string $name, mixed $context, arra
437431
if ($callerIn !== null) {
438432
$cx->depths[] = $callerIn;
439433
}
440-
$prev = self::$partialContext;
441-
self::$partialContext = $cx;
442434
try {
443-
$result = $fn($context);
435+
$result = $fn($context, ['data' => $cx->data, '_cx' => $cx]);
444436
} finally {
445-
self::$partialContext = $prev;
446437
if ($callerIn !== null) {
447438
array_pop($cx->depths);
448439
}
449440
if ($partialBlock !== null) {
450-
$cx->partialBlock = $currentBlock;
441+
$cx->data['partial-block'] = $currentBlock;
451442
}
452443
}
453444

src/RuntimeContext.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,5 @@ public function __construct(
2222
public array $inlinePartials = [],
2323
public array $depths = [],
2424
public array $data = [],
25-
public ?Closure $partialBlock = null,
2625
) {}
2726
}

0 commit comments

Comments
 (0)