Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Resonate Laravel Package

webpatser/resonate

Resonate is a Laravel package for adding real-time event broadcasting and pub/sub messaging to your app. It offers a simple, expressive API for publishing and subscribing to events with minimal configuration, helping you build interactive features faster.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config, then locate two things before writing any code:

  1. The plugin contract — ConnectionLifecycle. As of v0.4.0 it has four methods: onOpen, onSubscribe, onUnsubscribe, and onClose.
  2. PluginContext — the read/write surface a plugin uses to inspect and mutate connection state. connectionsOn() reads; unsubscribe() writes (new in v0.4.0).

Register your plugin with PluginManager (config array or a register()/plugin() call depending on how the package wires it), then start with the smallest useful case: an audit/logger plugin.

final class ConnectionAuditor implements ConnectionLifecycle
{
    public function __construct(private PluginContext $context) {}

    public function onOpen(Connection $c): void { /* ... */ }

    public function onSubscribe(Connection $c, Channel $ch): void
    {
        logger()->info('join', ['conn' => $c->id(), 'channel' => $ch->name()]);
    }

    // NEW in v0.4.0 — required, interface was extended
    public function onUnsubscribe(Connection $c, Channel $ch): void
    {
        logger()->info('leave', ['conn' => $c->id(), 'channel' => $ch->name()]);
    }

    public function onClose(Connection $c): void { /* ... */ }
}

If you already have a plugin written against v0.3.0, it will not load — see Gotchas.

Implementation Patterns

Channel-scoped presence tracking. This is the pattern the release is built for. Keep a counter per channel and adjust it in onSubscribe / onUnsubscribe. Use PluginContext::connectionsOn($channel) to reconcile against truth on startup or after deploys; don't treat your counter as authoritative.

Evict a connection from one channel. Use PluginContext::unsubscribe($conn, 'some-channel') for moderation, capacity limits, or kicking a user out of a room without dropping the socket. This is a direct action: it removes the connection from the channel and leaves the socket and all other subscriptions intact. It deliberately does not fire onUnsubscribe — see Gotchas.

Pair the read and write APIs. connectionsOn() + unsubscribe() are the intended symmetric pair. A moderation plugin can read the room, decide, and evict without ever touching the socket lifecycle.

Fan-out safety. PluginManager::notifyUnsubscribe() isolates exceptions per plugin, so one bad plugin won't stop the others from being notified. Lean on this: you can register multiple independent plugins (audit, rate-limit, presence) without defensive try/catch between them. Wrap your own downstream side effects (HTTP calls, broadcasts, DB writes) in try/catch or push them onto a queue, because isolation protects the fan-out, not your I/O.

Close vs. unsubscribe semantics. On an explicit pusher:unsubscribe, EventHandler::unsubscribe() removes the connection from the channel and then fires onUnsubscribe. On a full disconnect, the closing connection is reported once via onClose, not as N unsubscribes. So your channel bookkeeping must handle both paths: decrement on onUnsubscribe, and on onClose either bulk-clear all of that connection's channels or rely on connectionsOn() being empty for the socket.

Testing. Assert hook ordering and post-removal state. Inside onUnsubscribe, connectionsOn($channel) should already exclude the connection — the removal happens before the hook fires. Use that as a regression test for the ordering contract.

Gotchas and Tips

  • Breaking change: ConnectionLifecycle now has four methods. Any class implementing the interface directly will fatal on load until it adds onUnsubscribe. The plugin system is one release old, so the package chose to complete the contract now rather than carry a shim. Upgrade path: add the method to each plugin, or introduce your own AbstractPlugin / NoopUnsubscribe trait and extend it. If you have many plugins, do this before bumping the version — it's a load-time fatal, not a runtime warning, so it fails loudly but all at once.
  • PluginContext::unsubscribe() does NOT emit onUnsubscribe. It's documented as a direct action. If your own code relies on the hook firing for symmetric cleanup, you must do that bookkeeping at the call site, or call PluginManager::notifyUnsubscribe() yourself — and be aware that a manual notify plus the real removal path could double-count if you're not careful.
  • One onClose, not one onUnsubscribe per channel. If you decrement per-channel counters in both hooks, disconnects will corrupt your counts. Pick one owner per channel: unsubscribe handles explicit leaves, close handles teardown.
  • Ordering is post-removal. Inside onUnsubscribe, the connection is already gone from the channel. Don't write logic that assumes it can still query itself in connectionsOn().
  • Exception isolation hides failures. notifyUnsubscribe() swallows per-plugin exceptions by design. Make sure your plugin logs its own failures, or configure the package logger, otherwise a broken hook is silent. This is the most likely "why didn't my plugin run" debugging dead-end.
  • Hook only fires on explicit events. onUnsubscribe is tied to the explicit pusher:unsubscribe event. If clients rely on socket drop or pusher:unsubscribe is skipped (e.g. client just closes), you fall back to onClose — verify your client's teardown behavior in dev, because channel counters will drift silently otherwise.
  • Version pinning. Pin to ^0.4.0 if you depend on the interface completing at four methods; ^0.3 will keep you on the old three-method contract but leaves you without a way to leave a single channel.
  • Extension point to watch. The write side of PluginContext currently exposes only unsubscribe(). If you need to force-subscribe or move a connection between channels server-side, that surface isn't there yet — track upstream rather than reaching into internals.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console