StartServer   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 79
c 2
b 0
f 0
dl 0
loc 304
rs 10
wmc 24

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getLastRestart() 0 4 1
A configureManagers() 0 9 1
A configureHttpLogger() 0 6 2
A triggerSoftShutdown() 0 17 2
A configureRoutes() 0 3 1
A handle() 0 17 1
A configurePongTracker() 0 6 1
A configurePcntlSignal() 0 21 2
A __construct() 0 5 1
A configureLoggers() 0 5 1
A configureConnectionLogger() 0 6 1
A configureMessageLogger() 0 6 2
A buildServer() 0 15 2
A configureStatistics() 0 9 3
A configureRestartTimer() 0 7 2
A startServer() 0 7 1
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\Console\Commands;
4
5
use BeyondCode\LaravelWebSockets\Contracts\ChannelManager;
6
use BeyondCode\LaravelWebSockets\Facades\StatisticsCollector as StatisticsCollectorFacade;
7
use BeyondCode\LaravelWebSockets\Facades\WebSocketRouter;
8
use BeyondCode\LaravelWebSockets\Server\Loggers\ConnectionLogger;
9
use BeyondCode\LaravelWebSockets\Server\Loggers\HttpLogger;
10
use BeyondCode\LaravelWebSockets\Server\Loggers\WebSocketsLogger;
11
use BeyondCode\LaravelWebSockets\ServerFactory;
12
use Illuminate\Console\Command;
13
use Illuminate\Support\Facades\Cache;
14
use React\EventLoop\Factory as LoopFactory;
15
16
class StartServer extends Command
17
{
18
    /**
19
     * The name and signature of the console command.
20
     *
21
     * @var string
22
     */
23
    protected $signature = 'websockets:serve
24
        {--host=0.0.0.0}
25
        {--port=6001}
26
        {--disable-statistics : Disable the statistics tracking.}
27
        {--statistics-interval= : The amount of seconds to tick between statistics saving.}
28
        {--debug : Forces the loggers to be enabled and thereby overriding the APP_DEBUG setting.}
29
        {--loop : Programatically inject the loop.}
30
    ';
31
32
    /**
33
     * The console command description.
34
     *
35
     * @var string|null
36
     */
37
    protected $description = 'Start the LaravelWebSockets server.';
38
39
    /**
40
     * Get the loop instance.
41
     *
42
     * @var \React\EventLoop\LoopInterface
43
     */
44
    protected $loop;
45
46
    /**
47
     * The Pusher server instance.
48
     *
49
     * @var \Ratchet\Server\IoServer
50
     */
51
    public $server;
52
53
    /**
54
     * Initialize the command.
55
     *
56
     * @return void
57
     */
58
    public function __construct()
59
    {
60
        parent::__construct();
61
62
        $this->loop = LoopFactory::create();
0 ignored issues
show
Deprecated Code introduced by
The function React\EventLoop\Factory::create() has been deprecated: 1.2.0 See Loop::get() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

62
        $this->loop = /** @scrutinizer ignore-deprecated */ LoopFactory::create();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

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

Loading history...
63
    }
64
65
    /**
66
     * Run the command.
67
     *
68
     * @return void
69
     */
70
    public function handle()
71
    {
72
        $this->configureLoggers();
73
74
        $this->configureManagers();
75
76
        $this->configureStatistics();
77
78
        $this->configureRestartTimer();
79
80
        $this->configureRoutes();
81
82
        $this->configurePcntlSignal();
83
84
        $this->configurePongTracker();
85
86
        $this->startServer();
87
    }
88
89
    /**
90
     * Configure the loggers used for the console.
91
     *
92
     * @return void
93
     */
94
    protected function configureLoggers()
95
    {
96
        $this->configureHttpLogger();
97
        $this->configureMessageLogger();
98
        $this->configureConnectionLogger();
99
    }
100
101
    /**
102
     * Register the managers that are not resolved
103
     * in the package service provider.
104
     *
105
     * @return void
106
     */
107
    protected function configureManagers()
108
    {
109
        $this->laravel->singleton(ChannelManager::class, function ($app) {
110
            $config = $app['config']['websockets'];
111
            $mode = $config['replication']['mode'] ?? 'local';
112
113
            $class = $config['replication']['modes'][$mode]['channel_manager'];
114
115
            return new $class($this->loop);
116
        });
117
    }
118
119
    /**
120
     * Register the Statistics Collectors that
121
     * are not resolved in the package service provider.
122
     *
123
     * @return void
124
     */
125
    protected function configureStatistics()
126
    {
127
        if (! $this->option('disable-statistics')) {
128
            $intervalInSeconds = $this->option('statistics-interval') ?: config('websockets.statistics.interval_in_seconds', 3600);
129
130
            $this->loop->addPeriodicTimer($intervalInSeconds, function () {
0 ignored issues
show
Bug introduced by
It seems like $intervalInSeconds can also be of type string; however, parameter $interval of React\EventLoop\LoopInterface::addPeriodicTimer() does only seem to accept double|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
            $this->loop->addPeriodicTimer(/** @scrutinizer ignore-type */ $intervalInSeconds, function () {
Loading history...
131
                $this->line('Saving statistics...');
132
133
                StatisticsCollectorFacade::save();
134
            });
135
        }
136
    }
137
138
    /**
139
     * Configure the restart timer.
140
     *
141
     * @return void
142
     */
143
    public function configureRestartTimer()
144
    {
145
        $this->lastRestart = $this->getLastRestart();
0 ignored issues
show
Bug Best Practice introduced by
The property lastRestart does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
146
147
        $this->loop->addPeriodicTimer(10, function () {
148
            if ($this->getLastRestart() !== $this->lastRestart) {
149
                $this->triggerSoftShutdown();
150
            }
151
        });
152
    }
153
154
    /**
155
     * Register the routes for the server.
156
     *
157
     * @return void
158
     */
159
    protected function configureRoutes()
160
    {
161
        WebSocketRouter::registerRoutes();
0 ignored issues
show
Bug introduced by
The method registerRoutes() does not exist on BeyondCode\LaravelWebSoc...Facades\WebSocketRouter. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
        WebSocketRouter::/** @scrutinizer ignore-call */ 
162
                         registerRoutes();
Loading history...
162
    }
163
164
    /**
165
     * Configure the PCNTL signals for soft shutdown.
166
     *
167
     * @return void
168
     */
169
    protected function configurePcntlSignal()
170
    {
171
        // When the process receives a SIGTERM or a SIGINT
172
        // signal, it should mark the server as unavailable
173
        // to receive new connections, close the current connections,
174
        // then stopping the loop.
175
176
        if (! extension_loaded('pcntl')) {
177
            return;
178
        }
179
180
        $this->loop->addSignal(SIGTERM, function () {
181
            $this->line('Closing existing connections...');
182
183
            $this->triggerSoftShutdown();
184
        });
185
186
        $this->loop->addSignal(SIGINT, function () {
187
            $this->line('Closing existing connections...');
188
189
            $this->triggerSoftShutdown();
190
        });
191
    }
192
193
    /**
194
     * Configure the tracker that will delete
195
     * from the store the connections that.
196
     *
197
     * @return void
198
     */
199
    protected function configurePongTracker()
200
    {
201
        $this->loop->addPeriodicTimer(10, function () {
202
            $this->laravel
203
                ->make(ChannelManager::class)
204
                ->removeObsoleteConnections();
205
        });
206
    }
207
208
    /**
209
     * Configure the HTTP logger class.
210
     *
211
     * @return void
212
     */
213
    protected function configureHttpLogger()
214
    {
215
        $this->laravel->singleton(HttpLogger::class, function ($app) {
216
            return (new HttpLogger($this->output))
217
                ->enable($this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
0 ignored issues
show
Bug introduced by
It seems like $this->option('debug') ?...app']['debug'] ?? false can also be of type string; however, parameter $enabled of BeyondCode\LaravelWebSoc...oggers\Logger::enable() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

217
                ->enable(/** @scrutinizer ignore-type */ $this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
Loading history...
218
                ->verbose($this->output->isVerbose());
219
        });
220
    }
221
222
    /**
223
     * Configure the logger for messages.
224
     *
225
     * @return void
226
     */
227
    protected function configureMessageLogger()
228
    {
229
        $this->laravel->singleton(WebSocketsLogger::class, function ($app) {
230
            return (new WebSocketsLogger($this->output))
231
                ->enable($this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
0 ignored issues
show
Bug introduced by
It seems like $this->option('debug') ?...app']['debug'] ?? false can also be of type string; however, parameter $enabled of BeyondCode\LaravelWebSoc...oggers\Logger::enable() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

231
                ->enable(/** @scrutinizer ignore-type */ $this->option('debug') ?: ($app['config']['app']['debug'] ?? false))
Loading history...
232
                ->verbose($this->output->isVerbose());
233
        });
234
    }
235
236
    /**
237
     * Configure the connection logger.
238
     *
239
     * @return void
240
     */
241
    protected function configureConnectionLogger()
242
    {
243
        $this->laravel->bind(ConnectionLogger::class, function ($app) {
244
            return (new ConnectionLogger($this->output))
245
                ->enable($app['config']['app']['debug'] ?? false)
246
                ->verbose($this->output->isVerbose());
247
        });
248
    }
249
250
    /**
251
     * Start the server.
252
     *
253
     * @return void
254
     */
255
    protected function startServer()
256
    {
257
        $this->info("Starting the WebSocket server on port {$this->option('port')}...");
258
259
        $this->buildServer();
260
261
        $this->server->run();
262
    }
263
264
    /**
265
     * Build the server instance.
266
     *
267
     * @return void
268
     */
269
    protected function buildServer()
270
    {
271
        $this->server = new ServerFactory(
0 ignored issues
show
Documentation Bug introduced by
It seems like new BeyondCode\LaravelWe... $this->option('port')) of type BeyondCode\LaravelWebSockets\ServerFactory is incompatible with the declared type Ratchet\Server\IoServer of property $server.

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...
272
            $this->option('host'), $this->option('port')
273
        );
274
275
        if ($loop = $this->option('loop')) {
276
            $this->loop = $loop;
0 ignored issues
show
Documentation Bug introduced by
It seems like $loop of type string is incompatible with the declared type React\EventLoop\LoopInterface of property $loop.

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...
277
        }
278
279
        $this->server = $this->server
280
            ->setLoop($this->loop)
281
            ->withRoutes(WebSocketRouter::getRoutes())
0 ignored issues
show
Bug introduced by
The method getRoutes() does not exist on BeyondCode\LaravelWebSoc...Facades\WebSocketRouter. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

281
            ->withRoutes(WebSocketRouter::/** @scrutinizer ignore-call */ getRoutes())
Loading history...
282
            ->setConsoleOutput($this->output)
283
            ->createServer();
284
    }
285
286
    /**
287
     * Get the last time the server restarted.
288
     *
289
     * @return int
290
     */
291
    protected function getLastRestart()
292
    {
293
        return Cache::get(
294
            'beyondcode:websockets:restart', 0
295
        );
296
    }
297
298
    /**
299
     * Trigger a soft shutdown for the process.
300
     *
301
     * @return void
302
     */
303
    protected function triggerSoftShutdown()
304
    {
305
        $channelManager = $this->laravel->make(ChannelManager::class);
306
307
        // Close the new connections allowance on this server.
308
        $channelManager->declineNewConnections();
0 ignored issues
show
Bug introduced by
The method declineNewConnections() does not exist on BeyondCode\LaravelWebSoc...ontracts\ChannelManager. Since it exists in all sub-types, consider adding an abstract or default implementation to BeyondCode\LaravelWebSoc...ontracts\ChannelManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

308
        $channelManager->/** @scrutinizer ignore-call */ 
309
                         declineNewConnections();
Loading history...
309
310
        // Get all local connections and close them. They will
311
        // be automatically be unsubscribed from all channels.
312
        $channelManager->getLocalConnections()
313
            ->then(function ($connections) {
314
                foreach ($connections as $connection) {
315
                    $connection->close();
316
                }
317
            })
318
            ->then(function () {
319
                $this->loop->stop();
320
            });
321
    }
322
}
323