varspool/websocket-bundle
Symfony bundle integrating Ratchet WebSockets into Symfony apps. Provides a configurable WebSocket server, WAMP pub/sub routing, session and origin handling, and service-container-backed handlers for building real-time PHP applications.
Before you composer require anything, understand what you're holding: this is a Symfony 2 bundle, not a Laravel package. There is no service provider, no facade, no config file in config/. What you actually get is three reusable ideas wrapped in Symfony glue:
websocket:server — confirm with app/console list).prefix + topic), so one socket carries many logical channels.websocket.publisher) that lets ordinary request/response code push a message into a topic the server is holding open.So your mental model as a Laravel dev is: artisan command + broadcast driver. That's the whole value proposition.
Read in this order (clone the repo, don't install it yet):
README.md — the config sample and the demo client are the real spec.DependencyInjection/Configuration.php — the authoritative config tree; README examples drift from version to version.Command/ — find the server command, note its options (--port, verbosity).Server/ — the Wrench Application implementation, TopicManager, and the pub/sub adapters. This is 80% of the code you'd ever port.Resources/public/js/ — the bundled JS client shows exactly which WAMP frames the server expects.Minimal smoke test (do this in a scratch Symfony 2.8 app on a PHP 5.6/7.0 container, not your Laravel app — dependency resolution against modern Symfony will refuse):
# app/config/config.yml — illustrative; verify keys against Configuration.php
websocket:
server:
host: 0.0.0.0
port: 8080
app/console websocket:server -v
// the second argument matters: you must negotiate the 'wamp' subprotocol
const ws = new WebSocket('ws://localhost:8080/', 'wamp');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'prefix', prefix: 'chat' }));
ws.send(JSON.stringify({ type: 'subscribe', topic: 'room/1' }));
};
ws.onmessage = (e) => console.log(JSON.parse(e.data));
Your first real use case: a per-user notification feed. Browser subscribes to user/42/notifications; a Laravel listener publishes to the same topic when something happens. If that works end to end, everything else is polish.
1. Sidecar (recommended if you must use the code as-is). Run the bundle in a tiny Symfony 2 app (or a standalone PHP script that boots only Wrench + TopicManager) under Supervisor. Laravel never loads the bundle; it only publishes. This keeps the abandoned Symfony 2 dependency tree out of your composer.json entirely.
2. Extraction (recommended if you want to keep it). Copy Server/, TopicManager, and the pub/sub adapters into app/Websockets/, delete every use Symfony\... and ContainerAware reference, and wrap it in an artisan command:
// app/Console/Commands/WebsocketServer.php
class WebsocketServer extends Command
{
protected $signature = 'ws:serve {--port=8080} {--host=0.0.0.0}';
public function handle()
{
$server = new Server('0.0.0.0', $this->option('port'));
$server->registerApplication('topic', new TopicApplication(new TopicManager($this->laravel['log'])));
$server->run(); // blocking; Supervisor restarts it
}
}
3. Publish from Laravel. The bundle's publisher service normally talks to the running server process out-of-band (ZMQ or a Redis-style adapter, depending on version/config). In Laravel, build a one-method client that speaks the same wire format and bind it in a service provider so events can fire-and-forget:
// app/Events/OrderShipped.php -> listener
app(WebsocketPublisher::class)->publish("user/{$order->user_id}/notifications", [
'type' => 'order.shipped',
'order' => $order->id,
]);
Dispatch from a queued listener, not from the request cycle — a dead websocket server must never fail a checkout.
4. Topic naming discipline. Use hierarchical, query-friendly names (user/{id}/notifications, room/{id}/messages) and set a prefix per connection. Treat the topic string as a public API: renaming it silently breaks every open browser tab.
5. Auth across the framework boundary. The server process has no session, so Auth::user() does not exist there. Issue a short-lived signed token in Laravel, pass it in the connection URL or the subprotocol header, and verify it inside onOpen before letting the client subscribe. Reject (close with a policy code) rather than silently ignoring.
6. Reconnect on the client. Always implement exponential-backoff reconnect and re-subscribe on onopen; PHP servers do die and deploys will restart them.
7. Behind nginx, terminate TLS and add the upgrade dance:
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
each(), untyped ArrayAccess, and deprecated curly-brace string offsets if you force it in. Extraction or sidecar is the only sane path.'wamp' as the second WebSocket argument and you get the echo/plain application — the socket opens fine, subscriptions just never work. If "nothing happens," check this first.php-fpm has an empty registry. You must use the out-of-band publisher adapter; the in-memory/null one is a no-op from another process. This trips up nearly everyone once.onPublish/onData handler stalls every connected client. Keep handlers trivial: validate, enqueue, return. Push real work onto a Laravel queue worker.unset connection state on close, cap any per-topic buffers, and let Supervisor recycle the process daily.:8000 dev, wss:// prod) gets a handshake 400 or an immediate close. Either configure allowed origins explicitly or disable the check and enforce auth yourself — never rely on origin alone for security.ws://. Terminate at the proxy and make sure your origin check (if enabled) accounts for the X-Forwarded-Proto being https while the server sees http.cache:clear alone leaves the old listener running. Bake "restart websocket worker" into your deploy script.-v/-vvv, watch for the Sec-WebSocket-Key handshake exchange, and use lsof -i :8080 when the port is inexplicably busy from a crashed prior run. websocat ws://localhost:8080/ -s wamp is far better than console.log for protocol issues.proxy_read_timeout high on both nginx and any cloud LB.Application interface for custom behaviour (override onOpen/onClose/onData), and swap the TopicManager/pub-sub adapter for Redis-backed fan-out if you ever need more than one server process.How can I help you explore Laravel packages today?