Completed
Pull Request — master (#447)
by Marcel
03:21 queued 01:37
created

RedisStatisticsLogger::createRecord()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\Statistics\Logger;
4
5
use BeyondCode\LaravelWebSockets\Apps\App;
6
use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver;
7
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
8
use Illuminate\Cache\RedisLock;
9
use Illuminate\Support\Facades\Cache;
10
11
class RedisStatisticsLogger implements StatisticsLogger
12
{
13
    /**
14
     * The Channel manager.
15
     *
16
     * @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager
17
     */
18
    protected $channelManager;
19
20
    /**
21
     * The statistics driver instance.
22
     *
23
     * @var \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver
24
     */
25
    protected $driver;
26
27
    /**
28
     * The Redis manager instance.
29
     *
30
     * @var \Illuminate\Redis\RedisManager
31
     */
32
    protected $redis;
33
34
    /**
35
     * Initialize the logger.
36
     *
37
     * @param  \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager  $channelManager
38
     * @param  \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver  $driver
39
     * @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...
40
     */
41
    public function __construct(ChannelManager $channelManager, StatisticsDriver $driver)
42
    {
43
        $this->channelManager = $channelManager;
44
        $this->driver = $driver;
45
        $this->redis = Cache::getRedis();
46
    }
47
48
    /**
49
     * Handle the incoming websocket message.
50
     *
51
     * @param  mixed  $appId
52
     * @return void
53
     */
54
    public function webSocketMessage($appId)
55
    {
56
        $this->ensureAppIsSet($appId)
57
            ->hincrby($this->getHash($appId), 'websocket_message_count', 1);
58
    }
59
60
    /**
61
     * Handle the incoming API message.
62
     *
63
     * @param  mixed  $appId
64
     * @return void
65
     */
66
    public function apiMessage($appId)
67
    {
68
        $this->ensureAppIsSet($appId)
69
            ->hincrby($this->getHash($appId), 'api_message_count', 1);
70
    }
71
72
    /**
73
     * Handle the new conection.
74
     *
75
     * @param  mixed  $appId
76
     * @return void
77
     */
78 View Code Duplication
    public function connection($appId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
79
    {
80
        $currentConnectionCount = $this->ensureAppIsSet($appId)
81
            ->hincrby($this->getHash($appId), 'current_connection_count', 1);
82
83
        $currentPeakConnectionCount = $this->redis->hget($this->getHash($appId), 'peak_connection_count');
84
85
        $peakConnectionCount = is_null($currentPeakConnectionCount)
86
            ? $currentConnectionCount
87
            : max($currentPeakConnectionCount, $currentConnectionCount);
88
89
        $this->redis->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
90
    }
91
92
    /**
93
     * Handle disconnections.
94
     *
95
     * @param  mixed  $appId
96
     * @return void
97
     */
98 View Code Duplication
    public function disconnection($appId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99
    {
100
        $currentConnectionCount = $this->ensureAppIsSet($appId)
101
            ->hincrby($this->getHash($appId), 'current_connection_count', -1);
102
103
        $currentPeakConnectionCount = $this->redis->hget($this->getHash($appId), 'peak_connection_count');
104
105
        $peakConnectionCount = is_null($currentPeakConnectionCount)
106
            ? $currentConnectionCount
107
            : max($currentPeakConnectionCount, $currentConnectionCount);
108
109
        $this->redis->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
110
    }
111
112
    /**
113
     * Save all the stored statistics.
114
     *
115
     * @return void
116
     */
117
    public function save()
118
    {
119
        $this->lock()->get(function () {
120
            foreach ($this->redis->smembers('laravel-websockets:apps') as $appId) {
121
                if (! $statistic = $this->redis->hgetall($this->getHash($appId))) {
122
                    continue;
123
                }
124
125
                $this->createRecord($statistic, $appId);
126
127
                $currentConnectionCount = $this->channelManager->getConnectionCount($appId);
128
129
                $currentConnectionCount === 0
130
                    ? $this->resetAppTraces($appId)
131
                    : $this->resetStatistics($appId, $currentConnectionCount);
132
            }
133
        });
134
    }
135
136
    /**
137
     * Ensure the app id is stored in the Redis database.
138
     *
139
     * @param  mixed  $appId
140
     * @return \Illuminate\Redis\RedisManager
141
     */
142
    protected function ensureAppIsSet($appId)
143
    {
144
        $this->redis->sadd('laravel-websockets:apps', $appId);
145
146
        return $this->redis;
147
    }
148
149
    /**
150
     * Reset the statistics to a specific connection count.
151
     *
152
     * @param  mixed  $appId
153
     * @param  int  $currentConnectionCount
154
     * @return void
155
     */
156 View Code Duplication
    public function resetStatistics($appId, int $currentConnectionCount)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
157
    {
158
        $this->redis->hset($this->getHash($appId), 'current_connection_count', $currentConnectionCount);
159
        $this->redis->hset($this->getHash($appId), 'peak_connection_count', $currentConnectionCount);
160
        $this->redis->hset($this->getHash($appId), 'websocket_message_count', 0);
161
        $this->redis->hset($this->getHash($appId), 'api_message_count', 0);
162
    }
163
164
    /**
165
     * Remove all app traces from the database if no connections have been set
166
     * in the meanwhile since last save.
167
     *
168
     * @param  mixed  $appId
169
     * @return void
170
     */
171 View Code Duplication
    public function resetAppTraces($appId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
172
    {
173
        $this->redis->hdel($this->getHash($appId), 'current_connection_count');
174
        $this->redis->hdel($this->getHash($appId), 'peak_connection_count');
175
        $this->redis->hdel($this->getHash($appId), 'websocket_message_count');
176
        $this->redis->hdel($this->getHash($appId), 'api_message_count');
177
178
        $this->redis->srem('laravel-websockets:apps', $appId);
179
    }
180
181
    /**
182
     * Get the Redis hash name for the app.
183
     *
184
     * @param  mixed  $appId
185
     * @return string
186
     */
187
    protected function getHash($appId): string
188
    {
189
        return "laravel-websockets:app:{$appId}";
190
    }
191
192
    /**
193
     * Get a new RedisLock instance to avoid race conditions.
194
     *
195
     * @return \Illuminate\Cache\CacheLock
196
     */
197
    protected function lock()
198
    {
199
        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...
200
    }
201
202
    /**
203
     * Create a new record using the Statistic Driver.
204
     *
205
     * @param  array  $statistic
206
     * @param  mixed  $appId
207
     * @return void
208
     */
209
    protected function createRecord(array $statistic, $appId): void
210
    {
211
        $this->driver::create([
212
            'app_id' => $appId,
213
            'peak_connection_count' => $statistic['peak_connection_count'] ?? 0,
214
            'websocket_message_count' => $statistic['websocket_message_count'] ?? 0,
215
            'api_message_count' => $statistic['api_message_count'] ?? 0,
216
        ]);
217
    }
218
}
219