fix(hot-reload): keep the event dispatcher in the message child

The dev hot reload forgot the `events` instance, so the container rebuilt an
empty dispatcher and every listener the service providers registered was
gone for that message. Events fired from WS controllers reached nobody (a
shop's "order paid" mails and invoice silently never happened in dev).
Model observers kept working, which hid it: Eloquent holds its own
reference to the old dispatcher.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fabian @ Blax Software 2026-09-25 12:56:37 +02:00
parent ca90561c9d
commit 9dc34d5c10
2 changed files with 51 additions and 8 deletions

View File

@ -521,14 +521,10 @@ class Handler implements MessageComponentInterface
// Validator refresh failed, continue anyway
}
// 9. Clear event dispatcher cache (for fresh event/listener bindings)
try {
if ($container->bound('events')) {
$container->forgetInstance('events');
}
} catch (\Throwable $e) {
// Events refresh failed, continue anyway
}
// 9. The event dispatcher is deliberately KEPT: listeners are registered
// once by the service providers (Event::listen in boot()), which never
// run again in this child. A forgotten instance would be rebuilt empty
// and every event fired from the controller would reach no listener.
// 10. Clear WebSocket ControllerResolver cache for fresh controller loading
ControllerResolver::clearCache();

View File

@ -0,0 +1,47 @@
<?php
namespace BlaxSoftware\LaravelWebSockets\Test;
use BlaxSoftware\LaravelWebSockets\Websocket\Handler;
use Illuminate\Support\Facades\Event;
/**
* Regression guard: the dev hot reload in the message child used to forget the
* `events` instance. The container then built an empty dispatcher, and every
* listener the service providers had registered (Event::listen in boot()) was
* gone for that message, so events fired from a WS controller reached nobody
* (e.g. a shop's "order paid" mails and invoice never happened in dev).
*/
class HotReloadKeepsListenersTest extends TestCase
{
protected function tearDown(): void
{
$this->setHotReload(null);
parent::tearDown();
}
private function setHotReload(?bool $on): void
{
$flag = new \ReflectionProperty(Handler::class, 'hotReload');
$flag->setAccessible(true);
$flag->setValue(null, $on);
}
public function test_listeners_registered_at_boot_survive_the_hot_reload()
{
$heard = 0;
Event::listen('shop.order-paid', function () use (&$heard) {
$heard++;
});
$this->setHotReload(true);
$reload = new \ReflectionMethod($this->wsHandler, 'hotReloadChild');
$reload->setAccessible(true);
$reload->invoke($this->wsHandler);
event('shop.order-paid');
$this->assertSame(1, $heard);
}
}