skip to content

In a Blade view, when should you use @forelse instead of @foreach, and why does @empty mean two different things?

level: juniorimportance: should knowfreq 48%

answer

  1. loop plus a no-results branch
  2. @forelse ... @empty ... @endforelse
  3. @empty($x) wraps PHP's empty()
  4. an object is never empty()
  5. @isset: defined and not null

basics

~20 s

@forelse loops like @foreach but renders its @empty branch when the list has no items. A standalone @empty($x) ... @endempty is a different directive that wraps PHP's empty(), which is false for any object, even an empty Collection.

solid answer

~30 s

`@forelse ($plans as $plan) ... @empty ... @endforelse` compiles to a `foreach` that clears a flag on the first item, followed by an `if` on that flag, so the `@empty` branch renders only when nothing was iterated - the "no plans available" message. With an argument, `@empty($x) ... @endempty` is a separate conditional that compiles to PHP's `empty($x)`. That matters because `empty()` is `false` for every object, so `@empty($plans)` on an empty Eloquent `Collection` never fires; use `@forelse` or `@if ($plans->isEmpty())`. `@isset($x)` compiles to `isset()`: defined and not `null`. `@unless ($cond)` is `@if (! $cond)`.

code

html · 16 lines
html
@isset($coupon)
    <p class="banner">Code {{ $coupon->code }} applied</p>
@endisset

<ul>
@forelse ($plans as $plan)
    <li>{{ $plan->name }} - {{ $plan->price_label }}</li>
@empty
    <li>No plans are available in your region.</li>
@endforelse
</ul>

{{-- Bug: an empty Collection is an object, so this never fires --}}
@empty($plans)
    <p>Nothing to show.</p>
@endempty

go deeper

for a junior

Recall @forelse with its @empty branch for lists that may be empty, and @isset for optional variables.

for a middle

Explain that @forelse tracks whether the loop ran, while @empty($x) is PHP's empty(), which is false for any object including an empty Collection.

for a senior

Catch the empty-collection bug in review and standardise on isEmpty() or @forelse for collections, with @isset for optional view data.

for a principal

Make view contracts explicit - which variables a view requires, which are optional - so templates stop guarding against data they should always receive.

## @forelse: a loop with a no-results branch A very common template shape is "list the items, or say there are none". With `@foreach` it takes an extra `@if`: ```html @if (count($plans) > 0) @foreach ($plans as $plan) ... @endforeach @else <p>No plans are available in your region.</p> @endif ``` Blade's `@forelse` merges the two: ```html @forelse ($plans as $plan) <li>{{ $plan->name }}</li> @empty <li>No plans are available in your region.</li> @endforelse ``` Compiled, `@forelse` sets a flag such as `$__empty_1 = true`, opens a `foreach` that sets it to `false` on every iteration, and `@empty` closes the loop and opens `if ($__empty_1)`. Two consequences: - The check is **"did the loop run at all"**, not a call to `count()` or `isEmpty()`, so it works for arrays, collections, lazy collections and generators alike. - `$loop` is available inside the `@forelse` body, just as in `@foreach`. ## The other @empty With an argument, `@empty` is a different directive. `@empty($plans) ... @endempty` compiles to `<?php if(empty($plans)): ?>`, the PHP language construct **`empty()`**. Blade decides which one you meant by whether parentheses follow. `empty()` returns `true` for `null`, an undefined variable, `false`, `0`, `0.0`, `""`, `"0"` and `[]`, and `false` for everything else - including **every object**. An Eloquent query returns an `Illuminate\Database\Eloquent\Collection`, which is an object, so: | Value of `$plans` | `@empty($plans)` fires? | |---|---| | `[]` (array) | yes | | `null` | yes | | empty `Collection` | **no** | | `"0"` (string) | yes | The empty-collection row is the bug: a pricing page's "no plans" message never shows. Use `@forelse`, or `@if ($plans->isEmpty())`, for collections. ## @isset and @unless - `@isset($discount) ... @endisset` compiles to `if(isset($discount))`: true when the variable exists and is not `null`. It is the right guard for an **optional view variable** a controller may not pass, where a bare `{{ $discount }}` would throw an undefined-variable error. - `@unless ($user?->onTrial()) ... @endunless` compiles to `if (! (...))`. It reads well for a single negative condition; with `@else` branches, a plain `@if` is usually clearer. ## Choosing between them 1. Rendering a list with a fallback message: `@forelse`. 2. Guarding an optional variable: `@isset`, or `$discount ?? null` inside an echo. 3. Testing a collection for items: `$plans->isEmpty()` / `isNotEmpty()` inside `@if`. 4. Testing a scalar or array for "nothing there": `@empty($x)` is fine, knowing `"0"` counts as empty. ## A pricing-table example A plans page receives `$plans` (a collection that may be filtered to nothing for some regions) and an optional `$coupon`: - `@forelse ($plans as $plan)` renders cards, with `@empty` showing a "no plans in your region" notice. - `@isset($coupon)` shows the coupon banner only when the controller passed one. - `@unless ($plans->contains('trial', true))` hides the "free trial" footnote when no plan offers one. ## @switch for many fixed branches When a value maps to several fixed outputs - a plan's billing period, say - `@switch` reads better than a chain of `@elseif`: ```html @switch($plan->period) @case('month') per month @break @case('year') per year, billed annually @break @default one-off payment @endswitch ``` It compiles to PHP's `switch`, so each `@case` needs its own `@break` or execution falls through to the next case. Blade compiles the first `@case` into the same PHP block as `@switch`, so whitespace between the two does not leak into the output. ## Why the naming bites `@empty` inside `@forelse` and `@empty(...)` outside it share a name but compile to unrelated PHP. Code review catches the difference by the parentheses: a bare `@empty` must sit inside a `@forelse`; an `@empty(...)` must be closed by `@endempty`. Mixing them produces confusing compile errors - usually an unexpected `endforeach` or `endif` in the compiled file.

  • Does @forelse work with a LazyCollection or a generator?
    Yes. `@forelse` does not count the data; it sets a flag before the loop and clears it on the first iteration, then checks the flag after `@empty`. Anything `foreach` can walk works, including lazy collections, and the `@empty` branch appears only if no item was produced.
  • What does @isset($x) guard against that {{ $x }} alone would not?
    An undefined view variable. If the controller did not pass `$x`, echoing it raises an undefined-variable error, which Laravel turns into an exception. `@isset` compiles to PHP's `isset()`, true only when the variable exists and is not `null`, so the block is skipped cleanly.

saying these in an interview costs you the question

  • @empty($collection) detects an empty Eloquent Collection
  • @forelse calls count() on the data before looping
  • The @empty inside @forelse and @empty($x) compile to the same thing
  • @isset is true for a variable set to null
  • $loop is not available inside @forelse