Passed
Push — master ( 208511...51dea7 )
by Albert
05:55 queued 02:52
created

Websocket::getSender()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SwooleTW\Http\Websocket;
4
5
use InvalidArgumentException;
6
use Illuminate\Support\Facades\App;
7
use SwooleTW\Http\Websocket\Authenticatable;
8
use Illuminate\Contracts\Container\Container;
9
use SwooleTW\Http\Websocket\Rooms\RoomContract;
10
use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
11
12
class Websocket
13
{
14
    use Authenticatable;
15
16
    const PUSH_ACTION = 'push';
17
    const EVENT_CONNECT = 'connect';
18
    const USER_PREFIX = 'uid_';
19
20
    /**
21
     * Determine if to broadcast.
22
     *
23
     * @var boolean
24
     */
25
    protected $isBroadcast = false;
26
27
    /**
28
     * Scoket sender's fd.
29
     *
30
     * @var integer
31
     */
32
    protected $sender;
33
34
    /**
35
     * Recepient's fd or room name.
36
     *
37
     * @var array
38
     */
39
    protected $to = [];
40
41
    /**
42
     * Websocket event callbacks.
43
     *
44
     * @var array
45
     */
46
    protected $callbacks = [];
47
48
    /**
49
     * Middleware for on connect request.
50
     *
51
     * @var array
52
     */
53
    protected $middleware = [];
54
55
    /**
56
     * Pipeline instance.
57
     *
58
     * @var PipelineContract
59
     */
60
    protected $pipeline;
61
62
    /**
63
     * Room adapter.
64
     *
65
     * @var RoomContract
66
     */
67
    protected $room;
68
69
    /**
70
     * Websocket constructor.
71
     *
72
     * @var RoomContract $room
73
     * @var PipelineContract $pipeline
74
     */
75
    public function __construct(RoomContract $room, PipelineContract $pipeline)
76
    {
77
        $this->room = $room;
78
        $this->pipeline = $pipeline;
79
        $this->setDefaultMiddleware();
80
    }
81
82
    /**
83
     * Set broadcast to true.
84
     */
85
    public function broadcast()
86
    {
87
        $this->isBroadcast = true;
88
89
        return $this;
90
    }
91
92
    /**
93
     * Set multiple recipients fd or room names.
94
     *
95
     * @param integer, string, array
96
     * @return $this
97
     */
98
    public function to($values)
99
    {
100
        $values = is_string($values) || is_integer($values) ? func_get_args() : $values;
101
102
        foreach ($values as $value) {
103
            if (! in_array($value, $this->to)) {
104
                $this->to[] = $value;
105
            }
106
        }
107
108
        return $this;
109
    }
110
111
    /**
112
     * Join sender to multiple rooms.
113
     *
114
     * @param string, array $rooms
115
     * @return $this
116
     */
117
    public function join($rooms)
118
    {
119
        $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms;
120
121
        $this->room->add($this->sender, $rooms);
122
123
        return $this;
124
    }
125
126
    /**
127
     * Make sender leave multiple rooms.
128
     *
129
     * @param string, array
130
     * @return $this
131
     */
132
    public function leave($rooms = [])
133
    {
134
        $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms;
135
136
        $this->room->delete($this->sender, $rooms);
137
138
        return $this;
139
    }
140
141
    /**
142
     * Emit data and reset some status.
143
     *
144
     * @param string
145
     * @param mixed
146
     * @return boolean
147
     */
148
    public function emit(string $event, $data)
149
    {
150
        $fds = $this->getFds();
151
        $assigned = ! empty($this->to);
152
153
        // if no fds are found, but rooms are assigned
154
        // that means trying to emit to a non-existing room
155
        // skip it directly instead of pushing to a task queue
156
        if (empty($fds) && $assigned) {
157
            return false;
158
        }
159
160
        $result = app('swoole.server')->task([
161
            'action' => static::PUSH_ACTION,
162
            'data' => [
163
                'sender' => $this->sender,
164
                'fds' => $fds,
165
                'broadcast' => $this->isBroadcast,
166
                'assigned' => $assigned,
167
                'event' => $event,
168
                'message' => $data
169
            ]
170
        ]);
171
172
        $this->reset();
173
174
        return $result === false ? false : true;
175
    }
176
177
    /**
178
     * An alias of `join` function.
179
     *
180
     * @param string
181
     * @return $this
182
     */
183
    public function in($room)
184
    {
185
        $this->join($room);
186
187
        return $this;
188
    }
189
190
    /**
191
     * Register an event name with a closure binding.
192
     *
193
     * @param string
194
     * @param callback
195
     * @return $this
196
     */
197
    public function on(string $event, $callback)
198
    {
199
        if (! is_string($callback) && ! is_callable($callback)) {
200
            throw new InvalidArgumentException(
201
                'Invalid websocket callback. Must be a string or callable.'
202
            );
203
        }
204
205
        $this->callbacks[$event] = $callback;
206
207
        return $this;
208
    }
209
210
    /**
211
     * Check if this event name exists.
212
     *
213
     * @param string
214
     * @return boolean
215
     */
216
    public function eventExists(string $event)
217
    {
218
        return array_key_exists($event, $this->callbacks);
219
    }
220
221
    /**
222
     * Execute callback function by its event name.
223
     *
224
     * @param string
225
     * @param mixed
226
     * @return mixed
227
     */
228
    public function call(string $event, $data = null)
229
    {
230
        if (! $this->eventExists($event)) {
231
            return null;
232
        }
233
234
        // inject request param on connect event
235
        $isConnect = $event === static::EVENT_CONNECT;
236
        $dataKey = $isConnect ? 'request' : 'data';
237
238
        // dispatch request to pipeline if middleware are set
239
        if ($isConnect && count($this->middleware)) {
240
            $data = $this->setRequestThroughMiddleware($data);
241
        }
242
243
        return App::call($this->callbacks[$event], [
244
            'websocket' => $this,
245
            $dataKey => $data
246
        ]);
247
    }
248
249
    /**
250
     * Close current connection.
251
     *
252
     * @param integer
253
     * @return boolean
254
     */
255
    public function close(int $fd = null)
256
    {
257
        return app('swoole.server')->close($fd ?: $this->sender);
258
    }
259
260
    /**
261
     * Set sender fd.
262
     *
263
     * @param integer
264
     * @return $this
265
     */
266
    public function setSender(int $fd)
267
    {
268
        $this->sender = $fd;
269
270
        return $this;
271
    }
272
273
    /**
274
     * Get current sender fd.
275
     */
276
    public function getSender()
277
    {
278
        return $this->sender;
279
    }
280
281
    /**
282
     * Get broadcast status value.
283
     */
284
    public function getIsBroadcast()
285
    {
286
        return $this->isBroadcast;
287
    }
288
289
    /**
290
     * Get push destinations (fd or room name).
291
     */
292
    public function getTo()
293
    {
294
        return $this->to;
295
    }
296
297
    /**
298
     * Get all fds we're going to push data to.
299
     */
300
    protected function getFds()
301
    {
302
        $fds = array_filter($this->to, function ($value) {
303
            return is_integer($value);
304
        });
305
        $rooms = array_diff($this->to, $fds);
306
307
        foreach ($rooms as $room) {
308
            $clients = $this->room->getClients($room);
309
            // fallback fd with wrong type back to fds array
310
            if (empty($clients) && is_numeric($room)) {
311
                $fds[] = $room;
312
            } else {
313
                $fds = array_merge($fds, $clients);
314
            }
315
        }
316
317
        return array_values(array_unique($fds));
318
    }
319
320
    /**
321
     * Reset some data status.
322
     */
323
    public function reset($force = false)
324
    {
325
        $this->isBroadcast = false;
326
        $this->to = [];
327
328
        if ($force) {
329
            $this->sender = null;
330
            $this->userId = null;
331
        }
332
333
        return $this;
334
    }
335
336
    /**
337
     * Get or set middleware.
338
     */
339
    public function middleware($middleware = null)
340
    {
341
        if (is_null($middleware)) {
342
            return $this->middleware;
343
        }
344
345
        if (is_string($middleware)) {
346
            $middleware = func_get_args();
347
        }
348
349
        $this->middleware = array_unique(array_merge($this->middleware, $middleware));
350
351
        return $this;
352
    }
353
354
    /**
355
     * Set default middleware.
356
     */
357
    protected function setDefaultMiddleware()
358
    {
359
        $this->middleware = app('config')->get('swoole_websocket.middleware', []);
360
    }
361
362
    /**
363
     * Set container to pipeline.
364
     */
365
    public function setContainer(Container $container)
366
    {
367
        $pipeline = $this->pipeline;
368
369
        $closure = function () use ($container) {
370
            $this->container = $container;
0 ignored issues
show
Bug Best Practice introduced by
The property container does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
371
        };
372
373
        $resetPipeline = $closure->bindTo($pipeline, $pipeline);
374
        $resetPipeline();
375
    }
376
377
    /**
378
     * Set the given request through the middleware.
379
     *
380
     * @param  \Illuminate\Http\Request  $request
381
     * @return \Illuminate\Http\Request
382
     */
383
    protected function setRequestThroughMiddleware($request)
384
    {
385
        return $this->pipeline
386
            ->send($request)
387
            ->through($this->middleware)
388
            ->then(function ($request) {
389
                return $request;
390
            });
391
    }
392
}
393