Laravel 4 Real Time Chat

A Comprehensive Tutorial

Christopher Pitt
Laravel 4 Tutorials
16 min readOct 31, 2013

--

Laravel 4 is a huge step forward for the PHP community. It’s beautifully written, full of features and the community is presently exploding. We’re going to use it to create a socket based, real time chat application.

I have spent two full hours getting the code out of a Markdown document and into Medium. Medium really isn’t designed for tutorials such as this, and while much effort has been spent in the pursuit of accuracy; there’s a good chance you could stumble across a curly quote in a code listing. Please make a note and I will fix where needed.

I have also uploaded this code to Github. You need simply follow the configuration instructions in this tutorial, after downloading the source code, and the application should run fine. This assumes, of course, that you know how to do that sort of thing. If not; this shouldn’t be the first place you learn about making PHP applications.

https://github.com/formativ/tutorial-laravel-4-real-time-chat

If you spot differences between this tutorial and that source code, please raise it here or as a GitHub issue. Your help is greatly appreciated.

Installing Laravel 4

Laravel 4 uses Composer to manage its dependencies. You can install Composer by following the instructions at http://getcomposer.org/
doc/00-intro.md#installation-nix
.

Once you have Composer working, make a new directory or navigation to an existing directory and install Laravel 4 with the following command:

composer create-project laravel/laravel ./ --prefer-dist

If you chose not to install Composer globally (though you really should), then the command you use should resemble the following:

php composer.phar create-project laravel/laravel ./ --prefer-dist

Both of these commands will start the process of installing Laravel 4. There are many dependencies to be sourced and downloaded; so this process may take some time to finish.

Installing Other Dependencies

This tutorial depends heavily on client-side resources. We’re developing a Laravel 4 application which has lots of server-side aspects; but it’s also a chat app. There be scripts!

For this; we’re using Bootstrap and EmberJS. Download Bootstrap at: http://getbootstrap.com/ and unpack it into your public folder. Where you put the individual files makes little difference, but I have put the scripts in public/js, the stylesheets in public/css and the fonts in public/fonts. Where you see those paths in my source-code; you should substitute them with your own.

Next up, download EmberJS at: http://emberjs.com/ and unpack it into your public folder. You’ll also need the Ember.Data script at: http://emberjs.com/guides/getting-started/obtaining-emberjs-and-dependencies/.

For the server-side portion of dependencies, we need to download a library called Ratchet. I’ll explain it shortly, but in the meantime we need to add it to our composer.json file:

"require" : {
"laravel/framework" : "4.0.*",
"cboden/Ratchet" : "0.3.*"
},

This was extracted from composer.json for brevity.

Follow that up with:

composer update

Ratchet isn’t built specifically for Laravel 4, so there are no service providers for us to add.

We’ll now have access to the Ratchet library for client-server communication, Bootstrap for styling the interface and EmberJS for connecting these two things together.

ReactPHP

Before we can understand Ratchet, we need to understand ReactPHP. ReactPHP was born out of the need to develop event-based, asynchronous PHP applications. If you’ve worked with Node.JS you’ll feel right at home developing applications with ReactPHP; as they share a similar approaches to code. We’re not going to develop our chat application in ReactPHP, but it’s a dependency for Ratchet…

You can learn more about ReactPHP at: http://reactphp.org/.

Ratchet

One of the many ways in which real-time client-server applications are made possible is by what’s called socket programming. Believe it or not; most of what you do on the internet depends on socket programming. From simple browsing to streaming — your computer opens a socket connection to a server and the server sends data back through it.

PHP supports this type of programming but PHP websites have not typically been developed with this kind of model in mind. PHP developers have preferred the typical request/response model, and it’s comparatively easier than low-level socket programming.

Enter ReactPHP. One of the requirements for building a fully-capable socket programming framework is creating what’s called an Event Loop. ReactPHP has this and Ratchet uses it, along with the Publish/Subscribe model to accept and maintain open socket connections.

ReactPHP wraps the low-level PHP functions into a nice socket programming API and Ratchet wraps that API into another API that’s even easier to use.

You can learn more about Ratchet at: http://socketo.me/.

Creating An Interface

Let’s get to the code! We’re going to need an interface (kind of like a wireframe) so we know what to build with our application. Let’s set up a simple view and plug it into EmberJS.

I should mention that I am by no means an EmberJS expert. I learned all I know of it, while writing this tutorial, by following various guides. The point of this is not to teach EmberJS so much as it is to show EmberJS integration with Laravel 4.

Let’s change the default routes.php file to load a custom view:

<?phpRoute::get("/", function()
{
return View::make("index/index");
});

This file should be saved as app/routes.php.

Then we need to add this view:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link
rel="stylesheet"
type="text/css"
href="{{ asset("css/bootstrap.3.0.0.css") }}"
/>
<link
rel="stylesheet"
type="text/css"
href="{{ asset("css/bootstrap.theme.3.0.0.css") }}"
/>
<title>Laravel 4 Chat</title>
</head>
<body>
<script type="text/x-handlebars">
@{{outlet}}
</script>
<script
type="text/x-handlebars"
data-template-name="index"
>
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>Laravel 4 Chat</h1>
<table class="table table-striped">
@{{#each}}
<tr>
<td>
@{{user}}
</td>
<td>
@{{text}}
</td>
</tr>
@{{/each}}
</table>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="input-group">
<input
type="text"
class="form-control"
/>
<span class="input-group-btn">
<button class="btn btn-default">
Send
</button>
</span>
</div>
</div>
</div>
</div>
</script>
<script
type="text/javascript"
src="{{ asset("js/jquery.1.9.1.js") }}"
></script>
<script
type="text/javascript"
src="{{ asset("js/handlebars.1.0.0.js") }}"
></script>
<script
type="text/javascript"
src="{{ asset("js/ember.1.1.1.js") }}"
></script>
<script
type="text/javascript"
src="{{ asset("js/ember.data.1.0.0.js") }}"
></script>
<script
type="text/javascript"
src="{{ asset("js/bootstrap.3.0.0.js") }}"
></script>
<script
type="text/javascript"
src="{{ asset("js/shared.js") }}"
></script>
</body>
</html>

This file should be saved as app/views/index/index.blade.php.

Both Blade and EmberJS use double-curly-brace syntax for variable and logic substitution. Luckily Blade includes a mechanism to ignore curly brace blocks, by prepending them with @ symbols. Thus our template includes @ symbols before all EmberJS blocks.

The scripts and stylesheets need to be relative to where you saved them or you’re going to see errors.

You’ll notice I’ve specified shared.css and shared.js files — the CSS file is blank, but the JavaScript file contains:

// 1
var App = Ember.Application.create();
// 2
App.Router.map(function() {
this.resource("index", {
"path" : "/"
});
});
// 3
App.Message = DS.Model.extend({
"user" : DS.attr("string"),
"text" : DS.attr("string")
});
// 4
App.ApplicationAdapter = DS.FixtureAdapter.extend();
// 5
App.Message.FIXTURES = [
{
"id" : 1,
"user" : "Chris",
"text" : "Hello World."
},
{
"id" : 2,
"user" : "Wayne",
"text" : "Don't dig it, man."
},
{
"id" : 3,
"user" : "Chris",
"text" : "Meh."
}
];

This file should be saved as public/js/shared.js.

If you’re an EmberJS noob, like me, then it will help to understand what each piece of this script is doing.

  1. We create a new Ember application with Ember.Application.create().
  2. Routes are defined in the App.Route.map() method, and we tell the application to equate the path / to the index resource.
  3. We define a Message model. These are similar to the Eloquent models we have in Laravel 4, but they’re built to work with EmberJS (and are obviously on the client-side of the application).
  4. We specify a fixture-based data store for our application. We’re using this, temporarily, to fill our interface with some dummy data, but we’ll add a dynamic data store before too long…
  5. Here we add the fixture data. Notice that, in addition to the two model fields we defined, we also specify ID values for the fixture rows. This data is used to single out individual Message objects.

When you browse to the base URL of the application; you should now see an acceptably styled list of message objects, along with a heading and input form. Let’s make it dynamic!

You should also see some console log messages (depending on your browser) which show that EmberJS is running.

Creating A Service Provider

Following on from a previous tutorial; we’re going to be creating a service provider which will provide the various classes for our application. Create a new workbench:

php artisan workbench formativ/chat

This will produce (amongst other things) a service provider template. I’ve added a few IoC bindings to it:

<?phpnamespace Formativ\Chat;use Evenement\EventEmitter;
use Illuminate\Support\ServiceProvider;
use Ratchet\Server\IoServer;
class ChatServiceProvider
extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->bind("chat.emitter", function()
{
return new EventEmitter();
});
$this->app->bind("chat.chat", function()
{
return new Chat(
$this->app->make("chat.emitter")
);
});
$this->app->bind("chat.user", function()
{
return new User();
});
$this->app->bind("chat.command.serve", function()
{
return new Command\Serve(
$this->app->make("chat.chat")
);
});
$this->commands("chat.command.serve");
}
public function provides()
{
return [
"chat.chat",
"chat.command.serve",
"chat.emitter",
"chat.server"
];
}
}

This file should be saved as workbench/formativ/chat/src/Formativ/
Chat/ChatServiceProvider.php
.

The first binding is a simple alias to the Evenement\EventEmitter class (which Ratchet requires). We bind it here as we cannot guarantee that Ratchet will continue to use Evenement\EventEmitter and we’ll need a reliable way to unit test possible alternatives in the future.

Let’s look closer at the second and third bindings. The first is to the Formativ\Chat\Chat class. It implements the ChatInterface interface:

<?phpnamespace Formativ\Chat;use Evenement\EventEmitterInterface;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
interface ChatInterface
extends MessageComponentInterface
{
public function getUserBySocket(ConnectionInterface $socket);
public function getEmitter();
public function setEmitter(EventEmitterInterface $emitter);
public function getUsers();
}

This file should be saved as workbench/formativ/chat/src/Formativ/
Chat/ChatInterface.php.

It’s interesting to note that PHP actually supports interfaces which extend other interfaces. This is useful if you want to expect a certain level of functionality, provided by a third-party library (such as SPL), but want to add your own requirements on top.

The concrete implementation looks like this:

<?phpnamespace Formativ\Chat;use Evenement\EventEmitterInterface;
use Exception;
use Ratchet\ConnectionInterface;
use SplObjectStorage;
class Chat
implements ChatInterface
{
protected $users;
protected $emitter;
protected $id = 1;
public function getUserBySocket(ConnectionInterface $socket)
{
foreach ($this->users as $next)
{
if ($next->getSocket() === $socket)
{
return $next;
}
}
return null;
}
public function getEmitter()
{
return $this->emitter;
}
public function setEmitter(EventEmitterInterface $emitter)
{
$this->emitter = $emitter;
}
public function getUsers()
{
return $this->users;
}
public function __construct(EventEmitterInterface $emitter)
{
$this->emitter = $emitter;
$this->users = new SplObjectStorage();
}
public function onOpen(ConnectionInterface $socket)
{
$user = new User();
$user->setId($this->id++);
$user->setSocket($socket);
$this->users->attach($user);
$this->emitter->emit("open", [$user]);
}
public function onMessage(
ConnectionInterface $socket,
$message
)
{
$user = $this->getUserBySocket($socket);
$message = json_decode($message);
switch ($message->type)
{
case "name":
{
$user->setName($message->data);
$this->emitter->emit("name", [
$user,
$message->data
]);
break;
}
case "message":
{
$this->emitter->emit("message", [
$user,
$message->data
]);
break;
}
}
foreach ($this->users as $next)
{
if ($next !== $user)
{
$next->getSocket()->send(json_encode([
"user" => [
"id" => $user->getId(),
"name" => $user->getName()
],
"message" => $message
]));
}
}
}
public function onClose(ConnectionInterface $socket)
{
$user = $this->getUserBySocket($socket);
if ($user)
{
$this->users->detach($user);
$this->emitter->emit("close", [$user]);
}
}
public function onError(
ConnectionInterface $socket,
Exception $exception
)
{
$user = $this->getUserBySocket($socket);
if ($user)
{
$user->getSocket()->close();
$this->emitter->emit("error", [$user, $exception]);
}
}
}

This file should be saved as workbench/formativ/chat/src/Formativ/
Chat/Chat.php
.

It’s fairly simple: the (delegate) onOpen and onClose methods handle creating new User objects and disposing of them. The onMessage method translates JSON-encoded message objects into required actions and responds back to the other socket connections with further details.

Additionally; the UserInterface interface and User class look like this:

<?phpnamespace Formativ\Chat;use Evenement\EventEmitterInterface;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
interface UserInterface
{
public function getSocket();
public function setSocket(ConnectionInterface $socket);
public function getId();
public function setId($id);
public function getName();
public function setName($name);
}

This file should be saved as workbench/formativ/chat/src/Formativ/
Chat/UserInterface.php
.

<?phpnamespace Formativ\Chat;use Ratchet\ConnectionInterface;class User
implements UserInterface
{
protected $socket;
protected $id;
protected $name;
public function getSocket()
{
return $this->socket;
}
public function setSocket(ConnectionInterface $socket)
{
$this->socket = $socket;
return $this;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
}

This file should be saved as workbench/formativ/chat/src/Formativ/
Chat/User.php
.

The User class is a simple wrapper for a socket resource and name string. The way we’ve chosen to implement the Ratchet server requires that we have a class which implements the MessageComponentInterface interface; and this interface specifies that ConnectionInterface objects are passed back and forth. There’s no way to identify these, by name (and id), so we’re adding that functionality with the extra layer.

Creating A Serve Command

All these classes lead us to the artisan command which will kick things off:

<?phpnamespace Formativ\Chat\Command;use Illuminate\Console\Command;
use Formativ\Chat\ChatInterface;
use Formativ\Chat\UserInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
class Serve
extends Command
{
protected $name = "chat:serve";
protected $description = "Command description.";
protected $chat;
protected function getUserName($user)
{
$suffix = " (" . $user->getId() . ")";
if ($name = $user->getName())
{
return $name . $suffix;
}
return "User" . $suffix;
}
public function __construct(ChatInterface $chat)
{
parent::__construct();
$this->chat = $chat; $open = function(UserInterface $user)
{
$name = $this->getUserName($user);
$this->line("
<info>" . $name . " connected.</info>
");
};
$this->chat->getEmitter()->on("open", $open); $close = function(UserInterface $user)
{
$name = $this->getUserName($user);
$this->line("
<info>" . $name . " disconnected.</info>
");
};
$this->chat->getEmitter()->on("close", $close); $message = function(UserInterface $user, $message)
{
$name = $this->getUserName($user);
$this->line("
<info>New message from " . $name . ":</info>
<comment>" . $message . "</comment>
<info>.</info>
");
};
$this->chat->getEmitter()->on("message", $message); $name = function(UserInterface $user, $message)
{
$this->line("
<info>User changed their name to:</info>
<comment>" . $message . "</comment>
<info>.</info>
");
};
$this->chat->getEmitter()->on("name", $name); $error = function(UserInterface $user, $exception)
{
$message = $exception->getMessage();
$this->line("
<info>User encountered an exception:</info>
<comment>" . $message . "</comment>
<info>.</info>
");
};
$this->chat->getEmitter()->on("error", $error);
}
public function fire()
{
$port = (integer) $this->option("port");
if (!$port)
{
$port = 7778;
}
$server = IoServer::factory(
new HttpServer(
new WsServer(
$this->chat
)
),
$port
);
$this->line("
<info>Listening on port</info>
<comment>" . $port . "</comment>
<info>.</info>
");
$server->run();
}
protected function getOptions()
{
return [
[
"port",
null,
InputOption::VALUE_REQUIRED,
"Port to listen on.",
null
]
];
}
}

This file should be saved as workbench/formativ/chat/src/Formativ/
Chat/Command/Serve.php
.

The reason for us adding the event emitter to the equation should now be obvious — we need a way to tie into the delegated events, of the Chat class, without leaking the abstraction we gain from it. In other words; we don’t want the Chat class to know of the existence of the artisan command. Similarly; we don’t want the artisan command to know of the onOpen, onMessage, onError and onMessage methods so instead we use a publish/subscribe model for notifying the command of changes. The result is a clean abstraction.

The fire() method gets (or defaults) the port and starts the Ratchet web socket server.

The method we’re using, to start the server, is not the only way it can be started. You can learn more about the web socket server at: http://socketo.me/docs/websocket.

To connect to the web socket server; we need to add a bit of vanilla JavaScript:

try {
if (!WebSocket) {
console.log("no websocket support");
} else {
var socket = new WebSocket("ws://127.0.0.1:7778/");
socket.addEventListener("open", function (e) {
console.log("open: ", e);
});
socket.addEventListener("error", function (e) {
console.log("error: ", e);
});
socket.addEventListener("message", function (e) {
console.log("message: ", JSON.parse(e.data));
});
console.log("socket:", socket); window.socket = socket;
}
} catch (e) {
console.log("exception: " + e);
}

This was extracted from public/js/shared.js for brevity.

We’ve wrapped this code in a try-catch block as not all browsers support Web Sockets yet. There are a number of libraries which will shim this functionality, but their use is outside the scope of this tutorial.

You can find a couple of these libraries at: https://github.com/gimite/web-socket-js and https://github.com/sockjs.

This code will attempt to open a socket connection to 127.0.0.1:7778 (the address and port used in the serve command) and write some console messages depending on the events that are emitted. You’ll notice we’re also assigning the socket instance to the window object; so we can send some debugging commands through it.

This allows us to see both the server-side of things, as well as the client-side…

Sender
Receiver

Wiring Up The Interface

Getting our interface talking to our socket server is relatively straightforward. We begin by disabling our fixture data and modifying our model slightly:

App.Message = DS.Model.extend({
"user_id" : DS.attr("integer"),
"user_name" : DS.attr("string"),
"user_id_class" : DS.attr("string"),
"message" : DS.attr("string")
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();App.Message.FIXTURES = [
// {
// "id" : 1,
// "user" : "Chris",
// "text" : "Hello World."
// },
// {
// "id" : 2,
// "user" : "Wayne",
// "text" : "Don’t dig it, man."
// },
// {
// "id" : 3,
// "user" : "Chris",
// "text" : "Meh."
// }
];

This was extracted from public/js/shared.js for brevity.

If you want to pre-populate your chat application with a history; you could feed this fixture configuration with data from your server.

Now the index template will show only the heading and form elements, but no chat messages. In order to populate these; we need to store a reference to the application data store:

var store;App.IndexRoute = Ember.Route.extend({
"init" : function() {
store = this.store;
},
"model" : function () {
return store.find("message");
}
});

This was extracted from public/js/shared.js for brevity.

We store this reference because we will need to push rows into the store once we receive them from the open socket. This leads us to the changes to web sockets:

try {
var id = 1;
if (!WebSocket) {
console.log("no websocket support");
} else {
var socket = new WebSocket("ws://127.0.0.1:7778/");
var id = 1;
socket.addEventListener("open", function (e) {
// console.log("open: ", e);
});
socket.addEventListener("error", function (e) {
console.log("error: ", e);
});
socket.addEventListener("message", function (e) { var data = JSON.parse(e.data);
var user_id = data.user.id;
var user_name = data.user.name;
var message = data.message.data;
switch (data.message.type) { case "name":
$(".name-" + user_id).html(user_name);
break;
case "message":
store.push("message", {
"id" : id++,
"user_id" : user_id,
"user_name" : user_name || "User",
"user_id_class" : "name-" + user_id,
"message" : message
});
break;
} }); // console.log(“socket:”, socket); window.socket = socket; // debug
}
} catch (e) {
console.log("exception: " + e);
}

This was extracted from public/js/shared.js for brevity.

We start by defining an id variable, to store the id’s of message objects as they are passed through the socket. Inside the onMessage event handler; we parse the JSON data string and determine the type of message being received. If it’s a name message (a user changing their name) then we update all the table cells matching the user’s server id. If it’s a normal message object; we push it into the data store.

This gets us part of the way, since console message commands will visually affect the UI. We still need to wire up the input form…

App.IndexController = Ember.ArrayController.extend({
"command" : null,
"actions" : {
"send" : function(key) {
if (key && key != 13) {
return;
}
var command = this.get("command") || ""; if (command.indexOf("/name") === 0) {
socket.send(JSON.stringify({
"type" : "name",
"data" : command.split("/name")[1]
}));
} else {
socket.send(JSON.stringify({
"type" : "message",
"data" : command
}));
}
this.set("command", null);
}
}
});
App.IndexView = Ember.View.extend({
"keyDown" : function(e) {
this.get("controller").send("send", e.keyCode);
}
});

This was extracted from public/js/shared.js for brevity.

We create IndexController and IndexView objects. The IndexView object intercepts the keyDown event and passes it to the IndexController object. The first bit of logic tells the send() method to ignore all keystrokes that aren’t the enter key. This means enter will trigger the send() method.

It continues by checking for the presence of a /name command switch. If that’s present in the input value (this.get(“command”)) then it sends a message to the server to change the user’s name. Otherwise it sends a normal message to the server. In order for the UI to update for the person sending the message; we need to also slightly modify the Chat class:

public function onMessage(ConnectionInterface $socket, $message)
{
$user = $this->getUserBySocket($socket);
$message = json_decode($message);
switch ($message->type)
{
case "name":
{
$user->setName($message->data);
$this->emitter->emit("name", [
$user,
$message->data
]);
break;
}
case "message":
{
$this->emitter->emit("message", [
$user,
$message->data
]);
break;
}
}
foreach ($this->users as $next)
{
// if ($next !== $user)
// {
$next->getSocket()->send(json_encode([
"user" => [
"id" => $user->getId(),
"name" => $user->getName()
],
"message" => $message
]));
// }
}
}

This was extracted from workbench/formativ/chat/src/Formativ/
Chat/Chat.php
for brevity.

The change we’ve made is to exclude the logic which prevented messages from being sent to the user from which they came. All messages will essentially be sent to everyone on the server now.

The final change is to the index template, as we changed the model structure and need to adjust for this in the template:

<script
type="text/x-handlebars"
data-template-name="index"
>
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>Laravel 4 Chat</h1>
<table class="table table-striped">
@{{#each message in model}}
<tr>
<td @{{bind-attr class="message.user_id_class"}}>
@{{message.user_name}}
</td>
<td>
@{{message.message}}
</td>
</tr>
@{{/each}}
</table>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="input-group">
@{{input
type="text"
value=command
class="form-control"
}}
<span class="input-group-btn">
<button
class="btn btn-default"
@{{action "send"}}
>
Send
</button>
</span>
</div>
</div>
</div>
</div>
</script>

This was extracted from app/views/index/index.blade.php for brevity.

You’ll notice, apart from us using different field names; that we’ve change how the loop is done, the class added to each “label” cell and how the input field is generated.

The reason for the change to the loop structure is simply so that you can see another way to represented enumerable data in handlebars. Where previously we used a simple {{#each}} tag, we’re now being more explicit about the data we want to iterate.

We add a special class on each “label” cell as we need to target these cells and change their contents, in the event that a user decides to change their name.

Finally, we change how the input field is generated because we need to bind its value to the IndexController’s command property. This format allows that succinctly.

You can learn more about Ember JS at: http://emberjs.com/.

Real Time Chat

Conclusion

Laravel 4 is packed with excellent tools to build all sorts of real-time applications. In addition, it also has an excellent ORM, template language, input validator and filter system. And that’s just the tip of the iceberg!

If you found this tutorial helpful, please tell me about it @followchrisp and be sure to recommend it to PHP developers looking to Laravel 4!

This tutorial comes from a book I’m writing. If you like it and want to support future tutorials; please consider buying it. Half of all sales go to Laravel.

--

--