Skip to Content
Course content

214: WebSockets with Ratchet

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, PHP is request-response. How does Ratchet actually keep a connection open?

This is usually the first "aha!" moment when you dive into WebSockets. You're used to the standard PHP lifecycle: a request comes in, PHP scripts run, they spit out HTML, and the process dies. If you tried to do that with WebSockets, you'd be reconnecting every single second, which completely defeats the purpose.

Ratchet works differently because it doesn't run inside Apache or Nginx. It runs as a standalone CLI process. It uses an event loop (via ReactPHP) that basically says, "I'm going to sit here and listen on this specific port. When something happens—like a new connection or a message arriving—I'll trigger a specific function." It's a long-running process, meaning your script stays in memory. I'll warn you now: this means you have to be way more careful about memory leaks than you do in a standard Laravel or Symfony controller.

How do I actually track who is connected and broadcast messages to them?

You can't just use a global array to keep track of users because you need a way to uniquely identify the connection object itself. Ratchet provides a MessageComponentInterface that you implement. Inside this, I always recommend using SplObjectStorage. It's essentially a map that lets you store the connection objects as keys, which is perfect for this.

Let's imagine we're building a Live Order Alert system for a restaurant kitchen. When a new order comes in, we want to blast it to all the kitchen tablets.


use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class OrderNotifier implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        // SplObjectStorage is a lifesaver for tracking connections
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "New tablet connected! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        // We receive an order from the front-of-house system
        // and broadcast it to every connected kitchen tablet
        foreach ($this->clients as $client) {
            $client->send("NEW ORDER: " . $msg);
        }
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

Do I still need my web server, or does Ratchet replace it?

You still need your web server. This is a common point of confusion. Ratchet is a WebSocket server, not a Web server. It handles the ws:// or wss:// protocol, not http://.

In a real-world setup, your user's browser will first load your page via Nginx (port 80/443). Then, the JavaScript on that page will initiate a connection to your Ratchet server running on a different port—usually something like 8080. If you're deploying this to production, you'll likely use Nginx as a reverse proxy to route traffic from port 80 to port 8080 so you don't have to open random ports on your firewall.

On the client side, it's surprisingly simple. You don't need a heavy library; the native browser API handles it:


const conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
    console.log("Connected to the kitchen feed!");
};
conn.onmessage = function(e) {
    alert("Alert: " + e.data);
};



📋 Practical Task

Build a Real-Time Multi-User Stock Ticker

Your task is to expand the OrderNotifier logic to create a stock price broadcaster. Instead of broadcasting every message to everyone, implement a system where the server only sends messages to clients who have "subscribed" to a specific stock symbol.

  • Modify the onMessage method so that it expects a JSON payload like {"action": "subscribe", "symbol": "AAPL"} or {"action": "update", "symbol": "AAPL", "price": "150.00"}.
  • Use a second SplObjectStorage or a nested array to keep track of which ConnectionInterface objects are subscribed to which symbols.
  • Ensure that when an "update" message is received, only the connections subscribed to that specific symbol receive the price update.
  • Test it by opening three different browser tabs: subscribe Tab A to "AAPL", Tab B to "TSLA", and Tab C to "AAPL". Send an update for "AAPL" and verify that Tab B receives nothing while A and C do.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.