If you ever wondered if there is a way to write a real-time web application using PHP where any event or message is delivered/pushed to all recipients as they occur, in real-time? Then you are at the right place. We’ll be building a sample real-time chat application using PHP and Ratchet and all messages will be pushed to all recipients in real-time. There are other technologies like node.js and socket.io but obviously, there is a learning curve. If you don’t have time to learn a new thing or just want to stick with PHP then keep reading.
Goal
The goal of this application is to write a simple Chat application. Chats in event-driven programming are the “Hello World!” of applications. The chat will accept all incoming messages and deliver that message to all other connections.
The Chat class
Note: This document assumes you are familiar with PSR-0 and Composer. See Installation if you’re not yet familiar with this.
We’re going to hold everything in the MyApp namespace. Your composer file should look something like this:
{ "autoload": { "psr-0": { "MyApp": "src" } }, "require": { "cboden/ratchet": "0.3.*" } }
We’ll start off by creating a class. This class will be our chat “application”. This basic application will listen for 4 events:
- onOpen – Called when a new client has Connected
- onMessage – Called when a message is received by a Connection
- onClose – Called when a Connection is closed
- onError – Called when an error occurs on a Connection
Given those triggers, our class will implement the MessageComponentInterface:
<?php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { public function onOpen(ConnectionInterface $conn) { } public function onMessage(ConnectionInterface $from, $msg) { } public function onClose(ConnectionInterface $conn) { } public function onError(ConnectionInterface $conn, \Exception $e) { } }
You’ll notice, that in addition to just implementing methods from the MessageComponentInterface, we’ve given our application a namespace and are accepting the ConnectionInterfaceclasses. This class, usually implemented by a Connection instance, is a representation of a client’s connection on the other side of the socket. On each of the four triggered events, the client Connection representation is passed. These objects are re-used, and you will receive the same Connection sometimes.
Save this as /src/MyApp/Chat.php. We’ll come back to our Chat class soon.
Instantiation
Our Chat class will be our application logic. Next, we’re going to piece together our shell script. This is the script/file we will call from the command line to launch our application.
<?php use Ratchet\Server\IoServer; use MyApp\Chat; require dirname(__DIR__) . '/vendor/autoload.php'; $server = IoServer::factory( new Chat(), 8080 ); $server->run();
Above, you’ll see we create an I/O (Input/Output) server class. It stores all the established connections, mediates data sent between each client and our Chat application and catches errors. The new instance of the Chat class then wraps the I/O Server class. Finally, we tell the server to enter an event loop, listening for any incoming requests on port 8080.
Save this script as /bin/chat-server.php. Now, we can run it with the following command in your terminal:
$ php bin/chat-server.php
The script should now execute, taking possession of your terminal. You can cancel the script, as we’re not quite finished yet.
Logic
So far, we’ve just set up the structure, both in our shell script and our Chat class. Now, to add code to our Chat to complete our application.
Let’s add some logic to our Chat class. What we’re going to do, is track all incoming Connections, in order to send them messages. Typically, you would store a collection of items in an array, but we’re going to use something called SplObjectStorage. These storage containers are built to store objects, which is what the incoming Connections are.
<?php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { protected $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { // Store the new connection to send messages to later $this->clients->attach($conn); echo "New connection! ({$conn->resourceId})\n"; } public function onMessage(ConnectionInterface $from, $msg) { $numRecv = count($this->clients) - 1; echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n" , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's'); foreach ($this->clients as $client) { if ($from !== $client) { // The sender is not the receiver, send to each client connected $client->send($msg); } } } public function onClose(ConnectionInterface $conn) { // The connection is closed, remove it, as we can no longer send it messages $this->clients->detach($conn); echo "Connection {$conn->resourceId} has disconnected\n"; } public function onError(ConnectionInterface $conn, \Exception $e) { echo "An error has occurred: {$e->getMessage()}\n"; $conn->close(); } }
Running It
Complete, let’s run it and test it. Open up three terminal windows, typing:
$ php bin/chat-server.php
$ telnet localhost 8080
$ telnet localhost 8080
In each of the telnet windows, type a message (“Hello World!”) and see it appear in the other!
Next Steps
Now that we have a basic working Chat application, let’s make that work in a web browser (Chrome, Firefox, or Safari [for now]). First, let’s go back to our chat-server.php script. We’re going to utilize another component of Ratchet; the WsServer class:
<?php use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use MyApp\Chat; require dirname(__DIR__) . '/vendor/autoload.php'; $server = IoServer::factory( new HttpServer( new WsServer( new Chat() ) ), 8080 ); $server->run();
Run the shell script again, open a couple of web browser windows, and open a javascript console or a page with the following javascript:
var conn = new WebSocket('ws://localhost:8080'); conn.onopen = function(e) { console.log("Connection established!"); }; conn.onmessage = function(e) { console.log(e.data); };
Once you see the console message “Connection established!” you can start sending messages to other connected browsers:
conn.send('Hello World!');
This post was extracted from Ratchet documentation.
[…] my previous article I talked about creating real-time PHP application. That was on the server side and I demonstrated a very very basic client to connect with it. […]