Completed
Branch master (9dff9d)
by Albert
05:30
created

InteractsWithWebsocket::pushMessage()   C

Complexity

Conditions 13
Paths 12

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 12
nc 12
nop 2
dl 0
loc 25
rs 6.6166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SwooleTW\Http\Concerns;
4
5
use Throwable;
6
use Swoole\Websocket\Frame;
7
use Swoole\Websocket\Server;
8
use Illuminate\Pipeline\Pipeline;
9
use SwooleTW\Http\Websocket\Parser;
10
use Illuminate\Support\Facades\Facade;
11
use SwooleTW\Http\Websocket\Websocket;
12
use SwooleTW\Http\Transformers\Request;
13
use SwooleTW\Http\Websocket\HandlerContract;
14
use SwooleTW\Http\Websocket\Rooms\RoomContract;
15
16
trait InteractsWithWebsocket
17
{
18
    /**
19
     * @var boolean
20
     */
21
    protected $isWebsocket = false;
22
23
    /**
24
     * @var SwooleTW\Http\Websocket\HandlerContract
0 ignored issues
show
Bug introduced by
The type SwooleTW\Http\Concerns\S...bsocket\HandlerContract was not found. Did you mean SwooleTW\Http\Websocket\HandlerContract? If so, make sure to prefix the type with \.
Loading history...
25
     */
26
    protected $websocketHandler;
27
28
    /**
29
     * @var SwooleTW\Http\Websocket\Parser
0 ignored issues
show
Bug introduced by
The type SwooleTW\Http\Concerns\S...W\Http\Websocket\Parser was not found. Did you mean SwooleTW\Http\Websocket\Parser? If so, make sure to prefix the type with \.
Loading history...
30
     */
31
    protected $parser;
32
33
    /**
34
     * Websocket server events.
35
     *
36
     * @var array
37
     */
38
    protected $wsEvents = ['open', 'message', 'close'];
39
40
    /**
41
     * "onOpen" listener.
42
     *
43
     * @param \Swoole\Websocket\Server $server
44
     * @param \Swoole\Http\Request $swooleRequest
45
     */
46
    public function onOpen($server, $swooleRequest)
0 ignored issues
show
Unused Code introduced by
The parameter $server is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

46
    public function onOpen(/** @scrutinizer ignore-unused */ $server, $swooleRequest)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
47
    {
48
        $illuminateRequest = Request::make($swooleRequest)->toIlluminate();
49
50
        try {
51
            $this->app['swoole.websocket']->reset(true)->setSender($swooleRequest->fd);
52
            // set currnt request to sandbox
53
            $this->app['swoole.sandbox']->setRequest($illuminateRequest);
54
            // enable sandbox
55
            $this->app['swoole.sandbox']->enable();
56
            // check if socket.io connection established
57
            if (! $this->websocketHandler->onOpen($swooleRequest->fd, $illuminateRequest)) {
58
                return;
59
            }
60
            // trigger 'connect' websocket event
61
            if ($this->app['swoole.websocket']->eventExists('connect')) {
62
                // set sandbox container to websocket pipeline
63
                $this->app['swoole.websocket']->setContainer($this->app['swoole.sandbox']->getApplication());
64
                $this->app['swoole.websocket']->call('connect', $illuminateRequest);
65
            }
66
        } catch (Throwable $e) {
67
            $this->logServerError($e);
0 ignored issues
show
Bug introduced by
It seems like logServerError() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
            $this->/** @scrutinizer ignore-call */ 
68
                   logServerError($e);
Loading history...
68
        } finally {
69
            // disable and recycle sandbox resource
70
            $this->app['swoole.sandbox']->disable();
71
        }
72
    }
73
74
    /**
75
     * "onMessage" listener.
76
     *
77
     * @param \Swoole\Websocket\Server $server
78
     * @param \Swoole\Websocket\Frame $frame
79
     */
80
    public function onMessage($server, $frame)
81
    {
82
        try {
83
            // execute parser strategies and skip non-message packet
84
            if ($this->parser->execute($server, $frame)) {
85
                return;
86
            }
87
88
            // decode raw message via parser
89
            $payload = $this->parser->decode($frame);
90
91
            $this->app['swoole.websocket']->reset(true)->setSender($frame->fd);
92
93
            // enable sandbox
94
            $this->app['swoole.sandbox']->enable();
95
96
            // dispatch message to registered event callback
97
            if ($this->app['swoole.websocket']->eventExists($payload['event'])) {
98
                $this->app['swoole.websocket']->call($payload['event'], $payload['data']);
99
            } else {
100
                $this->websocketHandler->onMessage($frame);
101
            }
102
        } catch (Throwable $e) {
103
            $this->logServerError($e);
104
        } finally {
105
            // disable and recycle sandbox resource
106
            $this->app['swoole.sandbox']->disable();
107
        }
108
    }
109
110
    /**
111
     * "onClose" listener.
112
     *
113
     * @param \Swoole\Websocket\Server $server
114
     * @param int $fd
115
     * @param int $reactorId
116
     */
117
    public function onClose($server, $fd, $reactorId)
0 ignored issues
show
Unused Code introduced by
The parameter $server is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

117
    public function onClose(/** @scrutinizer ignore-unused */ $server, $fd, $reactorId)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
118
    {
119
        if (! $this->isWebsocket($fd)) {
120
            return;
121
        }
122
123
        try {
124
            $this->app['swoole.websocket']->reset(true)->setSender($fd);
125
            // trigger 'disconnect' websocket event
126
            if ($this->app['swoole.websocket']->eventExists('disconnect')) {
127
                $this->app['swoole.websocket']->call('disconnect');
128
            } else {
129
                $this->websocketHandler->onClose($fd, $reactorId);
130
            }
131
            // leave all rooms
132
            $this->app['swoole.websocket']->leave();
133
        } catch (Throwable $e) {
134
            $this->logServerError($e);
135
        }
136
    }
137
138
    /**
139
     * Push websocket message to clients.
140
     *
141
     * @param \Swoole\Websocket\Server $server
142
     * @param mixed $data
143
     */
144
    public function pushMessage($server, array $data)
145
    {
146
        [$opcode, $sender, $fds, $broadcast, $assigned, $event, $message] = $this->normalizePushData($data);
147
        $message = $this->parser->encode($event, $message);
148
149
        // attach sender if not broadcast
150
        if (! $broadcast && $sender && ! in_array($sender, $fds)) {
151
            $fds[] = $sender;
152
        }
153
154
        // check if to broadcast all clients
155
        if ($broadcast && empty($fds) && ! $assigned) {
156
            foreach ($server->connections as $fd) {
157
                if ($this->isWebsocket($fd)) {
0 ignored issues
show
Bug introduced by
$fd of type null is incompatible with the type integer expected by parameter $fd of SwooleTW\Http\Concerns\I...ebsocket::isWebsocket(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
                if ($this->isWebsocket(/** @scrutinizer ignore-type */ $fd)) {
Loading history...
158
                    $fds[] = $fd;
159
                }
160
            }
161
        }
162
163
        // push message to designated fds
164
        foreach ($fds as $fd) {
165
            if (($broadcast && $sender === (integer) $fd) || ! $server->exist($fd)) {
166
                continue;
167
            }
168
            $server->push($fd, $message, $opcode);
0 ignored issues
show
Bug introduced by
It seems like $opcode can also be of type integer; however, parameter $binary_data of Swoole\WebSocket\Server::push() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

168
            $server->push($fd, $message, /** @scrutinizer ignore-type */ $opcode);
Loading history...
169
        }
170
    }
171
172
    /**
173
     * Set frame parser for websocket.
174
     *
175
     * @param \SwooleTW\Http\Websocket\Parser $parser
176
     */
177
    public function setParser(Parser $parser)
178
    {
179
        $this->parser = $parser;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parser of type SwooleTW\Http\Websocket\Parser is incompatible with the declared type SwooleTW\Http\Concerns\S...W\Http\Websocket\Parser of property $parser.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
180
181
        return $this;
182
    }
183
184
    /**
185
     * Get frame parser for websocket.
186
     */
187
    public function getParser()
188
    {
189
        return $this->parser;
190
    }
191
192
    /**
193
     * Prepare settings if websocket is enabled.
194
     */
195
    protected function prepareWebsocket()
196
    {
197
        $isWebsocket = $this->container['config']->get('swoole_http.websocket.enabled');
198
        $parser = $this->container['config']->get('swoole_websocket.parser');
199
200
        if ($isWebsocket) {
201
            array_push($this->events, ...$this->wsEvents);
202
            $this->isWebsocket = true;
203
            $this->setParser(new $parser);
204
        }
205
    }
206
207
    /**
208
     * Check if it's a websocket fd.
209
     */
210
    protected function isWebsocket(int $fd)
211
    {
212
        $info = $this->container['swoole.server']->connection_info($fd);
213
214
        return array_key_exists('websocket_status', $info) && $info['websocket_status'];
215
    }
216
217
    /**
218
     * Prepare websocket handler for onOpen and onClose callback.
219
     */
220
    protected function prepareWebsocketHandler()
221
    {
222
        $handlerClass = $this->container['config']->get('swoole_websocket.handler');
223
224
        if (! $handlerClass) {
225
            throw new Exception('Websocket handler is not set in swoole_websocket config');
0 ignored issues
show
Bug introduced by
The type SwooleTW\Http\Concerns\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
226
        }
227
228
        $this->setWebsocketHandler($this->app->make($handlerClass));
229
    }
230
231
    /**
232
     * Set websocket handler.
233
     */
234
    public function setWebsocketHandler(HandlerContract $handler)
235
    {
236
        $this->websocketHandler = $handler;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handler of type SwooleTW\Http\Websocket\HandlerContract is incompatible with the declared type SwooleTW\Http\Concerns\S...bsocket\HandlerContract of property $websocketHandler.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
237
238
        return $this;
239
    }
240
241
    /**
242
     * Get websocket handler.
243
     */
244
    public function getWebsocketHandler()
245
    {
246
        return $this->websocketHandler;
247
    }
248
249
    /**
250
     * Get websocket handler for onOpen and onClose callback.
251
     */
252
    protected function getWebsocketRoom()
253
    {
254
        $driver = $this->container['config']->get('swoole_websocket.default');
255
        $configs = $this->container['config']->get("swoole_websocket.settings.{$driver}");
256
        $className = $this->container['config']->get("swoole_websocket.drivers.{$driver}");
257
258
        $websocketRoom = new $className($configs);
259
        $websocketRoom->prepare();
260
261
        return $websocketRoom;
262
    }
263
264
    /**
265
     * Bind room instance to Laravel app container.
266
     */
267
    protected function bindRoom()
268
    {
269
        $this->app->singleton(RoomContract::class, function ($app) {
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

269
        $this->app->singleton(RoomContract::class, function (/** @scrutinizer ignore-unused */ $app) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
270
            return $this->getWebsocketRoom();
271
        });
272
        $this->app->alias(RoomContract::class, 'swoole.room');
273
    }
274
275
    /**
276
     * Bind websocket instance to Laravel app container.
277
     */
278
    protected function bindWebsocket()
279
    {
280
        $this->app->singleton(Websocket::class, function ($app) {
281
            return new Websocket($app['swoole.room'], new Pipeline($app));
282
        });
283
        $this->app->alias(Websocket::class, 'swoole.websocket');
284
    }
285
286
    /**
287
     * Load websocket routes file.
288
     */
289
    protected function loadWebsocketRoutes()
290
    {
291
        $routePath = $this->container['config']->get('swoole_websocket.route_file');
292
293
        if (! file_exists($routePath)) {
294
            $routePath = __DIR__ . '/../../routes/websocket.php';
295
        }
296
297
        return require $routePath;
298
    }
299
300
    /**
301
     * Normalize data for message push.
302
     */
303
    public function normalizePushData(array $data)
304
    {
305
        $opcode = $data['opcode'] ?? 1;
306
        $sender = $data['sender'] ?? 0;
307
        $fds = $data['fds'] ?? [];
308
        $broadcast = $data['broadcast'] ?? false;
309
        $assigned = $data['assigned'] ?? false;
310
        $event = $data['event'] ?? null;
311
        $message = $data['message'] ?? null;
312
313
        return [$opcode, $sender, $fds, $broadcast, $assigned, $event, $message];
314
    }
315
}
316