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

RedisChannelManager   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 531
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 5
dl 0
loc 531
rs 8.96
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 2
A getLocalChannels() 0 4 1
A getGlobalChannels() 0 6 1
A unsubscribeFromAllChannels() 0 13 2
A subscribeToChannel() 0 20 2
A unsubscribeFromChannel() 0 32 3
A subscribeToApp() 0 6 1
A unsubscribeFromApp() 0 6 1
A getLocalConnectionsCount() 0 4 1
A getGlobalConnectionsCount() 0 8 2
A broadcastAcrossServers() 0 9 1
A userJoinedPresenceChannel() 0 6 1
A userLeftPresenceChannel() 0 6 1
A getChannelMembers() 0 16 1
A getChannelMember() 0 6 1
A getChannelsMembersCount() 0 16 2
A onMessage() 0 24 4
A getConnectionUri() 0 22 5
A getSubscribeClient() 0 4 1
A getPublishClient() 0 4 1
A getServerId() 0 4 1
A incrementSubscriptionsCount() 0 6 1
A storeUserData() 0 6 1
A removeUserData() 0 6 1
A subscribeToTopic() 0 6 1
A unsubscribeFromTopic() 0 6 1
A getRedisKey() 0 18 3

How to fix   Complexity   

Complex Class

Complex classes like RedisChannelManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RedisChannelManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\ChannelManagers;
4
5
use BeyondCode\LaravelWebSockets\Channels\Channel;
6
use Clue\React\Redis\Client;
7
use Clue\React\Redis\Factory;
8
use Illuminate\Support\Str;
9
use Ratchet\ConnectionInterface;
10
use React\EventLoop\LoopInterface;
11
use React\Promise\PromiseInterface;
12
use stdClass;
13
14
class RedisChannelManager extends LocalChannelManager
15
{
16
    /**
17
     * The running loop.
18
     *
19
     * @var LoopInterface
20
     */
21
    protected $loop;
22
23
    /**
24
     * The unique server identifier.
25
     *
26
     * @var string
27
     */
28
    protected $serverId;
29
30
    /**
31
     * The pub client.
32
     *
33
     * @var Client
34
     */
35
    protected $publishClient;
36
37
    /**
38
     * The sub client.
39
     *
40
     * @var Client
41
     */
42
    protected $subscribeClient;
43
44
    /**
45
     * Create a new channel manager instance.
46
     *
47
     * @param  LoopInterface  $loop
48
     * @param  string|null  $factoryClass
49
     * @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...
50
     */
51
    public function __construct(LoopInterface $loop, $factoryClass = null)
52
    {
53
        $this->loop = $loop;
54
55
        $connectionUri = $this->getConnectionUri();
56
57
        $factoryClass = $factoryClass ?: Factory::class;
58
        $factory = new $factoryClass($this->loop);
59
60
        $this->publishClient = $factory->createLazyClient($connectionUri);
61
        $this->subscribeClient = $factory->createLazyClient($connectionUri);
62
63
        $this->subscribeClient->on('message', function ($channel, $payload) {
64
            $this->onMessage($channel, $payload);
65
        });
66
67
        $this->serverId = Str::uuid()->toString();
68
    }
69
70
    /**
71
     * Get all channels for a specific app
72
     * for the current instance.
73
     *
74
     * @param  string|int  $appId
75
     * @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...
76
     */
77
    public function getLocalChannels($appId): PromiseInterface
78
    {
79
        return parent::getLocalChannels($appId);
80
    }
81
82
    /**
83
     * Get all channels for a specific app
84
     * across multiple servers.
85
     *
86
     * @param  string|int  $appId
87
     * @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...
88
     */
89
    public function getGlobalChannels($appId): PromiseInterface
90
    {
91
        return $this->getPublishClient()->smembers(
92
            $this->getRedisKey($appId, null, ['channels'])
93
        );
94
    }
95
96
    /**
97
     * Remove connection from all channels.
98
     *
99
     * @param  \Ratchet\ConnectionInterface  $connection
100
     * @return void
101
     */
102
    public function unsubscribeFromAllChannels(ConnectionInterface $connection)
103
    {
104
        $this->getGlobalChannels($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...
105
            ->then(function ($channels) use ($connection) {
106
                foreach ($channels as $channel) {
107
                    $this->unsubscribeFromChannel(
108
                        $connection, $channel, new stdClass
109
                    );
110
                }
111
            });
112
113
        parent::unsubscribeFromAllChannels($connection);
114
    }
115
116
    /**
117
     * Subscribe the connection to a specific channel.
118
     *
119
     * @param  \Ratchet\ConnectionInterface  $connection
120
     * @param  string  $channelName
121
     * @param  stdClass  $payload
122
     * @return void
123
     */
124
    public function subscribeToChannel(ConnectionInterface $connection, string $channelName, stdClass $payload)
125
    {
126
        $this->getGlobalConnectionsCount($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...
127
            ->then(function ($count) use ($connection, $channelName) {
128
                if ($count === 0) {
129
                    $this->subscribeToTopic($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...
130
                }
131
            });
132
133
        $this->getPublishClient()->sadd(
134
            $this->getRedisKey($connection->app->id, null, ['channels']),
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...
135
            $channelName
136
        );
137
138
        $this->incrementSubscriptionsCount(
139
            $connection->app->id, $channelName, 1
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...
140
        );
141
142
        parent::subscribeToChannel($connection, $channelName, $payload);
143
    }
144
145
    /**
146
     * Unsubscribe the connection from the channel.
147
     *
148
     * @param  \Ratchet\ConnectionInterface  $connection
149
     * @param  string  $channelName
150
     * @param  stdClass  $payload
151
     * @return void
152
     */
153
    public function unsubscribeFromChannel(ConnectionInterface $connection, string $channelName, stdClass $payload)
154
    {
155
        $this->getGlobalConnectionsCount($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...
156
            ->then(function ($count) use ($connection, $channelName) {
157
                if ($count === 0) {
158
                    $this->unsubscribeFromTopic($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...
159
160
                    $this->getPublishClient()->srem(
161
                        $this->getRedisKey($connection->app->id, null, ['channels']),
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...
162
                        $channelName
163
                    );
164
165
                    return;
166
                }
167
168
                $increment = $this->incrementSubscriptionsCount(
0 ignored issues
show
Unused Code introduced by
$increment is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
169
                    $connection->app->id, $channelName, -1
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...
170
                )
171
                ->then(function ($count) use ($connection, $channelName) {
172
                    if ($count < 1) {
173
                        $this->unsubscribeFromTopic($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...
174
175
                        $this->getPublishClient()->srem(
176
                            $this->getRedisKey($connection->app->id, null, ['channels']),
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...
177
                            $channelName
178
                        );
179
                    }
180
                });
181
            });
182
183
        parent::unsubscribeFromChannel($connection, $channelName, $payload);
184
    }
185
186
    /**
187
     * Subscribe the connection to a specific channel.
188
     *
189
     * @param  string|int  $appId
190
     * @return void
191
     */
192
    public function subscribeToApp($appId)
193
    {
194
        $this->subscribeToTopic($appId);
195
196
        $this->incrementSubscriptionsCount($appId);
197
    }
198
199
    /**
200
     * Unsubscribe the connection from the channel.
201
     *
202
     * @param  string|int  $appId
203
     * @return void
204
     */
205
    public function unsubscribeFromApp($appId)
206
    {
207
        $this->unsubscribeFromTopic($appId);
208
209
        $this->incrementSubscriptionsCount($appId, null, -1);
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 parent::getLocalConnectionsCount($appId, $channelName);
223
    }
224
225
    /**
226
     * Get the connections count
227
     * across multiple servers.
228
     *
229
     * @param  string|int  $appId
230
     * @param  string|null  $channelName
231
     * @return \React\Promise\PromiseInterface
232
     */
233
    public function getGlobalConnectionsCount($appId, string $channelName = null): PromiseInterface
234
    {
235
        return $this->publishClient
236
            ->hget($this->getRedisKey($appId, $channelName, ['stats']), 'connections')
237
            ->then(function ($count) {
238
                return is_null($count) ? 0 : (int) $count;
239
            });
240
    }
241
242
    /**
243
     * Broadcast the message across multiple servers.
244
     *
245
     * @param  string|int  $appId
246
     * @param  string  $channel
247
     * @param  stdClass  $payload
248
     * @return bool
249
     */
250
    public function broadcastAcrossServers($appId, string $channel, stdClass $payload)
251
    {
252
        $payload->appId = $appId;
253
        $payload->serverId = $this->getServerId();
254
255
        $this->publishClient->publish($this->getRedisKey($appId, $channel), json_encode($payload));
256
257
        return true;
258
    }
259
260
    /**
261
     * Handle the user when it joined a presence channel.
262
     *
263
     * @param  \Ratchet\ConnectionInterface  $connection
264
     * @param  stdClass  $user
265
     * @param  string  $channel
266
     * @param  stdClass  $payload
267
     * @return void
268
     */
269
    public function userJoinedPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel, stdClass $payload)
270
    {
271
        $this->storeUserData(
272
            $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...
273
        );
274
    }
275
276
    /**
277
     * Handle the user when it left a presence channel.
278
     *
279
     * @param  \Ratchet\ConnectionInterface  $connection
280
     * @param  stdClass  $user
281
     * @param  string  $channel
282
     * @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...
283
     * @return void
284
     */
285
    public function userLeftPresenceChannel(ConnectionInterface $connection, stdClass $user, string $channel)
286
    {
287
        $this->removeUserData(
288
            $connection->app->id, $channel, $connection->socketId
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...
289
        );
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
        return $this->publishClient
302
            ->hgetall($this->getRedisKey($appId, $channel, ['users']))
303
            ->then(function ($members) {
304
                [$keys, $values] = collect($members)->partition(function ($value, $key) {
0 ignored issues
show
Bug introduced by
The variable $keys does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $values does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
305
                    return $key % 2 === 0;
306
                });
307
308
                return collect(array_combine($keys->all(), $values->all()))
309
                    ->map(function ($user) {
310
                        return json_decode($user);
311
                    })
312
                    ->toArray();
313
            });
314
    }
315
316
    /**
317
     * Get a member from a presence channel based on connection.
318
     *
319
     * @param  \Ratchet\ConnectionInterface  $connection
320
     * @param  string  $channel
321
     * @return \React\Promise\PromiseInterface
322
     */
323
    public function getChannelMember(ConnectionInterface $connection, string $channel): PromiseInterface
324
    {
325
        return $this->publishClient->hget(
326
            $this->getRedisKey($connection->app->id, $channel, ['users']), $connection->socketId
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...
327
        );
328
    }
329
330
    /**
331
     * Get the presence channels total members count.
332
     *
333
     * @param  string|int  $appId
334
     * @param  array  $channelNames
335
     * @return \React\Promise\PromiseInterface
336
     */
337
    public function getChannelsMembersCount($appId, array $channelNames): PromiseInterface
338
    {
339
        $this->publishClient->multi();
340
341
        foreach ($channelNames as $channel) {
342
            $this->publishClient->hlen(
343
                $this->getRedisKey($appId, $channel, ['users'])
344
            );
345
        }
346
347
        return $this->publishClient
348
            ->exec()
349
            ->then(function ($data) use ($channelNames) {
350
                return array_combine($channelNames, $data);
351
            });
352
    }
353
354
    /**
355
     * Handle a message received from Redis on a specific channel.
356
     *
357
     * @param  string  $redisChannel
358
     * @param  string  $payload
359
     * @return void
360
     */
361
    public function onMessage(string $redisChannel, string $payload)
362
    {
363
        $payload = json_decode($payload);
364
365
        if (isset($payload->serverId) && $this->getServerId() === $payload->serverId) {
366
            return;
367
        }
368
369
        $payload->channel = Str::after($redisChannel, "{$payload->appId}:");
370
371
        if (! $channel = $this->find($payload->appId, $payload->channel)) {
372
            return;
373
        }
374
375
        $appId = $payload->appId ?? null;
376
        $socketId = $payload->socketId ?? null;
377
        $serverId = $payload->serverId ?? null;
0 ignored issues
show
Unused Code introduced by
$serverId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
378
379
        unset($payload->socketId);
380
        unset($payload->serverId);
381
        unset($payload->appId);
382
383
        $channel->broadcastToEveryoneExcept($payload, $socketId, $appId, false);
384
    }
385
386
    /**
387
     * Build the Redis connection URL from Laravel database config.
388
     *
389
     * @return string
390
     */
391
    protected function getConnectionUri()
392
    {
393
        $name = config('websockets.replication.redis.connection', 'default');
394
        $config = config("database.redis.{$name}");
395
396
        $host = $config['host'];
397
        $port = $config['port'] ?: 6379;
398
399
        $query = [];
400
401
        if ($config['password']) {
402
            $query['password'] = $config['password'];
403
        }
404
405
        if ($config['database']) {
406
            $query['database'] = $config['database'];
407
        }
408
409
        $query = http_build_query($query);
410
411
        return "redis://{$host}:{$port}".($query ? "?{$query}" : '');
412
    }
413
414
    /**
415
     * Get the Subscribe client instance.
416
     *
417
     * @return Client
418
     */
419
    public function getSubscribeClient()
420
    {
421
        return $this->subscribeClient;
422
    }
423
424
    /**
425
     * Get the Publish client instance.
426
     *
427
     * @return Client
428
     */
429
    public function getPublishClient()
430
    {
431
        return $this->publishClient;
432
    }
433
434
    /**
435
     * Get the unique identifier for the server.
436
     *
437
     * @return string
438
     */
439
    public function getServerId()
440
    {
441
        return $this->serverId;
442
    }
443
444
    /**
445
     * Increment the subscribed count number.
446
     *
447
     * @param  string|int  $appId
448
     * @param  string|null  $channel
449
     * @param  int  $increment
450
     * @return PromiseInterface
451
     */
452
    public function incrementSubscriptionsCount($appId, string $channel = null, int $increment = 1)
453
    {
454
        return $this->publishClient->hincrby(
455
            $this->getRedisKey($appId, $channel, ['stats']), 'connections', $increment
456
        );
457
    }
458
459
    /**
460
     * Set data for a topic. Might be used for the presence channels.
461
     *
462
     * @param  string|int  $appId
463
     * @param  string|null  $channel
464
     * @param  string  $key
465
     * @param  mixed  $data
466
     * @return PromiseInterface
467
     */
468
    public function storeUserData($appId, string $channel = null, string $key, $data)
469
    {
470
        $this->publishClient->hset(
471
            $this->getRedisKey($appId, $channel, ['users']), $key, $data
472
        );
473
    }
474
475
    /**
476
     * Remove data for a topic. Might be used for the presence channels.
477
     *
478
     * @param  string|int  $appId
479
     * @param  string|null  $channel
480
     * @param  string  $key
481
     * @return PromiseInterface
482
     */
483
    public function removeUserData($appId, string $channel = null, string $key)
484
    {
485
        return $this->publishClient->hdel(
486
            $this->getRedisKey($appId, $channel), $key
487
        );
488
    }
489
490
    /**
491
     * Subscribe to the topic for the app, or app and channel.
492
     *
493
     * @param  string|int  $appId
494
     * @param  string|null  $channel
495
     * @return void
496
     */
497
    public function subscribeToTopic($appId, string $channel = null)
498
    {
499
        $this->subscribeClient->subscribe(
500
            $this->getRedisKey($appId, $channel)
501
        );
502
    }
503
504
    /**
505
     * Unsubscribe from the topic for the app, or app and channel.
506
     *
507
     * @param  string|int  $appId
508
     * @param  string|null  $channel
509
     * @return void
510
     */
511
    public function unsubscribeFromTopic($appId, string $channel = null)
512
    {
513
        $this->subscribeClient->unsubscribe(
514
            $this->getRedisKey($appId, $channel)
515
        );
516
    }
517
518
    /**
519
     * Get the Redis Keyspace name to handle subscriptions
520
     * and other key-value sets.
521
     *
522
     * @param  mixed  $appId
523
     * @param  string|null  $channel
524
     * @return string
525
     */
526
    public function getRedisKey($appId, string $channel = null, array $suffixes = []): string
527
    {
528
        $prefix = config('database.redis.options.prefix', null);
529
530
        $hash = "{$prefix}{$appId}";
531
532
        if ($channel) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $channel of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
533
            $hash .= ":{$channel}";
534
        }
535
536
        $suffixes = join(':', $suffixes);
537
538
        if ($suffixes) {
539
            $hash .= $suffixes;
540
        }
541
542
        return $hash;
543
    }
544
}
545