Skip to content
LaravelBlog

livewire/livewire: Reverting the handler-marker approach for TypeError propagation

LaravelBlogBot
LaravelBlogBot

Contributed by calebporzio

Part of the livewire v4.4.7 release

This pull request reverts the handler-marker approach introduced in #10407 and hands its job back to a version-safe propagation mechanism (#10621). In short: Livewire's update-handling pipeline goes back to rethrowing TypeErrors only when app.debug is on, and the "rethrow even when exception handling is disabled in tests" behavior moves into a Livewire-owned scope that works on every supported Laravel version and can no longer skip broker cleanup. If you are upgrading from v3.8.10, there is nothing to migrate — the user-facing fix you may have read about in #10407 still ships, just implemented differently.

What changed

Two files were touched. In src/Mechanisms/HandleRequests/HandleRequests.php, the catch block that guards every Livewire update was simplified back to:

} catch (\TypeError $e) {
    report($e);

    if (config('app.debug')) throw $e;

    abort(419);
}

The instanceof WithoutExceptionHandlingHandler check added by #10407 is gone, along with the ExceptionHandler and WithoutExceptionHandlingHandler imports. In src/Features/SupportTesting/UnitTest.php, the two tests that pinned #10407's behavior — plus the ComponentWithActionThatThrowsTypeError fixture — were removed, so the replacement approach can assert the same outcomes with its own tests.

Why the marker approach was rolled back

The reverted implementation detected "tests are running with $this->withoutExceptionHandling()" by checking whether Laravel's exception handler had been swapped for Illuminate\Foundation\Testing\Concerns\WithoutExceptionHandlingHandler. That marker class only exists from Laravel 11 onward, so on Laravel 10 — which Livewire declares support for — the check silently never matched and the rethrow never happened. The fix would have been dead code on a supported framework version.

It had a second, subtler problem: when a TypeError escaped through that early throw, the RequestBroker skipped restoring the exception handler and middleware overrides it had swapped in for the duration of the update. Cleanup that should run unconditionally was skipped on exactly the path most likely to trigger it. #10621 fixes the same user-facing problem with an internal propagation scope that works across Laravel 10–13 and restores broker state inside a finally block, so exception propagation and cleanup can no longer interfere with each other.

What your tests will see

The visible contract for tests is unchanged from what #10407 promised: when exception handling is disabled, the original TypeError reaches your test instead of a generic 419, regardless of app.debug and regardless of Laravel version:

class SaveInventoryTest extends Tests\TestCase
{
    /** @test */
    function type_errors_surface_when_exception_handling_is_disabled()
    {
        $this->withoutExceptionHandling();

        // saveItems(array $items) — a string argument triggers a TypeError
        Livewire::test(InventoryDashboard::class)
            ->call('saveItems', 'not-an-array');

        // 4.x: the real TypeError from the component action is thrown
        // (on Laravel 10 through 13), not a bare 419 with no stack trace.
    }
}

If you happened to pull an interim release containing #10407 and wrote tests around its behavior on Laravel 11+, those tests keep passing — same assertion, different plumbing underneath.

Runtime behavior outside tests

For normal HTTP updates, nothing changes relative to the behavior you knew on v3.8.10: a TypeError escaping a component action is reported through report($e) and then converted into a 419 in production, while debug mode rethrows so you see the real error locally:

// .env
APP_DEBUG=false  // TypeErrors → reported, then abort(419)
APP_DEBUG=true   // TypeErrors → rethrown with a full stack trace

The only internal differences are how the "tests disabled exception handling" case is detected and how reliably broker state is restored afterwards.

Upgrading from v3.8.10

There are no public API, configuration, or component-code changes to make. The reverted check and the removed test fixture were always internal to Livewire's request-handling mechanism and test suite. If your application subclasses or decorates WithoutExceptionHandlingHandler, note that Livewire no longer references that class at all — it was never part of Livewire's public surface. One practical check: make sure the release you land on includes #10621's propagation scope alongside this revert, since it is what preserves the test-time rethrow behavior on every supported framework version.

Takeaways

  • #10407's WithoutExceptionHandlingHandler marker check is reverted; app.debug is once again the only runtime trigger for rethrowing TypeErrors from update handling.
  • The user-facing test fix survives via #10621's version-safe propagation scope, which works on Laravel 10–13 rather than Laravel 11+ only.
  • Broker state (exception handler and middleware overrides) is now restored in finally, so an escaping exception can no longer skip cleanup and leak state between requests.
  • No upgrade steps required: no public API changed, and tests written against the rethrow behavior keep passing.

Sources

More from this release

Related Articles