Chat   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 18
eloc 77
c 4
b 0
f 0
dl 0
loc 225
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A onError() 0 9 1
B onMessage() 0 40 7
A onOpen() 0 17 1
A onClose() 0 16 1
A unsubscribeFromChannel() 0 14 2
A sendMessageToChannel() 0 23 4
A __construct() 0 5 1
A subscribeToChannel() 0 11 1
1
<?php
2
3
namespace AppBundle\Server;
4
5
use Ratchet\ConnectionInterface;
6
use Ratchet\MessageComponentInterface;
7
8
/**
9
 * Chat Server.
10
 */
11
class Chat implements MessageComponentInterface
12
{
13
    /**
14
     * @var \SplObjectStorage
15
     */
16
    private $clients;
17
18
    /**
19
     * @var array
20
     */
21
    private $users = [];
22
23
    /**
24
     * @var string
25
     */
26
    private $botName = 'ChatBot';
27
28
    /**
29
     * @var string
30
     */
31
    private $defaultChannel = 'general';
32
33
    /**
34
     * Chat constructor.
35
     */
36
    public function __construct()
37
    {
38
        // Initialize
39
        $this->clients = new \SplObjectStorage();
40
        $this->users = [];
41
    }
42
43
    /**
44
     * A new websocket connection.
45
     *
46
     * @param ConnectionInterface $conn
47
     */
48
    public function onOpen(ConnectionInterface $conn)
49
    {
50
        // Initialize: Add connection to clients list
51
        $this->clients->attach($conn);
52
        $this->users[$conn->resourceId] = [
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
53
            'connection' => $conn,
54
            'user' => '',
55
            'channels' => [],
56
        ];
57
58
        // Send hello message
59
        $conn->send(json_encode([
60
            'action' => 'message',
61
            'channel' => $this->defaultChannel,
62
            'user' => $this->botName,
63
            'message' => sprintf('Connection established. Welcome #%d!', $conn->resourceId),
64
            'messageClass' => 'success',
65
        ]));
66
    }
67
68
    /**
69
     *
70
     * @param ConnectionInterface $closedConnection
71
     * A connection is closed.
72
     */
73
    public function onClose(ConnectionInterface $closedConnection)
74
    {
75
        // Send Goodbye message
76
        $this->sendMessageToChannel(
77
            $closedConnection,
78
            $this->defaultChannel,
79
            $this->botName,
80
            $this->users[$closedConnection->resourceId]['user'].' has disconnected'
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
81
        );
82
83
        // Remove connection from users
84
        unset($this->users[$closedConnection->resourceId]);
85
86
        // Detach connection and send message (for log purposes)
87
        $this->clients->detach($closedConnection);
88
        echo sprintf('Connection #%d has disconnected\n', $closedConnection->resourceId);
89
    }
90
91
    /**
92
     * Error handling.
93
     *
94
     * @param ConnectionInterface $conn
95
     * @param \Exception $e
96
     */
97
    public function onError(ConnectionInterface $conn, \Exception $e)
98
    {
99
        $conn->send(json_encode([
100
            'action' => 'message',
101
            'channel' => $this->defaultChannel,
102
            'user' => $this->botName,
103
            'message' => 'An error has occurred: '.$e->getMessage(),
104
        ]));
105
        $conn->close();
106
    }
107
108
    /**
109
     * Handle message sending.
110
     *
111
     * @param ConnectionInterface $conn
112
     * @param string $message
113
     *
114
     * @return bool - False if message is not a valid JSON or action is invalid
115
     */
116
    public function onMessage(ConnectionInterface $conn, $message)
117
    {
118
        // Initialize
119
        $messageData = json_decode($message);
120
121
        // Check message data
122
        if (null === $messageData) {
123
            return false;
124
        }
125
126
        // Check connection user
127
        if (empty($this->users[$conn->resourceId]['user']) && $messageData->user) {
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
128
            $this->users[$conn->resourceId]['user'] = $messageData->user;
129
        }
130
131
        // Initialize message data
132
        $action = $messageData->action ?? 'unknown';
133
        $channel = $messageData->channel ?? $this->defaultChannel;
134
        $user = $messageData->user ?? $this->botName;
135
        $message = $messageData->message ?? '';
136
137
        // Check action
138
        switch ($action) {
139
            case 'subscribe':
140
                $this->subscribeToChannel($conn, $channel, $user);
141
142
                return true;
143
            case 'unsubscribe':
144
                $this->unsubscribeFromChannel($conn, $channel, $user);
145
146
                return true;
147
            case 'message':
148
                return $this->sendMessageToChannel($conn, $channel, $user, $message);
149
            default:
150
                echo sprintf('Action "%s" is not supported yet!', $action);
151
                break;
152
        }
153
154
        // Return error
155
        return false;
156
    }
157
158
    /**
159
     * Subscribe connection to a given channel.
160
     *
161
     * @param ConnectionInterface $conn - Active connection
162
     * @param $channel - Channel to subscribe to
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
163
     * @param $user - Username of subscribed user
164
     */
165
    private function subscribeToChannel(ConnectionInterface $conn, $channel, $user)
166
    {
167
        // Add channel to connection channels
168
        $this->users[$conn->resourceId]['channels'][$channel] = $channel;
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
169
170
        // Send joined message to channel
171
        $this->sendMessageToChannel(
172
            $conn,
173
            $channel,
174
            $this->botName,
175
            $user.' joined #'.$channel
176
        );
177
    }
178
179
    /**
180
     * Unsubscribe connection to a given channel.
181
     *
182
     * @param ConnectionInterface $conn - Active connection
183
     * @param $channel - Channel to unsubscribe from
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
184
     * @param $user - Username of unsubscribed user
185
     */
186
    private function unsubscribeFromChannel(ConnectionInterface $conn, $channel, $user)
187
    {
188
        // Check connection
189
        if (\array_key_exists($channel, $this->users[$conn->resourceId]['channels'])) {
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
190
            // Delete connection
191
            unset($this->users[$conn->resourceId]['channels']);
192
        }
193
194
        // Send left message to channel
195
        $this->sendMessageToChannel(
196
            $conn,
197
            $channel,
198
            $this->botName,
199
            $user.' left #'.$channel
200
        );
201
    }
202
203
    /**
204
     * Send message to all connections of a given channel.
205
     *
206
     * @param ConnectionInterface $conn - Active connection
207
     * @param $channel - Channel to send message to
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
208
     * @param $user - User's username
209
     * @param $message - User's message
210
     *
211
     * @return bool - False if channel doesn't exists
212
     */
213
    private function sendMessageToChannel(ConnectionInterface $conn, $channel, $user, $message)
214
    {
215
        // Check if connection is linked to channel
216
        if (!isset($this->users[$conn->resourceId]['channels'][$channel])) {
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
217
            // Don't send message
218
            return false;
219
        }
220
221
        // Loop to send message to all users
222
        foreach ($this->users as $connectionId => $userConnection) {
223
            // Check if user has subscribe to channel
224
            if (\array_key_exists($channel, $userConnection['channels'])) {
225
                $userConnection['connection']->send(json_encode([
226
                    'action' => 'message',
227
                    'channel' => $channel,
228
                    'user' => $user,
229
                    'message' => $message,
230
                ]));
231
            }
232
        }
233
234
        // Return success
235
        return true;
236
    }
237
}
238