One of the most pervasive and least understood technologies that underpin the internet is socket programming. It’s been a dark art for decades, but the recent standardisation of web sockets (in relation to HTML 5) has made this type of programming a little easier to get into.
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:
1
"require"
:
{
2
"laravel/framework"
:
"4.0.*"
,
3
"cboden/Ratchet"
:
"0.3.*"
4
},
Follow that up with:
1
composer update
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.
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…
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.
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.
Let’s change the default routes.php file to load a custom view:
1
<?
php
2
3
Route
::
get
(
"/"
,
function
()
4
{
5
return
View
::
make
(
"index/index"
);
6
});
Then we need to add this view:
1
<!DOCTYPE html>
2
<html
lang=
"en"
>
3
<head>
4
<meta
charset=
"UTF-8"
/>
5
<link
6
rel=
"stylesheet"
7
type=
"text/css"
8
href=
"{{ asset("
css
/
bootstrap
.
3
.
0
.
0
.
css
")
}}"
9
/>
10
<link
11
rel=
"stylesheet"
12
type=
"text/css"
13
href=
"{{ asset("
css
/
bootstrap
.
theme
.
3
.
0
.
0
.
css
")
}}"
14
/>
15
<title>
Laravel 4 Chat</title>
16
</head>
17
<body>
18
<script
type=
"text/x-handlebars"
>
19
@
{{
outlet
}}
20
</script>
21
<script
22
type=
"text/x-handlebars"
23
data-template-name=
"index"
24
>
25
<
div
class
=
"container"
>
26
<
div
class
=
"row"
>
27
<
div
class
=
"col-md-12"
>
28
<
h1
>
Laravel
4
Chat
<
/h1>
29
<
table
class
=
"table table-striped"
>
30
@
{{
#
each
}}
31
<
tr
>
32
<
td
>
33
@
{{
user
}}
34
<
/td>
35
<
td
>
36
@
{{
text
}}
37
<
/td>
38
<
/tr>
39
@
{{
/each}}
40
<
/table>
41
<
/div>
42
<
/div>
43
<
div
class
=
"row"
>
44
<
div
class
=
"col-md-12"
>
45
<
div
class
=
"input-group"
>
46
<
input
47
type
=
"text"
48
class
=
"form-control"
49
/>
50
<
span
class
=
"input-group-btn"
>
51
<
button
class
=
"btn btn-default"
>
52
Send
53
<
/button>
54
<
/span>
55
<
/div>
56
<
/div>
57
<
/div>
58
<
/div>
59
</script>
60
<script
61
type=
"text/javascript"
62
src=
"{{ asset("
js
/
jquery
.
1
.
9
.
1
.
js
")
}}"
63
></script>
64
<script
65
type=
"text/javascript"
66
src=
"{{ asset("
js
/
handlebars
.
1
.
0
.
0
.
js
")
}}"
67
></script>
68
<script
69
type=
"text/javascript"
70
src=
"{{ asset("
js
/
ember
.
1
.
1
.
1
.
js
")
}}"
71
></script>
72
<script
73
type=
"text/javascript"
74
src=
"{{ asset("
js
/
ember
.
data
.
1
.
0
.
0
.
js
")
}}"
75
></script>
76
<script
77
type=
"text/javascript"
78
src=
"{{ asset("
js
/
bootstrap
.
3
.
0
.
0
.
js
")
}}"
79
></script>
80
<script
81
type=
"text/javascript"
82
src=
"{{ asset("
js
/
shared
.
js
")
}}"
83
></script>
84
</body>
85
</html>
You’ll notice I’ve specified shared.css and shared.js files — the CSS file is blank, but the JavaScript file contains:
1
// 1
2
var
App
=
Ember
.
Application
.
create
();
3
4
// 2
5
App
.
Router
.
map
(
function
()
{
6
this
.
resource
(
"index"
,
{
7
"path"
:
"/"
8
});
9
});
10
11
// 3
12
App
.
Message
=
DS
.
Model
.
extend
({
13
"user"
:
DS
.
attr
(
"string"
),
14
"text"
:
DS
.
attr
(
"string"
)
15
});
16
17
// 4
18
App
.
ApplicationAdapter
=
DS
.
FixtureAdapter
.
extend
();
19
20
// 5
21
App
.
Message
.
FIXTURES
=
[
22
{
23
"id"
:
1
,
24
"user"
:
"Chris"
,
25
"text"
:
"Hello World."
26
},
27
{
28
"id"
:
2
,
29
"user"
:
"Wayne"
,
30
"text"
:
"Don't dig it, man."
31
},
32
{
33
"id"
:
3
,
34
"user"
:
"Chris"
,
35
"text"
:
"Meh."
36
}
37
];
If you’re an EmberJS noob, like me, then it will help to understand what each piece of this script is doing.
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.
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:
1
php artisan workbench formativ/chat
This will produce (amongst other things) a service provider template. I’ve added a few IoC bindings to it:
1
<?
php
2
3
namespace
Formativ\Chat
;
4
5
use
Evenement\EventEmitter
;
6
use
Illuminate\Support\ServiceProvider
;
7
use
Ratchet\Server\IoServer
;
8
9
class
ChatServiceProvider
10
extends
ServiceProvider
11
{
12
protected
$defer
=
true
;
13
14
public
function
register
()
15
{
16
$this
->
app
->
bind
(
"chat.emitter"
,
function
()
17
{
18
return
new
EventEmitter
();
19
});
20
21
$this
->
app
->
bind
(
"chat.chat"
,
function
()
22
{
23
return
new
Chat
(
24
$this
->
app
->
make
(
"chat.emitter"
)
25
);
26
});
27
28
$this
->
app
->
bind
(
"chat.user"
,
function
()
29
{
30
return
new
User
();
31
});
32
33
$this
->
app
->
bind
(
"chat.command.serve"
,
function
()
34
{
35
return
new
Command\Serve
(
36
$this
->
app
->
make
(
"chat.chat"
)
37
);
38
});
39
40
$this
->
commands
(
"chat.command.serve"
);
41
}
42
43
public
function
provides
()
44
{
45
return
[
46
"chat.chat"
,
47
"chat.command.serve"
,
48
"chat.emitter"
,
49
"chat.server"
50
];
51
}
52
}
The first binding is a simple alias to the EvenementEventEmitter class (which Ratchet requires). We bind it here as we cannot guarantee that Ratchet will continue to use EvenementEventEmitter 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 Chat class. It implements the ChatInterface interface:
1
<?
php
2
3
namespace
Formativ\Chat
;
4
5
use
Evenement\EventEmitterInterface
;
6
use
Ratchet\ConnectionInterface
;
7
use
Ratchet\MessageComponentInterface
;
8
9
interface
ChatInterface
10
extends
MessageComponentInterface
11
{
12
public
function
getUserBySocket
(
ConnectionInterface
$socket
);
13
public
function
getEmitter
();
14
public
function
setEmitter
(
EventEmitterInterface
$emitter
);
15
public
function
getUsers
();
16
}
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:
1
<?
php
2
3
namespace
Formativ\Chat
;
4
5
use
Evenement\EventEmitterInterface
;
6
use
Exception
;
7
use
Ratchet\ConnectionInterface
;
8
use
SplObjectStorage
;
9
10
class
Chat
11
implements
ChatInterface
12
{
13
protected
$users
;
14
protected
$emitter
;
15
protected
$id
=
1
;
16
17
public
function
getUserBySocket
(
ConnectionInterface
$socket
)
18
{
19
foreach
(
$this
->
users
as
$next
)
20
{
21
if
(
$next
->
getSocket
()
===
$socket
)
22
{
23
return
$next
;
24
}
25
}
26
27
return
null
;
28
}
29
30
public
function
getEmitter
()
31
{
32
return
$this
->
emitter
;
33
}
34
35
public
function
setEmitter
(
EventEmitterInterface
$emitter
)
36
{
37
$this
->
emitter
=
$emitter
;
38
}
39
40
public
function
getUsers
()
41
{
42
return
$this
->
users
;
43
}
44
45
public
function
__construct
(
EventEmitterInterface
$emitter
)
46
{
47
$this
->
emitter
=
$emitter
;
48
$this
->
users
=
new
SplObjectStorage
();
49
}
50
51
public
function
onOpen
(
ConnectionInterface
$socket
)
52
{
53
$user
=
new
User
();
54
$user
->
setId
(
$this
->
id
++
);
55
$user
->
setSocket
(
$socket
);
56
57
$this
->
users
->
attach
(
$user
);
58
$this
->
emitter
->
emit
(
"open"
,
[
$user
]);
59
}
60
61
public
function
onMessage
(
62
ConnectionInterface
$socket
,
63
$message
64
)
65
{
66
$user
=
$this
->
getUserBySocket
(
$socket
);
67
$message
=
json_decode
(
$message
);
68
69
switch
(
$message
->
type
)
70
{
71
case
"name"
:
72
{
73
$user
->
setName
(
$message
->
data
);
74
$this
->
emitter
->
emit
(
"name"
,
[
75
$user
,
76
$message
->
data
77
]);
78
break
;
79
}
80
81
case
"message"
:
82
{
83
$this
->
emitter
->
emit
(
"message"
,
[
84
$user
,
85
$message
->
data
86
]);
87
break
;
88
}
89
}
90
91
foreach
(
$this
->
users
as
$next
)
92
{
93
if
(
$next
!==
$user
)
94
{
95
$next
->
getSocket
()
->
send
(
json_encode
([
96
"user"
=>
[
97
"id"
=>
$user
->
getId
(),
98
"name"
=>
$user
->
getName
()
99
],
100
"message"
=>
$message
101
]));
102
}
103
}
104
}
105
106
public
function
onClose
(
ConnectionInterface
$socket
)
107
{
108
$user
=
$this
->
getUserBySocket
(
$socket
);
109
110
if
(
$user
)
111
{
112
$this
->
users
->
detach
(
$user
);
113
$this
->
emitter
->
emit
(
"close"
,
[
$user
]);
114
}
115
}
116
117
public
function
onError
(
118
ConnectionInterface
$socket
,
119
Exception
$exception
120
)
121
{
122
$user
=
$this
->
getUserBySocket
(
$socket
);
123
124
if
(
$user
)
125
{
126
$user
->
getSocket
()
->
close
();
127
$this
->
emitter
->
emit
(
"error"
,
[
$user
,
$exception
]);
128
}
129
}
130
}
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:
1
<?
php
2
3
namespace
Formativ\Chat
;
4
5
use
Evenement\EventEmitterInterface
;
6
use
Ratchet\ConnectionInterface
;
7
use
Ratchet\MessageComponentInterface
;
8
9
interface
UserInterface
10
{
11
public
function
getSocket
();
12
public
function
setSocket
(
ConnectionInterface
$socket
);
13
public
function
getId
();
14
public
function
setId
(
$id
);
15
public
function
getName
();
16
public
function
setName
(
$name
);
17
}
1
<?
php
2
3
namespace
Formativ\Chat
;
4
5
use
Ratchet\ConnectionInterface
;
6
7
class
User
8
implements
UserInterface
9
{
10
protected
$socket
;
11
protected
$id
;
12
protected
$name
;
13
14
public
function
getSocket
()
15
{
16
return
$this
->
socket
;
17
}
18
19
public
function
setSocket
(
ConnectionInterface
$socket
)
20
{
21
$this
->
socket
=
$socket
;
22
return
$this
;
23
}
24
25
public
function
getId
()
26
{
27
return
$this
->
id
;
28
}
29
30
public
function
setId
(
$id
)
31
{
32
$this
->
id
=
$id
;
33
return
$this
;
34
}
35
36
public
function
getName
()
37
{
38
return
$this
->
name
;
39
}
40
41
public
function
setName
(
$name
)
42
{
43
$this
->
name
=
$name
;
44
return
$this
;
45
}
46
}
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.
All these classes lead us to the artisan command which will kick things off:
1
<?
php
2
3
namespace
Formativ\Chat\Command
;
4
5
use
Illuminate\Console\Command
;
6
use
Formativ\Chat\ChatInterface
;
7
use
Formativ\Chat\UserInterface
;
8
use
Ratchet\ConnectionInterface
;
9
use
Ratchet\Http\HttpServer
;
10
use
Ratchet\Server\IoServer
;
11
use
Ratchet\WebSocket\WsServer
;
12
use
Symfony\Component\Console\Input\InputOption
;
13
use
Symfony\Component\Console\Input\InputArgument
;
14
15
class
Serve
16
extends
Command
17
{
18
protected
$name
=
"chat:serve"
;
19
protected
$description
=
"Command description."
;
20
protected
$chat
;
21
22
protected
function
getUserName
(
$user
)
23
{
24
$suffix
=
" ("
.
$user
->
getId
()
.
")"
;
25
26
if
(
$name
=
$user
->
getName
())
27
{
28
return
$name
.
$suffix
;
29
}
30
31
return
"User"
.
$suffix
;
32
}
33
34
public
function
__construct
(
ChatInterface
$chat
)
35
{
36
parent
::
__construct
();
37
38
$this
->
chat
=
$chat
;
39
40
$open
=
function
(
UserInterface
$user
)
41
{
42
$name
=
$this
->
getUserName
(
$user
);
43
$this
->
line
(
"
44
<info>"
.
$name
.
" connected.</info>
45
"
);
46
};
47
48
$this
->
chat
->
getEmitter
()
->
on
(
"open"
,
$open
);
49
50
$close
=
function
(
UserInterface
$user
)
51
{
52
$name
=
$this
->
getUserName
(
$user
);
53
$this
->
line
(
"
54
<info>"
.
$name
.
" disconnected.</info>
55
"
);
56
};
57
58
$this
->
chat
->
getEmitter
()
->
on
(
"close"
,
$close
);
59
60
$message
=
function
(
UserInterface
$user
,
$message
)
61
{
62
$name
=
$this
->
getUserName
(
$user
);
63
$this
->
line
(
"
64
<info>New message from "
.
$name
.
":</info>
65
<comment>"
.
$message
.
"</comment>
66
<info>.</info>
67
"
);
68
};
69
70
$this
->
chat
->
getEmitter
()
->
on
(
"message"
,
$message
);
71
72
$name
=
function
(
UserInterface
$user
,
$message
)
73
{
74
$this
->
line
(
"
75
<info>User changed their name to:</info>
76
<comment>"
.
$message
.
"</comment>
77
<info>.</info>
78
"
);
79
};
80
81
$this
->
chat
->
getEmitter
()
->
on
(
"name"
,
$name
);
82
83
$error
=
function
(
UserInterface
$user
,
$exception
)
84
{
85
$message
=
$exception
->
getMessage
();
86
87
$this
->
line
(
"
88
<info>User encountered an exception:</info>
89
<comment>"
.
$message
.
"</comment>
90
<info>.</info>
91
"
);
92
};
93
94
$this
->
chat
->
getEmitter
()
->
on
(
"error"
,
$error
);
95
}
96
97
public
function
fire
()
98
{
99
$port
=
(
integer
)
$this
->
option
(
"port"
);
100
101
if
(
!
$port
)
102
{
103
$port
=
7778
;
104
}
105
106
$server
=
IoServer
::
factory
(
107
new
HttpServer
(
108
new
WsServer
(
109
$this
->
chat
110
)
111
),
112
$port
113
);
114
115
$this
->
line
(
"
116
<info>Listening on port</info>
117
<comment>"
.
$port
.
"</comment>
118
<info>.</info>
119
"
);
120
121
$server
->
run
();
122
}
123
124
protected
function
getOptions
()
125
{
126
return
[
127
[
128
"port"
,
129
null
,
130
InputOption
::
VALUE_REQUIRED
,
131
"Port to listen on."
,
132
null
133
]
134
];
135
}
136
}
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.
To connect to the web socket server; we need to add a bit of vanilla JavaScript:
1
try
{
2
if
(
!
WebSocket
)
{
3
console
.
log
(
"no websocket support"
);
4
}
else
{
5
var
socket
=
new
WebSocket
(
"ws://127.0.0.1:7778/"
);
6
7
socket
.
addEventListener
(
"open"
,
function
(
e
)
{
8
console
.
log
(
"open: "
,
e
);
9
});
10
11
socket
.
addEventListener
(
"error"
,
function
(
e
)
{
12
console
.
log
(
"error: "
,
e
);
13
});
14
15
socket
.
addEventListener
(
"message"
,
function
(
e
)
{
16
console
.
log
(
"message: "
,
JSON
.
parse
(
e
.
data
));
17
});
18
19
console
.
log
(
"socket:"
,
socket
);
20
21
window
.
socket
=
socket
;
22
}
23
}
catch
(
e
)
{
24
console
.
log
(
"exception: "
+
e
);
25
}
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.
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…
Getting our interface talking to our socket server is relatively straightforward. We begin by disabling our fixture data and modifying our model slightly:
1
App
.
Message
=
DS
.
Model
.
extend
({
2
"user_id"
:
DS
.
attr
(
"integer"
),
3
"user_name"
:
DS
.
attr
(
"string"
),
4
"user_id_class"
:
DS
.
attr
(
"string"
),
5
"message"
:
DS
.
attr
(
"string"
)
6
});
7
8
App
.
ApplicationAdapter
=
DS
.
FixtureAdapter
.
extend
();
9
10
App
.
Message
.
FIXTURES
=
[
11
// {
12
// "id" : 1,
13
// "user" : "Chris",
14
// "text" : "Hello World."
15
// },
16
// {
17
// "id" : 2,
18
// "user" : "Wayne",
19
// "text" : "Don't dig it, man."
20
// },
21
// {
22
// "id" : 3,
23
// "user" : "Chris",
24
// "text" : "Meh."
25
// }
26
];
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:
1
var
store
;
2
3
App
.
IndexRoute
=
Ember
.
Route
.
extend
({
4
"init"
:
function
()
{
5
store
=
this
.
store
;
6
},
7
"model"
:
function
()
{
8
return
store
.
find
(
"message"
);
9
}
10
});
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:
1
try
{
2
var
id
=
1
;
3
4
if
(
!
WebSocket
)
{
5
console
.
log
(
"no websocket support"
);
6
}
else
{
7
8
var
socket
=
new
WebSocket
(
"ws://127.0.0.1:7778/"
);
9
var
id
=
1
;
10
11
socket
.
addEventListener
(
"open"
,
function
(
e
)
{
12
// console.log("open: ", e);
13
});
14
15
socket
.
addEventListener
(
"error"
,
function
(
e
)
{
16
console
.
log
(
"error: "
,
e
);
17
});
18
19
socket
.
addEventListener
(
"message"
,
function
(
e
)
{
20
21
var
data
=
JSON
.
parse
(
e
.
data
);
22
var
user_id
=
data
.
user
.
id
;
23
var
user_name
=
data
.
user
.
name
;
24
var
message
=
data
.
message
.
data
;
25
26
switch
(
data
.
message
.
type
)
{
27
28
case
"name"
:
29
$
(
".name-"
+
user_id
).
html
(
user_name
);
30
break
;
31
32
case
"message"
:
33
store
.
push
(
"message"
,
{
34
"id"
:
id
++
,
35
"user_id"
:
user_id
,
36
"user_name"
:
user_name
||
"User"
,
37
"user_id_class"
:
"name-"
+
user_id
,
38
"message"
:
message
39
});
40
break
;
41
42
}
43
44
});
45
46
// console.log("socket:", socket);
47
48
window
.
socket
=
socket
;
// debug
49
}
50
}
catch
(
e
)
{
51
console
.
log
(
"exception: "
+
e
);
52
}
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…
1
App
.
IndexController
=
Ember
.
ArrayController
.
extend
({
2
"command"
:
null
,
3
"actions"
:
{
4
"send"
:
function
(
key
)
{
5
6
if
(
key
&&
key
!=
13
)
{
7
return
;
8
}
9
10
var
command
=
this
.
get
(
"command"
)
||
""
;
11
12
if
(
command
.
indexOf
(
"/name"
)
===
0
)
{
13
socket
.
send
(
JSON
.
stringify
({
14
"type"
:
"name"
,
15
"data"
:
command
.
split
(
"/name"
)[
1
]
16
}));
17
}
else
{
18
socket
.
send
(
JSON
.
stringify
({
19
"type"
:
"message"
,
20
"data"
:
command
21
}));
22
}
23
24
this
.
set
(
"command"
,
null
);
25
}
26
}
27
});
28
29
App
.
IndexView
=
Ember
.
View
.
extend
({
30
"keyDown"
:
function
(
e
)
{
31
this
.
get
(
"controller"
).
send
(
"send"
,
e
.
keyCode
);
32
}
33
});
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 (via 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:
1
public
function
onMessage
(
ConnectionInterface
$socket
,
$message
)
2
{
3
$user
=
$this
->
getUserBySocket
(
$socket
);
4
$message
=
json_decode
(
$message
);
5
6
switch
(
$message
->
type
)
7
{
8
case
"name"
:
9
{
10
$user
->
setName
(
$message
->
data
);
11
$this
->
emitter
->
emit
(
"name"
,
[
12
$user
,
13
$message
->
data
14
]);
15
break
;
16
}
17
18
case
"message"
:
19
{
20
$this
->
emitter
->
emit
(
"message"
,
[
21
$user
,
22
$message
->
data
23
]);
24
break
;
25
}
26
}
27
28
foreach
(
$this
->
users
as
$next
)
29
{
30
// if ($next !== $user)
31
// {
32
$next
->
getSocket
()
->
send
(
json_encode
([
33
"user"
=>
[
34
"id"
=>
$user
->
getId
(),
35
"name"
=>
$user
->
getName
()
36
],
37
"message"
=>
$message
38
]));
39
// }
40
}
41
}
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:
1
<script
2
type=
"text/x-handlebars"
3
data-template-name=
"index"
4
>
5
<
div
class
=
"container"
>
6
<
div
class
=
"row"
>
7
<
div
class
=
"col-md-12"
>
8
<
h1
>
Laravel
4
Chat
<
/h1>
9
<
table
class
=
"table table-striped"
>
10
@
{{
#
each
message
in
model
}}
11
<
tr
>
12
<
td
@
{{
bind
-
attr
class
=
"message.user_id_class"
}}
>
13
@
{{
message
.
user_name
}}
14
<
/td>
15
<
td
>
16
@
{{
message
.
message
}}
17
<
/td>
18
<
/tr>
19
@
{{
/each}}
20
<
/table>
21
<
/div>
22
<
/div>
23
<
div
class
=
"row"
>
24
<
div
class
=
"col-md-12"
>
25
<
div
class
=
"input-group"
>
26
@
{{
input
27
type
=
"text"
28
value
=
command
29
class
=
"form-control"
30
}}
31
<
span
class
=
"input-group-btn"
>
32
<
button
33
class
=
"btn btn-default"
34
@
{{
action
"send"
}}
35
>
36
Send
37
<
/button>
38
<
/span>
39
<
/div>
40
<
/div>
41
<
/div>
42
<
/div>
43
</script>
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.
For persistent sockets to remain open, Apache needs to keep a single process thread occupied. This is a problem as it will consume vast amounts of RAM over a shorter time period than non-persistent connections would. For this reason; I highly recommend using either Nginx or an event-based language to create your chat application.
It’s not that Apache sucks; this is just not the sort of thing it was designed for. Nginx, on the other hand, is event-based and doesn’t hold onto a whole thread while it waits for activity through the open socket.