Total Complexity | 46 |
Total Lines | 362 |
Duplicated Lines | 0 % |
Changes | 10 | ||
Bugs | 3 | Features | 2 |
Complex classes like HttpServerCommand 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.
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 HttpServerCommand, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class HttpServerCommand extends Command |
||
26 | { |
||
27 | /** |
||
28 | * The name and signature of the console command. |
||
29 | * |
||
30 | * @var string |
||
31 | */ |
||
32 | protected $signature = 'swoole:http {action : start|stop|restart|reload|infos}'; |
||
33 | |||
34 | /** |
||
35 | * The console command description. |
||
36 | * |
||
37 | * @var string |
||
38 | */ |
||
39 | protected $description = 'Swoole HTTP Server controller.'; |
||
40 | |||
41 | /** |
||
42 | * The console command action. start|stop|restart|reload |
||
43 | * |
||
44 | * @var string |
||
45 | */ |
||
46 | protected $action; |
||
47 | |||
48 | /** |
||
49 | * |
||
50 | * The pid. |
||
51 | * |
||
52 | * @var int |
||
53 | */ |
||
54 | protected $currentPid; |
||
55 | |||
56 | /** |
||
57 | * The configs for this package. |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $config; |
||
62 | |||
63 | /** |
||
64 | * Execute the console command. |
||
65 | * |
||
66 | * @return void |
||
67 | */ |
||
68 | public function handle() |
||
69 | { |
||
70 | $this->checkEnvironment(); |
||
71 | $this->loadConfigs(); |
||
72 | $this->initAction(); |
||
73 | $this->hookAction(); |
||
74 | $this->runAction(); |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Load configs. |
||
79 | */ |
||
80 | protected function loadConfigs() |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * Hook action |
||
87 | */ |
||
88 | protected function hookAction() |
||
89 | { |
||
90 | // custom hook task before starting server |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * Run action. |
||
95 | */ |
||
96 | protected function runAction() |
||
97 | { |
||
98 | $this->{$this->action}(); |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Run swoole_http_server. |
||
103 | */ |
||
104 | protected function start() |
||
105 | { |
||
106 | if ($this->isRunning()) { |
||
107 | $this->error('Failed! swoole_http_server process is already running.'); |
||
108 | |||
109 | return; |
||
110 | } |
||
111 | |||
112 | $host = Arr::get($this->config, 'server.host'); |
||
113 | $port = Arr::get($this->config, 'server.port'); |
||
114 | $hotReloadEnabled = Arr::get($this->config, 'hot_reload.enabled'); |
||
115 | $accessLogEnabled = Arr::get($this->config, 'server.access_log'); |
||
116 | |||
117 | $this->info('Starting swoole http server...'); |
||
118 | $this->info("Swoole http server started: <http://{$host}:{$port}>"); |
||
119 | if ($this->isDaemon()) { |
||
120 | $this->info( |
||
121 | '> (You can run this command to ensure the ' . |
||
122 | 'swoole_http_server process is running: ps aux|grep "swoole")' |
||
123 | ); |
||
124 | } |
||
125 | |||
126 | $manager = $this->laravel->make(Manager::class); |
||
127 | $server = $this->laravel->make(Server::class); |
||
128 | |||
129 | if ($accessLogEnabled) { |
||
130 | $this->registerAccessLog(); |
||
131 | } |
||
132 | |||
133 | if ($hotReloadEnabled) { |
||
134 | $manager->addProcess($this->getHotReloadProcess($server)); |
||
135 | } |
||
136 | |||
137 | $manager->run(); |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Stop swoole_http_server. |
||
142 | */ |
||
143 | protected function stop() |
||
144 | { |
||
145 | if (! $this->isRunning()) { |
||
146 | $this->error("Failed! There is no swoole_http_server process running."); |
||
147 | |||
148 | return; |
||
149 | } |
||
150 | |||
151 | $this->info('Stopping swoole http server...'); |
||
152 | |||
153 | $isRunning = $this->killProcess(SIGTERM, 15); |
||
154 | |||
155 | if ($isRunning) { |
||
156 | $this->error('Unable to stop the swoole_http_server process.'); |
||
157 | |||
158 | return; |
||
159 | } |
||
160 | |||
161 | // I don't known why Swoole didn't trigger "onShutdown" after sending SIGTERM. |
||
162 | // So we should manually remove the pid file. |
||
163 | $this->laravel->make(PidManager::class)->delete(); |
||
164 | |||
165 | $this->info('> success'); |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * Restart swoole http server. |
||
170 | */ |
||
171 | protected function restart() |
||
172 | { |
||
173 | if ($this->isRunning()) { |
||
174 | $this->stop(); |
||
175 | } |
||
176 | |||
177 | $this->start(); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Reload. |
||
182 | */ |
||
183 | protected function reload() |
||
184 | { |
||
185 | if (! $this->isRunning()) { |
||
186 | $this->error("Failed! There is no swoole_http_server process running."); |
||
187 | |||
188 | return; |
||
189 | } |
||
190 | |||
191 | $this->info('Reloading swoole_http_server...'); |
||
192 | |||
193 | if (! $this->killProcess(SIGUSR1)) { |
||
194 | $this->error('> failure'); |
||
195 | |||
196 | return; |
||
197 | } |
||
198 | |||
199 | $this->info('> success'); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Display PHP and Swoole misc info. |
||
204 | */ |
||
205 | protected function infos() |
||
206 | { |
||
207 | $this->showInfos(); |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Display PHP and Swoole miscs infos. |
||
212 | */ |
||
213 | protected function showInfos() |
||
214 | { |
||
215 | $isRunning = $this->isRunning(); |
||
216 | $host = Arr::get($this->config, 'server.host'); |
||
217 | $port = Arr::get($this->config, 'server.port'); |
||
218 | $reactorNum = Arr::get($this->config, 'server.options.reactor_num'); |
||
219 | $workerNum = Arr::get($this->config, 'server.options.worker_num'); |
||
220 | $taskWorkerNum = Arr::get($this->config, 'server.options.task_worker_num'); |
||
221 | $isWebsocket = Arr::get($this->config, 'websocket.enabled'); |
||
222 | $hasTaskWorker = $isWebsocket || Arr::get($this->config, 'queue.default') === 'swoole'; |
||
223 | $logFile = Arr::get($this->config, 'server.options.log_file'); |
||
224 | $pids = $this->laravel->make(PidManager::class)->read(); |
||
225 | $masterPid = $pids['masterPid'] ?? null; |
||
226 | $managerPid = $pids['managerPid'] ?? null; |
||
227 | |||
228 | $table = [ |
||
229 | ['PHP Version', 'Version' => phpversion()], |
||
230 | ['Swoole Version', 'Version' => swoole_version()], |
||
231 | ['Laravel Version', $this->getApplication()->getVersion()], |
||
232 | ['Listen IP', $host], |
||
233 | ['Listen Port', $port], |
||
234 | ['Server Status', $isRunning ? 'Online' : 'Offline'], |
||
235 | ['Reactor Num', $reactorNum], |
||
236 | ['Worker Num', $workerNum], |
||
237 | ['Task Worker Num', $hasTaskWorker ? $taskWorkerNum : 0], |
||
238 | ['Websocket Mode', $isWebsocket ? 'On' : 'Off'], |
||
239 | ['Master PID', $isRunning ? $masterPid : 'None'], |
||
240 | ['Manager PID', $isRunning && $managerPid ? $managerPid : 'None'], |
||
241 | ['Log Path', $logFile], |
||
242 | ]; |
||
243 | |||
244 | $this->table(['Name', 'Value'], $table); |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Initialize command action. |
||
249 | */ |
||
250 | protected function initAction() |
||
251 | { |
||
252 | $this->action = $this->argument('action'); |
||
|
|||
253 | |||
254 | if (! in_array($this->action, ['start', 'stop', 'restart', 'reload', 'infos'], true)) { |
||
255 | $this->error( |
||
256 | "Invalid argument '{$this->action}'. Expected 'start', 'stop', 'restart', 'reload' or 'infos'." |
||
257 | ); |
||
258 | |||
259 | return; |
||
260 | } |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * @param \SwooleTW\Http\Server\Facades\Server $server |
||
265 | * |
||
266 | * @return \Swoole\Process |
||
267 | */ |
||
268 | protected function getHotReloadProcess($server) |
||
269 | { |
||
270 | $recursively = Arr::get($this->config, 'hot_reload.recursively'); |
||
271 | $directory = Arr::get($this->config, 'hot_reload.directory'); |
||
272 | $filter = Arr::get($this->config, 'hot_reload.filter'); |
||
273 | $log = Arr::get($this->config, 'hot_reload.log'); |
||
274 | |||
275 | $cb = function (FSEvent $event) use ($server, $log) { |
||
276 | $log ? $this->info(FSOutput::format($event)) : null; |
||
277 | $server->reload(); |
||
278 | }; |
||
279 | |||
280 | return (new FSProcess($filter, $recursively, $directory))->make($cb); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * If Swoole process is running. |
||
285 | * |
||
286 | * @param int $pid |
||
287 | * |
||
288 | * @return bool |
||
289 | */ |
||
290 | public function isRunning() |
||
291 | { |
||
292 | $pids = $this->laravel->make(PidManager::class)->read(); |
||
293 | |||
294 | if (! count($pids)) { |
||
295 | return false; |
||
296 | } |
||
297 | |||
298 | $masterPid = $pids['masterPid'] ?? null; |
||
299 | $managerPid = $pids['managerPid'] ?? null; |
||
300 | |||
301 | if ($managerPid) { |
||
302 | // Swoole process mode |
||
303 | return $masterPid && $managerPid && Process::kill((int) $managerPid, 0); |
||
304 | } |
||
305 | |||
306 | // Swoole base mode, no manager process |
||
307 | return $masterPid && Process::kill((int) $masterPid, 0); |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Kill process. |
||
312 | * |
||
313 | * @param int $sig |
||
314 | * @param int $wait |
||
315 | * |
||
316 | * @return bool |
||
317 | */ |
||
318 | protected function killProcess($sig, $wait = 0) |
||
319 | { |
||
320 | Process::kill( |
||
321 | Arr::first($this->laravel->make(PidManager::class)->read()), |
||
322 | $sig |
||
323 | ); |
||
324 | |||
325 | if ($wait) { |
||
326 | $start = time(); |
||
327 | |||
328 | do { |
||
329 | if (! $this->isRunning()) { |
||
330 | break; |
||
331 | } |
||
332 | |||
333 | usleep(100000); |
||
334 | } while (time() < $start + $wait); |
||
335 | } |
||
336 | |||
337 | return $this->isRunning(); |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Return daemonize config. |
||
342 | */ |
||
343 | protected function isDaemon(): bool |
||
344 | { |
||
345 | return Arr::get($this->config, 'server.options.daemonize', false); |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Check running enironment. |
||
350 | */ |
||
351 | protected function checkEnvironment() |
||
352 | { |
||
353 | if (OS::is(OS::WIN)) { |
||
354 | $this->error('Swoole extension doesn\'t support Windows OS.'); |
||
355 | |||
356 | exit(1); |
||
357 | } |
||
358 | |||
359 | if (! extension_loaded('swoole')) { |
||
360 | $this->error('Can\'t detect Swoole extension installed.'); |
||
361 | |||
362 | exit(1); |
||
363 | } |
||
364 | |||
365 | if (! version_compare(swoole_version(), '4.3.1', 'ge')) { |
||
366 | $this->error('Your Swoole version must be higher than `4.3.1`.'); |
||
367 | |||
368 | exit(1); |
||
369 | } |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Register access log services. |
||
374 | */ |
||
375 | protected function registerAccessLog() |
||
387 | }); |
||
388 | } |
||
389 | } |
||
390 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.