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

RedisStatisticsLogger::apiMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\Statistics\Logger;
4
5
use BeyondCode\LaravelWebSockets\Apps\App;
6
use BeyondCode\LaravelWebSockets\PubSub\ReplicationInterface;
7
use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver;
8
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
9
use Illuminate\Cache\RedisLock;
10
use Illuminate\Support\Facades\Redis;
11
12
class RedisStatisticsLogger implements StatisticsLogger
13
{
14
    /**
15
     * The Channel manager.
16
     *
17
     * @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager
18
     */
19
    protected $channelManager;
20
21
    /**
22
     * The statistics driver instance.
23
     *
24
     * @var \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver
25
     */
26
    protected $driver;
27
28
    /**
29
     * The replicator client.
30
     *
31
     * @var ReplicationInterface
32
     */
33
    protected $replicator;
34
35
    /**
36
     * The Redis manager instance.
37
     *
38
     * @var \Illuminate\Redis\RedisManager
39
     */
40
    protected $redis;
41
42
    /**
43
     * Initialize the logger.
44
     *
45
     * @param  \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager  $channelManager
46
     * @param  \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver  $driver
47
     * @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...
48
     */
49
    public function __construct(ChannelManager $channelManager, StatisticsDriver $driver)
50
    {
51
        $this->channelManager = $channelManager;
52
        $this->driver = $driver;
53
        $this->replicator = app(ReplicationInterface::class);
54
55
        $this->redis = Redis::connection(
0 ignored issues
show
Documentation Bug introduced by
It seems like \Illuminate\Support\Faca...onnection', 'default')) of type object<Illuminate\Redis\Connections\Connection> is incompatible with the declared type object<Illuminate\Redis\RedisManager> of property $redis.

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

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

Loading history...
56
            config('websockets.replication.redis.connection', 'default')
57
        );
58
    }
59
60
    /**
61
     * Handle the incoming websocket message.
62
     *
63
     * @param  mixed  $appId
64
     * @return void
65
     */
66
    public function webSocketMessage($appId)
67
    {
68
        $this->ensureAppIsSet($appId)
69
            ->hincrby($this->getHash($appId), 'websocket_message_count', 1);
70
    }
71
72
    /**
73
     * Handle the incoming API message.
74
     *
75
     * @param  mixed  $appId
76
     * @return void
77
     */
78
    public function apiMessage($appId)
79
    {
80
        $this->ensureAppIsSet($appId)
81
            ->hincrby($this->getHash($appId), 'api_message_count', 1);
82
    }
83
84
    /**
85
     * Handle the new conection.
86
     *
87
     * @param  mixed  $appId
88
     * @return void
89
     */
90
    public function connection($appId)
91
    {
92
        // Increment the current connections count by 1.
93
        $incremented = $this->ensureAppIsSet($appId)
94
            ->hincrby($this->getHash($appId), 'current_connection_count', 1);
95
96
        $incremented->then(function ($currentConnectionCount) use ($appId) {
97
            // Get the peak connections count from Redis.
98
            $peakConnectionCount = $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
99
                ->getPublishClient()
100
                ->hget($this->getHash($appId), 'peak_connection_count');
101
102
            $peakConnectionCount->then(function ($currentPeakConnectionCount) use ($currentConnectionCount, $appId) {
103
                // Extract the greatest number between the current peak connection count
104
                // and the current connection number.
105
106
                $peakConnectionCount = is_null($currentPeakConnectionCount)
107
                    ? $currentConnectionCount
108
                    : max($currentPeakConnectionCount, $currentConnectionCount);
109
110
                // Then set it to the database.
111
                $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
112
                    ->getPublishClient()
113
                    ->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
114
            });
115
        });
116
    }
117
118
    /**
119
     * Handle disconnections.
120
     *
121
     * @param  mixed  $appId
122
     * @return void
123
     */
124
    public function disconnection($appId)
125
    {
126
        // Decrement the current connections count by 1.
127
        $decremented = $this->ensureAppIsSet($appId)
128
            ->hincrby($this->getHash($appId), 'current_connection_count', -1);
129
130
        $decremented->then(function ($currentConnectionCount) use ($appId) {
131
            // Get the peak connections count from Redis.
132
            $peakConnectionCount = $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
133
                ->getPublishClient()
134
                ->hget($this->getHash($appId), 'peak_connection_count');
135
136
            $peakConnectionCount->then(function ($currentPeakConnectionCount) use ($currentConnectionCount, $appId) {
137
                // Extract the greatest number between the current peak connection count
138
                // and the current connection number.
139
140
                $peakConnectionCount = is_null($currentPeakConnectionCount)
141
                    ? $currentConnectionCount
142
                    : max($currentPeakConnectionCount, $currentConnectionCount);
143
144
                // Then set it to the database.
145
                $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
146
                    ->getPublishClient()
147
                    ->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
148
            });
149
        });
150
    }
151
152
    /**
153
     * Save all the stored statistics.
154
     *
155
     * @return void
156
     */
157
    public function save()
158
    {
159
        $this->lock()->get(function () {
160
            $setMembers = $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
161
                ->getPublishClient()
162
                ->smembers('laravel-websockets:apps');
163
164
            $setMembers->then(function ($members) {
165
                foreach ($members as $appId) {
166
                    $member = $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
167
                        ->getPublishClient()
168
                        ->hgetall($this->getHash($appId));
169
170
                    $member->then(function ($statistic) use ($appId) {
171
                        if (! $statistic) {
172
                            return;
173
                        }
174
175
                        // Statistics come into a list where the keys are on even indexes
176
                        // and the values are on odd indexes. This way, we know which
177
                        // ones are keys and which ones are values and their get combined
178
                        // later to form the key => value array
179
180
                        [$keys, $values] = collect($statistic)->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...
181
                            return $key % 2 === 0;
182
                        });
183
184
                        $statistic = array_combine($keys->all(), $values->all());
185
186
                        $this->createRecord($statistic, $appId);
187
188
                        $this->channelManager
189
                            ->getGlobalConnectionsCount($appId)
190
                            ->then(function ($currentConnectionCount) use ($appId) {
191
                                $currentConnectionCount === 0 || is_null($currentConnectionCount)
192
                                    ? $this->resetAppTraces($appId)
193
                                    : $this->resetStatistics($appId, $currentConnectionCount);
194
                            });
195
                    });
196
                }
197
            });
198
        });
199
    }
200
201
    /**
202
     * Ensure the app id is stored in the Redis database.
203
     *
204
     * @param  mixed  $appId
205
     * @return \Illuminate\Redis\RedisManager
206
     */
207
    protected function ensureAppIsSet($appId)
208
    {
209
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
210
            ->getPublishClient()
211
            ->sadd('laravel-websockets:apps', $appId);
212
213
        return $this->replicator->getPublishClient();
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
214
    }
215
216
    /**
217
     * Reset the statistics to a specific connection count.
218
     *
219
     * @param  mixed  $appId
220
     * @param  int  $currentConnectionCount
221
     * @return void
222
     */
223
    public function resetStatistics($appId, int $currentConnectionCount)
224
    {
225
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
226
            ->getPublishClient()
227
            ->hset($this->getHash($appId), 'current_connection_count', $currentConnectionCount);
228
229
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
230
            ->getPublishClient()
231
            ->hset($this->getHash($appId), 'peak_connection_count', $currentConnectionCount);
232
233
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
234
            ->getPublishClient()
235
            ->hset($this->getHash($appId), 'websocket_message_count', 0);
236
237
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
238
            ->getPublishClient()
239
            ->hset($this->getHash($appId), 'api_message_count', 0);
240
    }
241
242
    /**
243
     * Remove all app traces from the database if no connections have been set
244
     * in the meanwhile since last save.
245
     *
246
     * @param  mixed  $appId
247
     * @return void
248
     */
249
    public function resetAppTraces($appId)
250
    {
251
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
252
            ->getPublishClient()
253
            ->hdel($this->getHash($appId), 'current_connection_count');
254
255
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
256
            ->getPublishClient()
257
            ->hdel($this->getHash($appId), 'peak_connection_count');
258
259
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
260
            ->getPublishClient()
261
            ->hdel($this->getHash($appId), 'websocket_message_count');
262
263
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
264
            ->getPublishClient()
265
            ->hdel($this->getHash($appId), 'api_message_count');
266
267
        $this->replicator
0 ignored issues
show
Bug introduced by
The method getPublishClient() does not exist on BeyondCode\LaravelWebSoc...ub\ReplicationInterface. Did you maybe mean publish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
268
            ->getPublishClient()
269
            ->srem('laravel-websockets:apps', $appId);
270
    }
271
272
    /**
273
     * Get the Redis hash name for the app.
274
     *
275
     * @param  mixed  $appId
276
     * @return string
277
     */
278
    protected function getHash($appId): string
279
    {
280
        return "laravel-websockets:app:{$appId}";
281
    }
282
283
    /**
284
     * Get a new RedisLock instance to avoid race conditions.
285
     *
286
     * @return \Illuminate\Cache\CacheLock
287
     */
288
    protected function lock()
289
    {
290
        return new RedisLock($this->redis, 'laravel-websockets:lock', 0);
0 ignored issues
show
Documentation introduced by
$this->redis is of type object<Illuminate\Redis\RedisManager>, but the function expects a object<Illuminate\Redis\Connections\Connection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
291
    }
292
293
    /**
294
     * Create a new record using the Statistic Driver.
295
     *
296
     * @param  array  $statistic
297
     * @param  mixed  $appId
298
     * @return void
299
     */
300
    protected function createRecord(array $statistic, $appId): void
301
    {
302
        $this->driver::create([
303
            'app_id' => $appId,
304
            'peak_connection_count' => $statistic['peak_connection_count'] ?? 0,
305
            'websocket_message_count' => $statistic['websocket_message_count'] ?? 0,
306
            'api_message_count' => $statistic['api_message_count'] ?? 0,
307
        ]);
308
    }
309
}
310