thruway/client
Thruway Client is a PHP WAMP v2 client built on ReactPHP, enabling WebSocket pub/sub and RPC over persistent connections. It supports asynchronous messaging, topic subscriptions, and remote procedure calls for real-time PHP applications.
composer require thruway/client
There is no Laravel auto-discovery, facade, config publish, or service provider in this package. Treat it as a low-level PHP WAMP client and build the Laravel glue yourself.
Look first at:
examples/ directory.vendor/thruway/client/src/Connection.php and ClientSession.php — these are the entry points.Minimal first use case: an Artisan command that connects to a WAMP router, subscribes to a topic, and logs payloads.
// app/Console/Commands/WampListen.php
use Thruway\ClientSession;
use Thruway\Connection;
public function handle()
{
$connection = new Connection([
'realm' => config('thruway.realm'),
'url' => config('thruway.url'),
]);
$connection->on('open', function (ClientSession $session) {
$session->subscribe('com.example.events', function ($args) {
logger()->info('WAMP event', $args);
});
});
$connection->on('error', fn ($e) => logger()->error($e->getMessage()));
$connection->open(); // starts the ReactPHP event loop
}
Add a minimal config/thruway.php:
return [
'url' => env('WAMP_URL', 'ws://127.0.0.1:8080/ws'),
'realm' => env('WAMP_REALM', 'realm1'),
];
Run it as a long-lived daemon:
php artisan wamp:listen
Wrap the client in a Laravel service. Bind a factory or singleton in a service provider, but be deliberate about lifecycle. A singleton connection is useful in long-running workers and dangerous in PHP-FPM.
$this->app->singleton('wamp.connection', function () {
return new Connection([
'realm' => config('thruway.realm'),
'url' => config('thruway.url'),
'authmethods' => ['wampcra'],
'authid' => config('thruway.authid'),
'authrole' => config('thruway.authrole'),
'onChallenge' => function ($session, $challenge, $canceller) {
return $canceller->answer(config('thruway.secret'));
},
]);
});
Prefer a small WampClient wrapper class over injecting Thruway\Connection directly. That gives you one place for reconnection, logging, serialization, and test mocking.
Publish from Artisan commands or queued jobs. Opening a WAMP connection per HTTP request is usually the wrong tradeoff: connection setup, auth, and event-loop execution add latency and failure modes. Use a daemon or a short-lived command when possible.
$connection->on('open', function (ClientSession $session) use ($payload) {
$session->publish('com.example.events', [$payload], null, ['acknowledge' => true])
->then(function () use ($session) {
$session->getConnection()->close();
});
});
$connection->open();
Subscribe in a long-running consumer. The natural Laravel pattern is an Artisan command supervised by Supervisor/systemd. Dispatch Laravel events or jobs from WAMP callbacks so business logic stays out of the event loop.
$session->subscribe('com.example.orders', function ($args) {
Event::dispatch(new WampOrderReceived($args));
// or: ProcessWampOrder::dispatch($args)->onQueue('wamp');
});
Use RPC for request/response. For remote procedure calls, wrap the promise and integrate it with Laravel’s timeout/retry expectations. Avoid unbounded waits inside HTTP requests.
$session->call('com.example.calculate', [$input])->then(
fn ($result) => logger()->info('RPC result', $result),
fn ($error) => logger()->error('RPC failed', [$error])
);
Register procedures when Laravel is the callee. Keep handlers fast: validate, dispatch a job, return an acknowledgement. Long-running work in the event-loop callback blocks all other WAMP traffic on that connection.
Reconnect explicitly. Treat close and error as normal operational events. Reopen with backoff, and rebuild subscriptions after reconnect. Do not assume the package reconnects for you.
Separate concerns by transport. Use WAMP for real-time pub/sub and RPC, Laravel queues for durable background work, and Laravel events for in-process decoupling. WAMP is not a replacement for your queue.
open() starts ReactPHP’s loop and blocks. Calls are asynchronous; code after open() may not run as expected. Understand when the loop exits and close connections explicitly.error, close, and challenge failures. Log connection state transitions; otherwise failures look like silent hangs.authid, authrole, and challenge handling against your router, not just the client README.How can I help you explore Laravel packages today?