44
55namespace DevTheorem \Handlebars ;
66
7+ use Closure ;
8+
79/**
810 * @internal
911 * @phpstan-import-type RenderOptions from Handlebars
1012 */
1113final class Runtime
1214{
13- /** @var array<string, \ Closure>|null */
15+ /** @var array<string, Closure>|null */
1416 private static ?array $ defaultHelpers = null ;
1517 /** Parent RuntimeContext during a user-partial invocation, null at top level. */
1618 private static ?RuntimeContext $ partialContext = null ;
@@ -19,7 +21,7 @@ final class Runtime
1921 * Default implementations of the built-in Handlebars helpers.
2022 * These are pre-registered in every runtime context and can be overridden.
2123 *
22- * @return array<string, \ Closure>
24+ * @return array<string, Closure>
2325 */
2426 public static function defaultHelpers (): array
2527 {
@@ -30,7 +32,7 @@ public static function defaultHelpers(): array
3032 }
3133 /** @var HelperOptions $options */
3234 $ options = $ args [1 ];
33- $ condition = $ args [0 ] instanceof \ Closure ? $ args [0 ]($ options ->scope ) : $ args [0 ];
35+ $ condition = $ args [0 ] instanceof Closure ? $ args [0 ]($ options ->scope ) : $ args [0 ];
3436 return static ::ifvar ($ condition , (bool ) ($ options ->hash ['includeZero ' ] ?? false ))
3537 ? $ options ->fn ($ options ->scope )
3638 : $ options ->inverse ();
@@ -41,7 +43,7 @@ public static function defaultHelpers(): array
4143 }
4244 /** @var HelperOptions $options */
4345 $ options = $ args [1 ];
44- $ condition = $ args [0 ] instanceof \ Closure ? $ args [0 ]($ options ->scope ) : $ args [0 ];
46+ $ condition = $ args [0 ] instanceof Closure ? $ args [0 ]($ options ->scope ) : $ args [0 ];
4547 return static ::ifvar ($ condition , (bool ) ($ options ->hash ['includeZero ' ] ?? false ))
4648 ? $ options ->inverse ()
4749 : $ options ->fn ($ options ->scope );
@@ -50,7 +52,7 @@ public static function defaultHelpers(): array
5052 if (!$ options ) {
5153 throw new \Exception ('Must pass iterator to #each ' );
5254 }
53- if ($ context instanceof \ Closure) {
55+ if ($ context instanceof Closure) {
5456 $ context = $ context ($ options ->scope );
5557 }
5658 if ($ context instanceof \Traversable) {
@@ -66,7 +68,7 @@ public static function defaultHelpers(): array
6668 }
6769 /** @var HelperOptions $options */
6870 $ options = $ args [1 ];
69- $ context = $ args [0 ] instanceof \ Closure ? $ args [0 ]($ options ->scope ) : $ args [0 ];
71+ $ context = $ args [0 ] instanceof Closure ? $ args [0 ]($ options ->scope ) : $ args [0 ];
7072 if (static ::ifvar ($ context )) {
7173 return $ options ->fn ($ context , ['blockParams ' => [$ context ]]);
7274 }
@@ -164,7 +166,7 @@ public static function lookupLength(mixed $base, bool $strict = false): mixed
164166 * Build a RuntimeContext from raw render options and compile-time partial closures.
165167 *
166168 * @param RenderOptions $options
167- * @param array<string, \ Closure> $compiledPartials
169+ * @param array<string, Closure> $compiledPartials
168170 */
169171 public static function createContext (mixed $ context , array $ options , array $ compiledPartials ): RuntimeContext
170172 {
@@ -201,11 +203,12 @@ public static function createContext(mixed $context, array $options, array $comp
201203
202204 /**
203205 * Invoke $v if it is callable, passing any extra args; otherwise return $v as-is.
204- * Used for data variables that may hold functions (e.g. {{@hello}} or {{@hello "arg"}}).
206+ * Used for data variables that may hold functions (e.g. {{@hello}}) and for non-simple
207+ * pathed expressions with arguments (e.g. {{./helper "arg"}}).
205208 */
206209 public static function dv (mixed $ v , mixed ...$ args ): mixed
207210 {
208- return $ v instanceof \ Closure ? $ v (...$ args ) : $ v ;
211+ return $ v instanceof Closure ? $ v (...$ args ) : $ v ;
209212 }
210213
211214 /**
@@ -219,7 +222,7 @@ public static function dv(mixed $v, mixed ...$args): mixed
219222 public static function cv (mixed &$ _this , string $ name ): mixed
220223 {
221224 $ v = is_array ($ _this ) ? ($ _this [$ name ] ?? null ) : null ;
222- return $ v instanceof \ Closure ? $ v ($ _this ) : $ v ;
225+ return $ v instanceof Closure ? $ v ($ _this ) : $ v ;
223226 }
224227
225228 /**
@@ -241,18 +244,13 @@ public static function hv(RuntimeContext $cx, string $name, mixed &$_this): mixe
241244 }
242245
243246 /**
244- * For {{#if}} and {{#unless}}.
245- *
246- * @param array<array<mixed>|string|int>|string|\Stringable|int|float|bool|null $v value to be tested
247- * @param bool $zero include zero as true
248- *
249- * @return bool Return true when the value is not null nor false.
247+ * Returns true or false following the semantics of {{#if}} and {{#unless}} in Handlebars.js.
250248 */
251- public static function ifvar (mixed $ v , bool $ zero = false ): bool
249+ public static function ifvar (mixed $ v , bool $ includeZero = false ): bool
252250 {
253251 return $ v !== null
254252 && $ v !== false
255- && ($ zero || ($ v !== 0 && $ v !== 0.0 ))
253+ && ($ includeZero || ($ v !== 0 && $ v !== 0.0 ))
256254 && $ v !== ''
257255 && (!is_array ($ v ) || $ v )
258256 && (!$ v instanceof \Stringable || (string ) $ v !== '' );
@@ -300,13 +298,14 @@ public static function raw(mixed $value): string
300298
301299 /**
302300 * For {{#var}} and {{^var}} sections.
303- * Pass null for $cb when compiling an inverted section ({{^var}}) — blockHelperMissing will call inverse().
301+ * Pass null for $cb when compiling an inverted section ({{^var}}): blockHelperMissing routes
302+ * truthy contexts through fn() (which returns '' when $cb is null) and falsy contexts through inverse().
304303 *
305304 * @param mixed $in input data with current scope
306- * @param \ Closure|null $cb callback function to render child context; null for inverted sections
307- * @param \ Closure|null $else callback function to render child context when {{else}}
305+ * @param Closure|null $cb callback function to render child context; null for inverted sections
306+ * @param Closure|null $else callback function to render child context when {{else}}
308307 */
309- public static function sec (RuntimeContext $ cx , mixed $ value , mixed $ in , ?\ Closure $ cb , ?\ Closure $ else = null , ?string $ helperName = null ): string
308+ public static function sec (RuntimeContext $ cx , mixed $ value , mixed $ in , ?Closure $ cb , ?Closure $ else = null , ?string $ helperName = null ): string
310309 {
311310 $ helper = $ helperName !== null ? ($ cx ->helpers [$ helperName ] ?? null ) : null ;
312311 if ($ helper !== null ) {
@@ -316,7 +315,7 @@ public static function sec(RuntimeContext $cx, mixed $value, mixed $in, ?\Closur
316315 // Lambda functions in block position: simple-path identifiers ($helperName set) receive
317316 // HelperOptions so they can render fn/inverse; complex paths ($helperName null) are called
318317 // with no arguments, mirroring HBS.js which does not treat them as helper calls.
319- if ($ value instanceof \ Closure) {
318+ if ($ value instanceof Closure) {
320319 $ result = $ helperName !== null
321320 ? $ value (new HelperOptions (scope: $ in , data: $ cx ->data , cx: $ cx , cb: $ cb , inv: $ else ))
322321 : $ value ();
@@ -328,11 +327,6 @@ public static function sec(RuntimeContext $cx, mixed $value, mixed $in, ?\Closur
328327
329328 /**
330329 * Get merged context.
331- *
332- * @param array<array<mixed>|string|int>|object|string|int|null $a the context to be merged
333- * @param array<array<mixed>|string|int|null>|string|int|null $b the new context to overwrite
334- *
335- * @return array<array<mixed>|string|int|null>|object|string|int|null the merged context object
336330 */
337331 public static function merge (mixed $ a , mixed $ b ): mixed
338332 {
@@ -358,7 +352,7 @@ public static function merge(mixed $a, mixed $b): mixed
358352 * @param array<string, mixed> $hash named hash overrides merged into the context
359353 * @param string $indent whitespace to prepend to each line of the partial's output
360354 */
361- public static function p (RuntimeContext $ cx , string $ name , mixed $ context , array $ hash , string $ indent , ?\ Closure $ partialBlock = null ): string
355+ public static function p (RuntimeContext $ cx , string $ name , mixed $ context , array $ hash , string $ indent , ?Closure $ partialBlock = null ): string
362356 {
363357 // inlinePartials (block-scoped {{#* inline}}) take precedence over partials (persistent),
364358 // mirroring Handlebars.js which checks options.partials before env.partials.
@@ -415,9 +409,9 @@ public static function p(RuntimeContext $cx, string $name, mixed $context, array
415409 * For {{#* inline "name"}} and {{#> partial}}fallback{{/partial}} blocks.
416410 *
417411 * @param string $name partial name
418- * @param \ Closure $partial the compiled partial
412+ * @param Closure $partial the compiled partial
419413 */
420- public static function in (RuntimeContext $ cx , string $ name , \ Closure $ partial ): string
414+ public static function in (RuntimeContext $ cx , string $ name , Closure $ partial ): string
421415 {
422416 $ cx ->inlinePartials [$ name ] = $ partial ;
423417 return '' ;
@@ -435,7 +429,7 @@ public static function dynhbch(RuntimeContext $cx, string $name, array $position
435429 }
436430
437431 $ fn = $ _this [$ name ] ?? null ;
438- if ($ fn instanceof \ Closure) {
432+ if ($ fn instanceof Closure) {
439433 return static ::hbch ($ cx , $ fn , $ name , $ positional , $ hash , $ _this );
440434 }
441435
@@ -448,15 +442,18 @@ public static function dynhbch(RuntimeContext $cx, string $name, array $position
448442 }
449443
450444 /**
451- * For single known helpers.
445+ * Invoke a resolved helper Closure with positional params, hash, and a HelperOptions instance.
446+ * Used for known helpers (direct hbch calls from generated code), runtime-registered helpers
447+ * (called from hv()), context closures (called from dynhbch()), and built-in fallbacks like
448+ * helperMissing/blockHelperMissing.
452449 *
453450 * @param array<mixed> $positional
454451 * @param array<string, mixed> $hash
455452 * @param mixed $_this current rendering context for the helper
456453 */
457- public static function hbch (RuntimeContext $ cx , \ Closure $ helper , string $ name , array $ positional , array $ hash , mixed &$ _this ): mixed
454+ public static function hbch (RuntimeContext $ cx , Closure $ helper , string $ name , array $ positional , array $ hash , mixed &$ _this ): mixed
458455 {
459- /** @var \WeakMap<\ Closure, int>|null $paramCounts */
456+ /** @var \WeakMap<Closure, int>|null $paramCounts */
460457 static $ paramCounts = null ;
461458 $ paramCounts ??= new \WeakMap ();
462459
@@ -488,11 +485,11 @@ public static function hbch(RuntimeContext $cx, \Closure $helper, string $name,
488485 * @param array<mixed> $positional
489486 * @param array<string, mixed> $hash
490487 * @param mixed $_this current rendering context for the helper
491- * @param \ Closure|null $cb callback function to render child context (null for inverted blocks)
492- * @param \ Closure|null $else callback function to render child context when {{else}}
488+ * @param Closure|null $cb callback function to render child context (null for inverted blocks)
489+ * @param Closure|null $else callback function to render child context when {{else}}
493490 * @param array<mixed> $outerBlockParams outer block param stack for block params declared by the template
494491 */
495- public static function hbbch (RuntimeContext $ cx , \ Closure $ helper , string $ name , array $ positional , array $ hash , mixed &$ _this , ?\ Closure $ cb , ?\ Closure $ else , int $ blockParamCount = 0 , array $ outerBlockParams = []): string
492+ public static function hbbch (RuntimeContext $ cx , Closure $ helper , string $ name , array $ positional , array $ hash , mixed &$ _this , ?Closure $ cb , ?Closure $ else , int $ blockParamCount = 0 , array $ outerBlockParams = []): string
496493 {
497494 $ positional [] = new HelperOptions (
498495 scope: $ _this ,
@@ -513,18 +510,18 @@ public static function hbbch(RuntimeContext $cx, \Closure $helper, string $name,
513510 * @param array<mixed> $positional
514511 * @param array<string, mixed> $hash
515512 * @param array<string,array<mixed>|string|int> $_this current rendering context for the helper
516- * @param \ Closure $cb callback function to render child context
517- * @param \ Closure|null $else callback function to render child context when {{else}}
513+ * @param Closure|null $cb callback function to render main block; null for inverted sections with params/hash
514+ * @param Closure|null $else callback function to render {{else}}
518515 * @param array<mixed> $outerBlockParams outer block param stack for block params declared by the template
519516 */
520- public static function dynhbbch (RuntimeContext $ cx , string $ name , mixed $ callable , array $ positional , array $ hash , mixed &$ _this , ?\ Closure $ cb , ?\ Closure $ else , int $ blockParamCount , array $ outerBlockParams ): mixed
517+ public static function dynhbbch (RuntimeContext $ cx , string $ name , mixed $ callable , array $ positional , array $ hash , mixed &$ _this , ?Closure $ cb , ?Closure $ else , int $ blockParamCount , array $ outerBlockParams ): mixed
521518 {
522519 $ helper = $ cx ->helpers [$ name ] ?? null ;
523520 if ($ helper !== null ) {
524521 return static ::hbbch ($ cx , $ helper , $ name , $ positional , $ hash , $ _this , $ cb , $ else , $ blockParamCount , $ outerBlockParams );
525522 }
526523
527- if (!$ callable instanceof \ Closure) {
524+ if (!$ callable instanceof Closure) {
528525 return static ::hbbch ($ cx , $ cx ->helpers ['helperMissing ' ], $ name , $ positional , $ hash , $ _this , $ cb , $ else , $ blockParamCount , $ outerBlockParams );
529526 }
530527
@@ -535,7 +532,7 @@ public static function dynhbbch(RuntimeContext $cx, string $name, mixed $callabl
535532 * Resolve the return value of a block helper call:
536533 * pass through string/SafeString, stringify arrays, or delegate non-string values to blockHelperMissing.
537534 */
538- private static function resolveBlockResult (RuntimeContext $ cx , mixed $ result , mixed $ _this , ?\ Closure $ cb , ?\ Closure $ else ): string
535+ private static function resolveBlockResult (RuntimeContext $ cx , mixed $ result , mixed $ _this , ?Closure $ cb , ?Closure $ else ): string
539536 {
540537 if (is_string ($ result ) || $ result instanceof SafeString) {
541538 return (string ) $ result ;
0 commit comments