artisan-build / resonance
Laravel package for beautiful Reverb, Pusher, Soketi, and Ably integration in pure PHP.
Fund package maintenance!
artisan-build
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 1
pkg:composer/artisan-build/resonance
Requires
- php: ^8.4
- artisan-build/pusher-websocket-php: ^0.1.0
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
This package is auto-updated.
Last update: 2026-01-18 08:23:44 UTC
README
Resonance
Resonance is a Laravel WebSocket client for CLI commands, queue workers, and background processes. Inspired by Laravel Echo's API, it provides a familiar interface for real-time event listening—but from your PHP backend instead of the browser.
Built on artisan-build/pusher-websocket-php and ReactPHP's async event loop, Resonance connects to Reverb, Pusher, Soketi, and other Pusher-compatible servers.
Why Resonance?
Laravel Echo handles real-time features in the browser, but what about server-side PHP processes that need to listen for WebSocket events? Resonance fills that gap with:
- Echo-inspired API - Familiar
listen(),private(),join()methods - Manager pattern - Connect to multiple WebSocket servers simultaneously
- Extensible drivers - Add custom broadcasters with
extend() - Laravel-native - Config files, facades, and service providers out of the box
Installation
composer require artisan-build/resonance
You can publish the config file with:
php artisan vendor:publish --tag="resonance-config"
Configuration
Resonance follows Laravel's convention of defining connections in config and selecting via environment variable:
// config/resonance.php return [ 'default' => env('RESONANCE_CONNECTION', 'reverb'), 'connections' => [ 'reverb' => [ 'broadcaster' => 'reverb', 'key' => env('REVERB_APP_KEY'), 'authToken' => env('RESONANCE_AUTH_TOKEN'), 'host' => env('REVERB_HOST', '127.0.0.1'), 'port' => env('REVERB_PORT', 8080), 'forceTLS' => env('REVERB_SCHEME', 'https') === 'https', 'channelAuthorization' => [ 'endpoint' => env('APP_URL').'/broadcasting/auth', ], ], 'pusher' => [ 'broadcaster' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'authToken' => env('RESONANCE_AUTH_TOKEN'), 'cluster' => env('PUSHER_APP_CLUSTER', 'mt1'), 'forceTLS' => true, 'channelAuthorization' => [ 'endpoint' => env('APP_URL').'/broadcasting/auth', ], ], 'null' => [ 'broadcaster' => 'null', ], ], 'namespace' => 'App.Events', ];
Configuration Options
| Option | Description |
|---|---|
broadcaster |
The broadcaster type: reverb, pusher, or null |
key |
Your app key for the WebSocket server |
authToken |
Bearer token for channel authorization (e.g., Sanctum token) |
host |
WebSocket server hostname |
port |
WebSocket server port |
forceTLS |
Whether to use secure WebSocket (wss://) |
cluster |
Pusher cluster (for Pusher broadcaster) |
channelAuthorization.endpoint |
Full URL to your broadcasting auth endpoint |
Note: The
authTokenis your authentication token (like a Laravel Sanctum token) used to authorize private/presence channel subscriptions. This is not the Reverb app secret—channel authorization is handled server-side where the signing secret lives.
Use RESONANCE_CONNECTION=null in your .env.testing to disable WebSocket connections during tests.
Usage
Basic Channel Subscription
use ArtisanBuild\Resonance\Facades\Resonance; // Using the default connection from config $channel = Resonance::channel('orders'); $channel->listen('OrderShipped', function ($event) { echo "Order shipped: " . $event['order_id']; });
Multiple Connections
Resonance uses the Manager pattern, allowing you to work with multiple connections:
use ArtisanBuild\Resonance\Facades\Resonance; // Use a specific connection Resonance::connection('reverb')->listen('orders', 'OrderShipped', function ($event) { // Handle internal events from your Reverb server }); Resonance::connection('pusher')->listen('analytics', 'PageView', function ($event) { // Handle events from a third-party Pusher service });
Custom Drivers
Extend Resonance with custom broadcasters:
use ArtisanBuild\Resonance\Facades\Resonance; Resonance::extend('ably', function ($app, $config) { return new AblyConnector($config); });
Direct Instantiation
Or instantiate directly with custom options:
use ArtisanBuild\Resonance\Resonance; $resonance = new Resonance([ 'broadcaster' => 'reverb', 'key' => 'your-app-key', 'authToken' => 'your-sanctum-token', 'host' => '127.0.0.1', 'port' => 8080, 'forceTLS' => false, 'channelAuthorization' => [ 'endpoint' => 'https://your-app.com/broadcasting/auth', ], ]); $resonance->listen('orders', 'OrderShipped', function ($event) { echo "Order shipped: " . $event['order_id']; });
Private Channels
// Subscribe to a private channel $channel = $resonance->private('orders.123'); $channel->listen('OrderUpdated', function ($event) { // Handle the event });
Presence Channels
// Join a presence channel $presence = $resonance->join('chat.room.1'); $presence->here(function ($users) { // Users currently in the channel }); $presence->joining(function ($user) { // A user joined }); $presence->leaving(function ($user) { // A user left }); $presence->listen('NewMessage', function ($event) { // Handle chat message });
Connection Management
// Register a callback for when connection is established $resonance->connected(function () { echo "Connected to WebSocket server!"; }); // Get the socket ID (useful for excluding sender) $socketId = $resonance->socketId(); // Leave a specific channel $resonance->leave('orders'); // Leave a single channel variant $resonance->leaveChannel('private-orders.123'); // Disconnect entirely $resonance->disconnect();
Encrypted Private Channels
$channel = $resonance->encryptedPrivate('sensitive-data'); $channel->listen('SecretEvent', function ($event) { // Decrypted event data });
Real-World Example: CLI Chat with Community Prompts
Here's a real-time chat application built with AsyncPrompt from artisan-build/community-prompts, demonstrating how Resonance enables interactive CLI tools:
<?php namespace App\Console\Prompts; use ArtisanBuild\Resonance\Facades\Resonance; use ArtisanBuild\Resonance\Resonance as ResonanceInstance; use Illuminate\Support\Collection; use Laravel\Prompts\AsyncPrompt; use Laravel\Prompts\Key; class ChatPrompt extends AsyncPrompt { protected ResonanceInstance $socket; protected mixed $channel; public Collection $messages; public function __construct() { $this->messages = collect([['system', 'Connecting...']]); $this->on('key', fn ($key) => match ($key) { Key::ENTER => $this->sendMessage($this->value()), Key::ESCAPE => $this->disconnect(), default => null, }); $this->connect(); } protected function connect(): void { // Use the facade - config provides auth token and endpoint $this->socket = Resonance::connection('reverb'); $this->socket->connected(function () { $this->channel = $this->socket->private('chat'); // Listen for all events on the channel $this->channel->listenToAll(function ($event, $data) { $this->handleEvent($event, $data); }); // Or listen for specific events $this->channel->listen('MessageSent', function ($data) { $this->messages->push(['left', $data['message']]); $this->render(); }); // Listen for client whispers (peer-to-peer) $this->channel->listenForWhisper('typing', function ($data) { $this->typing = true; $this->render(); }); $this->messages = collect([['system', 'Connected! Start chatting...']]); $this->render(); }); } protected function sendMessage(string $message): void { if (empty($message)) return; // Send a whisper to other clients (no server round-trip) $this->channel->whisper('message', ['text' => $message]); $this->messages->push(['right', $message]); $this->typedValue = ''; $this->render(); } protected function disconnect(): void { $this->socket->disconnect(); $this->submit(); } }
This example showcases:
- Facade usage with config-driven auth token and endpoint
- Private channel subscription with automatic Bearer token authentication
listenToAll()for catching all channel eventslistenForWhisper()for peer-to-peer client eventswhisper()for sending client events without server round-trips
API Reference
Channel Methods
| Method | Description |
|---|---|
listen($event, $callback) |
Listen for a specific event |
listenToAll($callback) |
Listen for all events on the channel |
listenForWhisper($event, $callback) |
Listen for client whisper events |
stopListening($event) |
Stop listening to an event |
stopListeningForWhisper($event) |
Stop listening for a whisper |
notification($callback) |
Listen for Laravel notifications |
subscribed($callback) |
Callback when subscription succeeds |
error($callback) |
Callback when subscription fails |
Private Channel Methods
| Method | Description |
|---|---|
whisper($event, $data) |
Send a client event to other subscribers |
Presence Channel Methods
| Method | Description |
|---|---|
here($callback) |
Get current members when joining |
joining($callback) |
Called when a member joins |
leaving($callback) |
Called when a member leaves |
whisper($event, $data) |
Send a client event to other subscribers |
Supported Broadcasters
| Broadcaster | Status |
|---|---|
| Laravel Reverb | Supported |
| Pusher Channels | Supported |
| Soketi | Supported (via Pusher connector) |
| Ably | Planned |
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
