Completed
Pull Request — master (#447)
by Alexandru
01:26
created

LocalChannelManager::userLeftPresenceChannel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\ChannelManagers;
4
5
use BeyondCode\LaravelWebSockets\Channels\Channel;
6
use BeyondCode\LaravelWebSockets\Channels\PresenceChannel;
7
use BeyondCode\LaravelWebSockets\Channels\PrivateChannel;
8
use BeyondCode\LaravelWebSockets\Contracts\ChannelManager;
9
use Illuminate\Support\Str;
10
use Ratchet\ConnectionInterface;
11
use React\EventLoop\LoopInterface;
12
use React\Promise\FulfilledPromise;
13
use React\Promise\PromiseInterface;
14
use stdClass;
15
16
class LocalChannelManager implements ChannelManager
17
{
18
    /**
19
     * The list of stored channels.
20
     *
21
     * @var array
22
     */
23
    protected $channels = [];
24
25
    /**
26
     * The list of users that joined the presence channel.
27
     *
28
     * @var array
29
     */
30
    protected $users = [];
31
32
    /**
33
     * Wether the current instance accepts new connections.
34
     *
35
     * @var bool
36
     */
37
    protected $acceptsNewConnections = true;
38
39
    /**
40
     * Create a new channel manager instance.
41
     *
42
     * @param  LoopInterface  $loop
43
     * @param  string|null  $factoryClass
44
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
45
     */
46
    public function __construct(LoopInterface $loop, $factoryClass = null)
47
    {
48
        //
49
    }
50
51
    /**
52
     * Find the channel by app & name.
53
     *
54
     * @param  string|int  $appId
55
     * @param  string  $channel
56
     * @return null|BeyondCode\LaravelWebSockets\Channels\Channel
57
     */
58
    public function find($appId, string $channel)
59
    {
60
        return $this->channels[$appId][$channel] ?? null;
61
    }
62
63
    /**
64
     * Find a channel by app & name or create one.
65
     *
66
     * @param  string|int  $appId
67
     * @param  string  $channel
68
     * @return BeyondCode\LaravelWebSockets\Channels\Channel
69
     */
70
    public function findOrCreate($appId, string $channel)
71
    {
72
        if (! $channelInstance = $this->find($appId, $channel)) {
73
            $class = $this->getChannelClassName($channel);
74
75
            $this->channels[$appId][$channel] = new $class($channel);
76
        }
77
78
        return $this->channels[$appId][$channel];
79
    }
80
81
    /**
82
     * Get the local connections, regardless of the channel
83
     * they are connected to.
84
     *
85
     * @return \React\Promise\PromiseInterface
86
     */
87
    public function getLocalConnections(): PromiseInterface
88
    {
89
        $connections = collect($this->channels)
90
            ->map(function ($channelsWithConnections, $appId) {
0 ignored issues
show
Unused Code introduced by
The parameter $appId is not used and could be removed.

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

Loading history...
91
                return collect($channelsWithConnections)->values();
92
            })
93
            ->values()->collapse()
94
            ->map(function ($channel) {
95
                return collect($channel->getConnections());
96
            })
97
            ->values()->collapse()
98
            ->toArray();
99
100
        return new FulfilledPromise($connections);
0 ignored issues
show
Deprecated Code introduced by
The class React\Promise\FulfilledPromise has been deprecated with message: 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
101
    }
102
103
    /**
104
     * Get all channels for a specific app
105
     * for the current instance.
106
     *
107
     * @param  string|int  $appId
108
     * @return \React\Promise\PromiseInterface[array]
0 ignored issues
show
Documentation introduced by
The doc-type \React\Promise\PromiseInterface[array] could not be parsed: Expected "]" at position 2, but found "array". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
109
     */
110
    public function getLocalChannels($appId): PromiseInterface
111
    {
112
        return new FulfilledPromise(
0 ignored issues
show
Deprecated Code introduced by
The class React\Promise\FulfilledPromise has been deprecated with message: 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
113
            $this->channels[$appId] ?? []
114
        );
115
    }
116
117
    /**
118
     * Get all channels for a specific app
119
     * across multiple servers.
120
     *
121
     * @param  string|int  $appId
122
     * @return \React\Promise\PromiseInterface[array]
0 ignored issues
show
Documentation introduced by
The doc-type \React\Promise\PromiseInterface[array] could not be parsed: Expected "]" at position 2, but found "array". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
123
     */
124
    public function getGlobalChannels($appId): PromiseInterface
125
    {
126
        return $this->getLocalChannels($appId);
127
    }
128
129
    /**
130
     * Remove connection from all channels.
131
     *
132
     * @param  \Ratchet\ConnectionInterface  $connection
133
     * @return void
134
     */
135
    public function unsubscribeFromAllChannels(ConnectionInterface $connection)
136
    {
137
        if (! isset($connection->app)) {
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
138
            return;
139
        }
140
141
        $this->getLocalChannels($connection->app->id)
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
142
            ->then(function ($channels) use ($connection) {
143
                collect($channels)->each->unsubscribe($connection);
144
145
                collect($channels)
146
                    ->reject->hasConnections()
147
                    ->each(function (Channel $channel, string $channelName) use ($connection) {
148
                        unset($this->channels[$connection->app->id][$channelName]);
149
                    });
150
            });
151
152
        $this->getLocalChannels($connection->app->id)
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
153
            ->then(function ($channels) use ($connection) {
154
                if (count($channels) === 0) {
155
                    unset($this->channels[$connection->app->id]);
156
                }
157
            });
158
    }
159
160
    /**
161
     * Subscribe the connection to a specific channel.
162
     *
163
     * @param  \Ratchet\ConnectionInterface  $connection
164
     * @param  string  $channelName
165
     * @param  stdClass  $payload
166
     * @return void
167
     */
168
    public function subscribeToChannel(ConnectionInterface $connection, string $channelName, stdClass $payload)
169
    {
170
        $channel = $this->findOrCreate($connection->app->id, $channelName);
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
171
172
        $channel->subscribe($connection, $payload);
173
    }
174
175
    /**
176
     * Unsubscribe the connection from the channel.
177
     *
178
     * @param  \Ratchet\ConnectionInterface  $connection
179
     * @param  string  $channelName
180
     * @param  stdClass  $payload
181
     * @return void
182
     */
183
    public function unsubscribeFromChannel(ConnectionInterface $connection, string $channelName, stdClass $payload)
184
    {
185
        $channel = $this->findOrCreate($connection->app->id, $channelName);
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
186
187
        $channel->unsubscribe($connection, $payload);
188
    }
189
190
    /**
191
     * Subscribe the connection to a specific channel.
192
     *
193
     * @param  string|int  $appId
194
     * @return void
195
     */
196
    public function subscribeToApp($appId)
197
    {
198
        //
199
    }
200
201
    /**
202
     * Unsubscribe the connection from the channel.
203
     *
204
     * @param  string|int  $appId
205
     * @return void
206
     */
207
    public function unsubscribeFromApp($appId)
208
    {
209
        //
210
    }
211
212
    /**
213
     * Get the connections count on the app
214
     * for the current server instance.
215
     *
216
     * @param  string|int  $appId
217
     * @param  string|null  $channelName
218
     * @return \React\Promise\PromiseInterface
219
     */
220
    public function getLocalConnectionsCount($appId, string $channelName = null): PromiseInterface
221
    {
222
        return $this->getLocalChannels($appId)
223
            ->then(function ($channels) use ($channelName) {
224
                return collect($channels)
225
                    ->when(! is_null($channelName), function ($collection) use ($channelName) {
226
                        return $collection->filter(function (Channel $channel) use ($channelName) {
227
                            return $channel->getName() === $channelName;
228
                        });
229
                    })
230
                    ->flatMap(function (Channel $channel) {
231
                        return collect($channel->getConnections())->pluck('socketId');
232
                    })
233
                    ->unique()
234
                    ->count();
235
            });
236
    }
237
238
    /**
239
     * Get the connections count
240
     * across multiple servers.
241
     *
242
     * @param  string|int  $appId
243
     * @param  string|null  $channelName
244
     * @return \React\Promise\PromiseInterface
245
     */
246
    public function getGlobalConnectionsCount($appId, string $channelName = null): PromiseInterface
247
    {
248
        return $this->getLocalConnectionsCount($appId, $channelName);
249
    }
250
251
    /**
252
     * Broadcast the message across multiple servers.
253
     *
254
     * @param  string|int  $appId
255
     * @param  string  $channel
256
     * @param  stdClass  $payload
257
     * @return bool
258
     */
259
    public function broadcastAcrossServers($appId, string $channel, stdClass $payload)
260
    {
261
        return true;
262
    }
263
264
    /**
265
     * Handle the user when it joined a presence channel.
266
     *
267
     * @param  \Ratchet\ConnectionInterface  $connection
268
     * @param  stdClass  $user
269
     * @param  string  $channel
270
     * @param  stdClass  $payload
271
     * @return void
272
     */
273
    public function userJoinedPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel, stdClass $payload)
274
    {
275
        $this->users["{$connection->app->id}:{$channel}"][$connection->socketId] = json_encode($user);
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
276
    }
277
278
    /**
279
     * Handle the user when it left a presence channel.
280
     *
281
     * @param  \Ratchet\ConnectionInterface  $connection
282
     * @param  stdClass  $user
283
     * @param  string  $channel
284
     * @param  stdClass  $payload
0 ignored issues
show
Bug introduced by
There is no parameter named $payload. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
285
     * @return void
286
     */
287
    public function userLeftPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel)
288
    {
289
        unset($this->users["{$connection->app->id}:{$channel}"][$connection->socketId]);
290
    }
291
292
    /**
293
     * Get the presence channel members.
294
     *
295
     * @param  string|int  $appId
296
     * @param  string  $channel
297
     * @return \React\Promise\PromiseInterface
298
     */
299
    public function getChannelMembers($appId, string $channel): PromiseInterface
300
    {
301
        $members = $this->users["{$appId}:{$channel}"] ?? [];
302
303
        $members = collect($members)->map(function ($user) {
304
            return json_decode($user);
305
        })->toArray();
306
307
        return new FulfilledPromise($members);
0 ignored issues
show
Deprecated Code introduced by
The class React\Promise\FulfilledPromise has been deprecated with message: 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
308
    }
309
310
    /**
311
     * Get a member from a presence channel based on connection.
312
     *
313
     * @param  \Ratchet\ConnectionInterface  $connection
314
     * @param  string  $channel
315
     * @return \React\Promise\PromiseInterface
316
     */
317
    public function getChannelMember(ConnectionInterface $connection, string $channel): PromiseInterface
318
    {
319
        $member = $this->users["{$connection->app->id}:{$channel}"][$connection->socketId] ?? null;
0 ignored issues
show
Bug introduced by
Accessing app on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing socketId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
320
321
        return new FulfilledPromise($member);
0 ignored issues
show
Deprecated Code introduced by
The class React\Promise\FulfilledPromise has been deprecated with message: 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
322
    }
323
324
    /**
325
     * Get the presence channels total members count.
326
     *
327
     * @param  string|int  $appId
328
     * @param  array  $channelNames
329
     * @return \React\Promise\PromiseInterface
330
     */
331
    public function getChannelsMembersCount($appId, array $channelNames): PromiseInterface
332
    {
333
        $results = collect($channelNames)
334
            ->reduce(function ($results, $channel) use ($appId) {
335
                $results[$channel] = isset($this->users["{$appId}:{$channel}"])
336
                    ? count($this->users["{$appId}:{$channel}"])
337
                    : 0;
338
339
                return $results;
340
            }, []);
341
342
        return new FulfilledPromise($results);
0 ignored issues
show
Deprecated Code introduced by
The class React\Promise\FulfilledPromise has been deprecated with message: 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
343
    }
344
345
    /**
346
     * Keep tracking the connections availability when they pong.
347
     *
348
     * @param  \Ratchet\ConnectionInterface  $connection
349
     * @return bool
350
     */
351
    public function connectionPonged(ConnectionInterface $connection): bool
352
    {
353
        return true;
354
    }
355
356
    /**
357
     * Remove the obsolete connections that didn't ponged in a while.
358
     *
359
     * @return bool
360
     */
361
    public function removeObsoleteConnections(): bool
362
    {
363
        return true;
364
    }
365
366
    /**
367
     * Mark the current instance as unable to accept new connections.
368
     *
369
     * @return $this
370
     */
371
    public function declineNewConnections()
372
    {
373
        $this->acceptsNewConnections = false;
374
375
        return $this;
376
    }
377
378
    /**
379
     * Check if the current server instance
380
     * accepts new connections.
381
     *
382
     * @return bool
383
     */
384
    public function acceptsNewConnections(): bool
385
    {
386
        return $this->acceptsNewConnections;
387
    }
388
389
    /**
390
     * Get the channel class by the channel name.
391
     *
392
     * @param  string  $channelName
393
     * @return string
394
     */
395
    protected function getChannelClassName(string $channelName): string
396
    {
397
        if (Str::startsWith($channelName, 'private-')) {
398
            return PrivateChannel::class;
399
        }
400
401
        if (Str::startsWith($channelName, 'presence-')) {
402
            return PresenceChannel::class;
403
        }
404
405
        return Channel::class;
406
    }
407
}
408