Portal   F
last analyzed

Complexity

Total Complexity 75

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Importance

Changes 13
Bugs 4 Features 1
Metric Value
wmc 75
eloc 210
c 13
b 4
f 1
dl 0
loc 356
rs 2.4

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A configure() 0 11 1
B execute() 0 51 7
A loadApollo() 0 8 2
A makeLaravelSCmd() 0 4 1
A makeArtisanCmd() 0 6 3
A showInfo() 0 3 1
A restart() 0 7 2
B stop() 0 44 9
C reload() 0 56 15
D start() 0 58 18
A kill() 0 6 2
A getConfigPath() 0 7 2
A runArtisanCommand() 0 4 1
A getConfig() 0 3 1
A runLaravelSCommand() 0 4 1
A runCommand() 0 13 4
A makePhpCmd() 0 18 4

How to fix   Complexity   

Complex Class

Complex classes like Portal 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 Portal, and based on these observations, apply Extract Interface, too.

1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace Hhxsv5\LaravelS\Console;
4
5
use Hhxsv5\LaravelS\Components\Apollo\Client;
6
use Hhxsv5\LaravelS\Illuminate\LogTrait;
7
use Hhxsv5\LaravelS\LaravelS;
8
use Swoole\Process;
9
use Symfony\Component\Console\Command\Command;
10
use Symfony\Component\Console\Input\InputArgument;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Input\InputOption;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Console\Style\SymfonyStyle;
15
16
class Portal extends Command
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class Portal
Loading history...
17
{
18
    use LogTrait;
19
20
21
    /**@var string */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
22
    protected $basePath;
23
24
    /**@var InputInterface */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
25
    protected $input;
26
27
    /**@var OutputInterface */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
28
    protected $output;
29
30
    public function __construct($basePath)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
31
    {
32
        parent::__construct('laravels');
33
        $this->basePath = $basePath;
34
    }
35
36
    protected function configure()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function configure()
Loading history...
37
    {
38
        $this->setDescription('LaravelS console tool');
39
        $this->setHelp('LaravelS console tool');
40
41
        $this->addArgument('action', InputArgument::OPTIONAL, 'start|stop|restart|reload|info|help', 'help');
42
        $this->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'The environment the command should run under, this feature requires Laravel 5.2+');
43
        $this->addOption('daemonize', 'd', InputOption::VALUE_NONE, 'Run as a daemon');
44
        $this->addOption('ignore', 'i', InputOption::VALUE_NONE, 'Ignore checking PID file of Master process');
45
        $this->addOption('x-version', 'x', InputOption::VALUE_OPTIONAL, 'The version(branch) of the current project, stored in $_ENV/$_SERVER');
46
        Client::attachCommandOptions($this);
47
    }
48
49
    protected function execute(InputInterface $input, OutputInterface $output): int
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function execute()
Loading history...
50
    {
51
        $this->input = $input;
52
        $this->output = $output;
53
        LaravelS::setOutputStyle(new SymfonyStyle($this->input, $this->output));
54
55
        try {
56
            $action = $input->getArgument('action');
57
            switch ($action) {
58
                case 'start':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
59
                    return $this->start();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->start() could return the type false which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
60
                case 'stop':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
61
                    return $this->stop();
62
                case 'restart':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
63
                    return $this->restart();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->restart() could return the type false which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
64
                case 'reload':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
65
                    return $this->reload();
66
                case 'info':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
67
                    return $this->showInfo();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->showInfo() could return the type false which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
68
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
69
                    $help = <<<EOS
70
71
Usage: 
72
  [%s] ./bin/laravels [options] <action>
73
74
Arguments:
75
  action                start|stop|restart|reload|info|help
76
77
Options:
78
  -e, --env             The environment the command should run under, this feature requires Laravel 5.2+
79
  -d, --daemonize       Run as a daemon
80
  -i, --ignore          Ignore checking PID file of Master process
81
  -x, --x-version       The version(branch) of the current project, stored in \$_ENV/\$_SERVER
82
EOS;
83
84
                    $this->info(sprintf($help, PHP_BINARY));
85
                    return 0;
86
            }
87
        } catch (\Exception $e) {
88
            $error = sprintf(
89
                'Uncaught exception "%s"([%d]%s) at %s:%s, %s%s',
90
                get_class($e),
91
                $e->getCode(),
92
                $e->getMessage(),
93
                $e->getFile(),
94
                $e->getLine(),
95
                PHP_EOL,
96
                $e->getTraceAsString()
97
            );
98
            $this->error($error);
99
            return 1;
100
        }
101
    }
102
103
    public function start()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function start()
Loading history...
104
    {
105
        if (!extension_loaded('swoole') && !extension_loaded('openswoole')) {
106
            $this->error('LaravelS requires swoole / openswoole extension, try to `pecl install swoole` and `php --ri swoole` OR `pecl install openswoole` and `php --ri openswoole`.');
107
            return 1;
108
        }
109
110
        // Generate conf file storage/laravels.conf
111
        $options = $this->input->getOptions();
112
        if (isset($options['env']) && $options['env'] !== '') {
113
            $_SERVER['_ENV'] = $_ENV['_ENV'] = $options['env'];
114
        }
115
        if (isset($options['x-version']) && $options['x-version'] !== '') {
116
            $_SERVER['X_VERSION'] = $_ENV['X_VERSION'] = $options['x-version'];
117
        }
118
119
        // Load Apollo configurations to .env file
120
        if (!empty($options['enable-apollo'])) {
121
            $this->loadApollo($options);
122
        }
123
124
        $passOptionStr = '';
125
        $passOptions = ['daemonize', 'ignore', 'x-version'];
126
        foreach ($passOptions as $key) {
127
            if (!isset($options[$key])) {
128
                continue;
129
            }
130
            $value = $options[$key];
131
            if ($value === false) {
132
                continue;
133
            }
134
            $passOptionStr .= sprintf('--%s%s ', $key, is_bool($value) ? '' : ('=' . $value));
135
        }
136
        $statusCode = self::runArtisanCommand($this->basePath, trim('laravels config ' . $passOptionStr));
137
        if ($statusCode !== 0) {
138
            return $statusCode;
139
        }
140
141
        // Here we go...
142
        $config = $this->getConfig();
143
144
        if (!$config['server']['ignore_check_pid'] && file_exists($config['server']['swoole']['pid_file'])) {
145
            $pid = (int)file_get_contents($config['server']['swoole']['pid_file']);
146
            if ($pid > 0 && self::kill($pid, 0)) {
147
                $this->warning(sprintf('Swoole[PID=%d] is already running.', $pid));
148
                return 1;
149
            }
150
        }
151
152
        if ($config['server']['swoole']['daemonize']) {
153
            $this->trace('Swoole is running in daemon mode, see "ps -ef|grep laravels".');
154
        } else {
155
            $this->trace('Swoole is running, press Ctrl+C to quit.');
156
        }
157
158
        (new LaravelS($config['server'], $config['laravel']))->run();
159
160
        return 0;
161
    }
162
163
    public function stop()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function stop()
Loading history...
164
    {
165
        $config = $this->getConfig();
166
        $pidFile = $config['server']['swoole']['pid_file'];
167
        if (!file_exists($pidFile)) {
168
            $this->warning('It seems that Swoole is not running.');
169
            return 0;
170
        }
171
172
        $pid = file_get_contents($pidFile);
173
        if (!self::kill($pid, 0)) {
174
            $this->warning("Swoole [PID={$pid}] does not exist, or permission denied.");
175
            return 0;
176
        }
177
        if (!self::kill($pid, SIGTERM)) {
178
            $this->error("Swoole [PID={$pid}] is stopped failed.");
179
            return 1;
180
        }
181
        // Make sure that master process quit
182
        $time = 1;
183
        $waitTime = isset($config['server']['swoole']['max_wait_time']) ? $config['server']['swoole']['max_wait_time'] : 60;
184
        $this->info("The max time of waiting to forcibly stop is {$waitTime}s.");
185
        while (self::kill($pid, 0)) {
186
            if ($time > $waitTime) {
187
                $this->warning("Swoole [PID={$pid}] cannot be stopped gracefully in {$waitTime}s, will be stopped forced right now.");
188
                return 1;
189
            }
190
            $this->info("Waiting Swoole[PID={$pid}] to stop. [{$time}]");
191
            sleep(1);
192
            $time++;
193
        }
194
        $basePath = dirname($pidFile);
195
        $deleteFiles = [
196
            $pidFile,
197
            $basePath . '/laravels-custom-processes.pid',
198
            $basePath . '/laravels-timer-process.pid',
199
        ];
200
        foreach ($deleteFiles as $deleteFile) {
201
            if (file_exists($deleteFile)) {
202
                unlink($deleteFile);
203
            }
204
        }
205
        $this->info("Swoole [PID={$pid}] is stopped.");
206
        return 0;
207
    }
208
209
    public function restart()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function restart()
Loading history...
210
    {
211
        $code = $this->stop();
212
        if ($code !== 0) {
213
            return $code;
214
        }
215
        return $this->start();
216
    }
217
218
    public function reload()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function reload()
Loading history...
219
    {
220
        $config = $this->getConfig();
221
        $pidFile = $config['server']['swoole']['pid_file'];
222
        if (!file_exists($pidFile)) {
223
            $this->error('It seems that Swoole is not running.');
224
            return 1;
225
        }
226
227
        // Reload worker processes
228
        $pid = file_get_contents($pidFile);
229
        if (!$pid || !self::kill($pid, 0)) {
230
            $this->error("Swoole [PID={$pid}] does not exist, or permission denied.");
231
            return 1;
232
        }
233
        if (self::kill($pid, SIGUSR1)) {
234
            $this->info("Swoole [PID={$pid}] is reloaded.");
235
        } else {
236
            $this->error("Swoole [PID={$pid}] is reloaded failed.");
237
        }
238
239
        // Reload custom processes
240
        $pidFile = dirname($pidFile) . '/laravels-custom-processes.pid';
241
        if (file_exists($pidFile)) {
242
            $pids = (array)explode("\n", trim(file_get_contents($pidFile)));
243
            unlink($pidFile);
244
            foreach ($pids as $pid) {
245
                if (!$pid || !self::kill($pid, 0)) {
246
                    $this->error("Custom process[PID={$pid}] does not exist, or permission denied.");
247
                    continue;
248
                }
249
250
                if (self::kill($pid, SIGUSR1)) {
251
                    $this->info("Custom process[PID={$pid}] is reloaded.");
252
                } else {
253
                    $this->error("Custom process[PID={$pid}] is reloaded failed.");
254
                }
255
            }
256
        }
257
258
        // Reload timer process
259
        if (!empty($config['server']['timer']['enable']) && !empty($config['server']['timer']['jobs'])) {
260
            $pidFile = dirname($pidFile) . '/laravels-timer-process.pid';
261
            $pid = file_get_contents($pidFile);
262
            if (!$pid || !self::kill($pid, 0)) {
263
                $this->error("Timer process[PID={$pid}] does not exist, or permission denied.");
264
                return 1;
265
            }
266
267
            if (self::kill($pid, SIGUSR1)) {
268
                $this->info("Timer process[PID={$pid}] is reloaded.");
269
            } else {
270
                $this->error("Timer process[PID={$pid}] is reloaded failed.");
271
            }
272
        }
273
        return 0;
274
    }
275
276
    public function showInfo()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function showInfo()
Loading history...
277
    {
278
        return self::runArtisanCommand($this->basePath, 'laravels info');
279
    }
280
281
    public function loadApollo(array $options)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function loadApollo()
Loading history...
282
    {
283
        Client::putCommandOptionsToEnv($options);
284
        $envFile = $this->basePath . '/.env';
285
        if (isset($options['env'])) {
286
            $envFile .= '.' . $options['env'];
287
        }
288
        Client::createFromCommandOptions($options)->pullAllAndSave($envFile);
289
    }
290
291
    protected static function makeArtisanCmd($basePath, $subCmd)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function makeArtisanCmd()
Loading history...
292
    {
293
        $phpCmd = self::makePhpCmd();
294
        $env = isset($_ENV['_ENV']) ? trim($_ENV['_ENV']) : '';
295
        $appEnv = $env === '' ? '' : "APP_ENV={$env}";
296
        return trim(sprintf('%s %s %s/artisan %s', $appEnv, $phpCmd, $basePath, $subCmd));
297
    }
298
299
    protected static function makeLaravelSCmd($basePath, $subCmd)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function makeLaravelSCmd()
Loading history...
300
    {
301
        $phpCmd = self::makePhpCmd();
302
        return trim(sprintf('%s %s/bin/laravels %s', $phpCmd, $basePath, $subCmd));
303
    }
304
305
    protected static function makePhpCmd($subCmd = '')
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function makePhpCmd()
Loading history...
306
    {
307
        $iniFile = php_ini_loaded_file();
308
        if ($iniFile === false) {
309
            $phpCmd = PHP_BINARY;
310
        } else {
311
            $phpCmd = sprintf('%s -c "%s"', PHP_BINARY, $iniFile);
312
        }
313
314
        $checkSwooleCmd = $phpCmd . ' --ri swoole';
315
        $checkOpenSwooleCmd = $phpCmd . ' --ri openswoole';
316
        // Short-circuit, if Swoole is found Loaded already. If not, check for Open-Swoole as well.
317
        if (stripos((string)shell_exec($checkSwooleCmd), 'enabled') === false
318
            && stripos((string)shell_exec($checkOpenSwooleCmd), 'enabled') === false) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
319
            $phpCmd .= ' -d "extension=swoole"';
320
        }
321
322
        return trim($phpCmd . ' ' . $subCmd);
323
    }
324
325
    public static function runArtisanCommand($basePath, $cmd)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function runArtisanCommand()
Loading history...
326
    {
327
        $cmd = self::makeArtisanCmd($basePath, $cmd);
328
        return self::runCommand($cmd);
329
    }
330
331
    public static function runLaravelSCommand($basePath, $cmd)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function runLaravelSCommand()
Loading history...
332
    {
333
        $cmd = self::makeLaravelSCmd($basePath, $cmd);
334
        return self::runCommand($cmd);
335
    }
336
337
    public function getConfig()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getConfig()
Loading history...
338
    {
339
        return unserialize((string)file_get_contents($this->getConfigPath()));
340
    }
341
342
    protected function getConfigPath()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getConfigPath()
Loading history...
343
    {
344
        $storagePath = getenv('APP_STORAGE_PATH');
345
        if ($storagePath === false) {
346
            $storagePath = $this->basePath . '/storage';
347
        }
348
        return $storagePath . '/laravels.conf';
349
    }
350
351
    public static function runCommand($cmd, $input = null)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function runCommand()
Loading history...
352
    {
353
        $fp = popen($cmd, 'wb');
354
        if ($fp === false) {
355
            return false;
356
        }
357
        if ($input !== null) {
358
            $bytes = fwrite($fp, $input);
359
            if ($bytes === false) {
360
                return 1;
361
            }
362
        }
363
        return pclose($fp);
364
    }
365
366
    public static function kill($pid, $sig)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function kill()
Loading history...
367
    {
368
        try {
369
            return Process::kill((int)$pid, $sig);
370
        } catch (\Exception $e) {
371
            return false;
372
        }
373
    }
374
}
375