Completed
Pull Request — master (#519)
by Alexandru
14:48
created

LocalChannelManager   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
wmc 25
lcom 2
cbo 5
dl 0
loc 319
rs 10
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A find() 0 4 1
A findOrCreate() 0 10 2
A getLocalChannels() 0 6 1
A getGlobalChannels() 0 4 1
A unsubscribeFromAllChannels() 0 24 3
A subscribeToChannel() 0 6 1
A unsubscribeFromChannel() 0 6 1
A subscribeToApp() 0 4 1
A unsubscribeFromApp() 0 4 1
A getLocalConnectionsCount() 0 17 1
A getGlobalConnectionsCount() 0 4 1
A broadcastAcrossServers() 0 4 1
A userJoinedPresenceChannel() 0 4 1
A userLeftPresenceChannel() 0 4 1
A getChannelMembers() 0 10 1
A getChannelMember() 0 6 1
A getChannelsMembersCount() 0 13 2
A getChannelClassName() 0 12 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
     * Create a new channel manager instance.
34
     *
35
     * @param  LoopInterface  $loop
36
     * @param  string|null  $factoryClass
37
     * @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...
38
     */
39
    public function __construct(LoopInterface $loop, $factoryClass = null)
40
    {
41
        //
42
    }
43
44
    /**
45
     * Find the channel by app & name.
46
     *
47
     * @param  string|int  $appId
48
     * @param  string  $channel
49
     * @return null|BeyondCode\LaravelWebSockets\Channels\Channel
50
     */
51
    public function find($appId, string $channel)
52
    {
53
        return $this->channels[$appId][$channel] ?? null;
54
    }
55
56
    /**
57
     * Find a channel by app & name or create one.
58
     *
59
     * @param  string|int  $appId
60
     * @param  string  $channel
61
     * @return BeyondCode\LaravelWebSockets\Channels\Channel
62
     */
63
    public function findOrCreate($appId, string $channel)
64
    {
65
        if (! $channelInstance = $this->find($appId, $channel)) {
66
            $class = $this->getChannelClassName($channel);
67
68
            $this->channels[$appId][$channel] = new $class($channel);
69
        }
70
71
        return $this->channels[$appId][$channel];
72
    }
73
74
    /**
75
     * Get all channels for a specific app
76
     * for the current instance.
77
     *
78
     * @param  string|int  $appId
79
     * @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...
80
     */
81
    public function getLocalChannels($appId): PromiseInterface
82
    {
83
        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...
84
            $this->channels[$appId] ?? []
85
        );
86
    }
87
88
    /**
89
     * Get all channels for a specific app
90
     * across multiple servers.
91
     *
92
     * @param  string|int  $appId
93
     * @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...
94
     */
95
    public function getGlobalChannels($appId): PromiseInterface
96
    {
97
        return $this->getLocalChannels($appId);
98
    }
99
100
    /**
101
     * Remove connection from all channels.
102
     *
103
     * @param  \Ratchet\ConnectionInterface  $connection
104
     * @return void
105
     */
106
    public function unsubscribeFromAllChannels(ConnectionInterface $connection)
107
    {
108
        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...
109
            return;
110
        }
111
112
        $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...
113
            ->then(function ($channels) use ($connection) {
114
                collect($channels)->each->unsubscribe($connection);
115
116
                collect($channels)
117
                    ->reject->hasConnections()
118
                    ->each(function (Channel $channel, string $channelName) use ($connection) {
119
                        unset($this->channels[$connection->app->id][$channelName]);
120
                    });
121
            });
122
123
        $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...
124
            ->then(function ($channels) use ($connection) {
125
                if (count($channels) === 0) {
126
                    unset($this->channels[$connection->app->id]);
127
                }
128
            });
129
    }
130
131
    /**
132
     * Subscribe the connection to a specific channel.
133
     *
134
     * @param  \Ratchet\ConnectionInterface  $connection
135
     * @param  string  $channelName
136
     * @param  stdClass  $payload
137
     * @return void
138
     */
139
    public function subscribeToChannel(ConnectionInterface $connection, string $channelName, stdClass $payload)
140
    {
141
        $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...
142
143
        $channel->subscribe($connection, $payload);
144
    }
145
146
    /**
147
     * Unsubscribe the connection from the channel.
148
     *
149
     * @param  \Ratchet\ConnectionInterface  $connection
150
     * @param  string  $channelName
151
     * @param  stdClass  $payload
152
     * @return void
153
     */
154
    public function unsubscribeFromChannel(ConnectionInterface $connection, string $channelName, stdClass $payload)
155
    {
156
        $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...
157
158
        $channel->unsubscribe($connection, $payload);
159
    }
160
161
    /**
162
     * Subscribe the connection to a specific channel.
163
     *
164
     * @param  string|int  $appId
165
     * @return void
166
     */
167
    public function subscribeToApp($appId)
168
    {
169
        //
170
    }
171
172
    /**
173
     * Unsubscribe the connection from the channel.
174
     *
175
     * @param  string|int  $appId
176
     * @return void
177
     */
178
    public function unsubscribeFromApp($appId)
179
    {
180
        //
181
    }
182
183
    /**
184
     * Get the connections count on the app
185
     * for the current server instance.
186
     *
187
     * @param  string|int  $appId
188
     * @param  string|null  $channelName
189
     * @return \React\Promise\PromiseInterface
190
     */
191
    public function getLocalConnectionsCount($appId, string $channelName = null): PromiseInterface
192
    {
193
        return $this->getLocalChannels($appId)
194
            ->then(function ($channels) use ($channelName) {
195
                return collect($channels)
196
                    ->when(! is_null($channelName), function ($collection) use ($channelName) {
197
                        return $collection->filter(function (Channel $channel) use ($channelName) {
198
                            return $channel->getName() === $channelName;
199
                        });
200
                    })
201
                    ->flatMap(function (Channel $channel) {
202
                        return collect($channel->getConnections())->pluck('socketId');
203
                    })
204
                    ->unique()
205
                    ->count();
206
            });
207
    }
208
209
    /**
210
     * Get the connections count
211
     * across multiple servers.
212
     *
213
     * @param  string|int  $appId
214
     * @param  string|null  $channelName
215
     * @return \React\Promise\PromiseInterface
216
     */
217
    public function getGlobalConnectionsCount($appId, string $channelName = null): PromiseInterface
218
    {
219
        return $this->getLocalConnectionsCount($appId, $channelName);
220
    }
221
222
    /**
223
     * Broadcast the message across multiple servers.
224
     *
225
     * @param  string|int  $appId
226
     * @param  string  $channel
227
     * @param  stdClass  $payload
228
     * @return bool
229
     */
230
    public function broadcastAcrossServers($appId, string $channel, stdClass $payload)
231
    {
232
        return true;
233
    }
234
235
    /**
236
     * Handle the user when it joined a presence channel.
237
     *
238
     * @param  \Ratchet\ConnectionInterface  $connection
239
     * @param  stdClass  $user
240
     * @param  string  $channel
241
     * @param  stdClass  $payload
242
     * @return void
243
     */
244
    public function userJoinedPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel, stdClass $payload)
245
    {
246
        $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...
247
    }
248
249
    /**
250
     * Handle the user when it left a presence channel.
251
     *
252
     * @param  \Ratchet\ConnectionInterface  $connection
253
     * @param  stdClass  $user
254
     * @param  string  $channel
255
     * @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...
256
     * @return void
257
     */
258
    public function userLeftPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel)
259
    {
260
        unset($this->users["{$connection->app->id}:{$channel}"][$connection->socketId]);
261
    }
262
263
    /**
264
     * Get the presence channel members.
265
     *
266
     * @param  string|int  $appId
267
     * @param  string  $channel
268
     * @return \React\Promise\PromiseInterface
269
     */
270
    public function getChannelMembers($appId, string $channel): PromiseInterface
271
    {
272
        $members = $this->users["{$appId}:{$channel}"] ?? [];
273
274
        $members = collect($members)->map(function ($user) {
275
            return json_decode($user);
276
        })->toArray();
277
278
        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...
279
    }
280
281
    /**
282
     * Get a member from a presence channel based on connection.
283
     *
284
     * @param  \Ratchet\ConnectionInterface  $connection
285
     * @param  string  $channel
286
     * @return \React\Promise\PromiseInterface
287
     */
288
    public function getChannelMember(ConnectionInterface $connection, string $channel): PromiseInterface
289
    {
290
        $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...
291
292
        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...
293
    }
294
295
    /**
296
     * Get the presence channels total members count.
297
     *
298
     * @param  string|int  $appId
299
     * @param  array  $channelNames
300
     * @return \React\Promise\PromiseInterface
301
     */
302
    public function getChannelsMembersCount($appId, array $channelNames): PromiseInterface
303
    {
304
        $results = collect($channelNames)
305
            ->reduce(function ($results, $channel) use ($appId) {
306
                $results[$channel] = isset($this->users["{$appId}:{$channel}"])
307
                    ? count($this->users["{$appId}:{$channel}"])
308
                    : 0;
309
310
                return $results;
311
            }, []);
312
313
        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...
314
    }
315
316
    /**
317
     * Get the channel class by the channel name.
318
     *
319
     * @param  string  $channelName
320
     * @return string
321
     */
322
    protected function getChannelClassName(string $channelName): string
323
    {
324
        if (Str::startsWith($channelName, 'private-')) {
325
            return PrivateChannel::class;
326
        }
327
328
        if (Str::startsWith($channelName, 'presence-')) {
329
            return PresenceChannel::class;
330
        }
331
332
        return Channel::class;
333
    }
334
}
335