All rules

Sheath rule

blade-prefer-forelse

Prefer @forelse over @if + @foreach when checking for empty collections.
Package
Core
Category
Blade
Default severity
info by default
Auto-fix
Auto-fix available

as a dangerous fix when the block has an @else branch. See Auto-fix.

#Why

@if wrapping a @foreach states the emptiness check twice: once in the condition and once in the loop that would not run anyway. @forelse is the directive Blade provides for exactly this shape, and it puts the empty case next to the loop it belongs to rather than a branch away.

They do not compile the same way. @forelse sets a flag inside the loop and checks it afterwards, so it never asks the collection how many items it holds. @if (count($rows) > 0) does, which is why @forelse works on a generator, where count() raises a TypeError.

This is a stylistic preference, so it belongs to stylistic rather than recommended.

#Examples

#Bad

<!-- Verbose: @if wrapping @foreach -->
@if(count($users) > 0)
@foreach($users as $user)
<p>{{ $user->name }}</p>
@endforeach
@else
<p>No users found.</p>
@endif
<!-- Also verbose with a collection emptiness check -->
@if($items->isNotEmpty())
@foreach($items as $item)
<li>{{ $item->title }}</li>
@endforeach
@else
<p>No items available.</p>
@endif
@if($products->count())
@foreach($products as $product)
<div>{{ $product->name }}</div>
@endforeach
@else
<p>No products.</p>
@endif

#Good

<!-- Clean: @forelse handles both cases -->
@forelse($users as $user)
<p>{{ $user->name }}</p>
@empty
<p>No users found.</p>
@endforelse
@forelse($items as $item)
<li>{{ $item->title }}</li>
@empty
<p>No items available.</p>
@endforelse
@forelse($products as $product)
<div>{{ $product->name }}</div>
@empty
<p>No products.</p>
@endforelse

#Pattern Detection

The following patterns are reported:

<!-- Pattern 1: count() > 0 -->
@if(count($items) > 0)
@foreach($items as $item)
@endforeach
@else
@endif
<!-- Pattern 2: ->isNotEmpty() -->
@if($items->isNotEmpty())
@foreach($items as $item)
@endforeach
@else
@endif
<!-- Pattern 3: ->count() -->
@if($items->count())
@foreach($items as $item)
@endforeach
@else
@endif

#Auto-fix

When the block has an @else branch, --fix rewrites it. Both bodies are carried over unchanged apart from indentation: the loop body loses one level, because the @foreach it sat inside is gone.

{{-- before --}}
@if (count($posts) > 0)
@foreach ($posts as $post)
<li>{{ $post->title }}</li>
@endforeach
@else
<p>Nothing yet.</p>
@endif
{{-- after --}}
@forelse ($posts as $post)
<li>{{ $post->title }}</li>
@empty
<p>Nothing yet.</p>
@endforelse

The indent unit is read from the file, so tabs stay tabs and a two-space project stays on two spaces.

The rewrite requires --dangerous. A valid object can implement both Countable and IteratorAggregate while returning a count that differs from the number of values it yields, so the original guard and @forelse are not provably behavior-equivalent.

#When it reports but will not rewrite

This is a structural rewrite rather than an edit to one token, so it only runs when the shape is unambiguous. In these cases the finding is still reported, with no fix attached:

Shape Why
Anything beside the @foreach in the @if branch It would be lost, or pulled inside the loop
An @elseif branch @forelse has no third branch to hold it
A bare @empty in the loop body It would become the @forelse branch separator
No @else branch Nothing to carry into @empty; removing the @if is a different edit

#Notes

  • Only applies when checking the same variable for emptiness and iteration
  • Bare $items and ! empty($items) guards are not treated as emptiness checks. PHP objects are truthy even when an iterable object such as a Laravel collection contains no items, so rewriting those guards would change output.
  • Nullsafe checks such as $items?->isNotEmpty() are not treated as candidates: the guard is safe when $items is null, while @forelse must iterate it.
  • The @else block maps directly to @empty
  • This preference has info severity and belongs to the stylistic preset rather than recommended

#Related Rules