@@ -162,11 +162,13 @@ pub fn preprocess(content: &str) -> (String, BladeSourceMap) {
162162 replacement = " if (true): " . to_string ( ) ;
163163 next_mode = Mode :: Html ;
164164 }
165+ } else if matches ! ( directive, "foreach" | "forelse" ) {
166+ replacement = format ! ( " {} " , translate_directive( directive) ) ;
167+ next_mode = Mode :: DirectiveArgs ( ": /** @var object{index: int, iteration: int, remaining: int, count: int, first: bool, last: bool, even: bool, odd: bool, depth: int, parent: ?object} $loop */ $loop = (object)[];" ) ;
168+ paren_depth = 0 ;
165169 } else if matches ! (
166170 directive,
167171 "if" | "elseif"
168- | "foreach"
169- | "forelse"
170172 | "for"
171173 | "while"
172174 | "switch"
@@ -446,6 +448,29 @@ mod tests {
446448 ) ;
447449 }
448450
451+ #[ test]
452+ fn test_preprocess_foreach_loop_variable ( ) {
453+ let content = "@foreach($items as $item)\n {{ $loop->first }}\n @endforeach\n " ;
454+ let ( php, _) = preprocess ( content) ;
455+ assert ! ( php. contains( "$loop" ) , "should inject $loop variable: {}" , php) ;
456+ assert ! (
457+ php. contains( "object{index: int" ) ,
458+ "should have typed $loop: {}" ,
459+ php
460+ ) ;
461+ // $loop should be declared before its usage
462+ let loop_decl = php. find ( "$loop = (object)[];" ) . unwrap ( ) ;
463+ let loop_use = php. rfind ( "$loop" ) . unwrap ( ) ;
464+ assert ! ( loop_use > loop_decl, "$loop usage after declaration: {}" , php) ;
465+ }
466+
467+ #[ test]
468+ fn test_preprocess_forelse_loop_variable ( ) {
469+ let content = "@forelse($items as $item)\n {{ $loop->index }}\n @empty\n @endforelse\n " ;
470+ let ( php, _) = preprocess ( content) ;
471+ assert ! ( php. contains( "$loop = (object)[];" ) , "forelse should also inject $loop: {}" , php) ;
472+ }
473+
449474 #[ test]
450475 fn test_preprocess_echo_with_string_braces ( ) {
451476 let content = "{{ \" }} \" }}" ;
0 commit comments