Completed
Push — master ( 3e3a37...ef36d2 )
by Biao
03:58
created

Portal::stop()   B

Complexity

Conditions 9
Paths 17

Size

Total Lines 45
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 9
eloc 34
c 5
b 1
f 0
nc 17
nop 0
dl 0
loc 45
rs 8.0555
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)
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();
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();
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();
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')) {
106
            $this->error('LaravelS requires swoole extension, try to `pecl install swoole` and `php --ri swoole`.');
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['LARAVEL_ENV'] = $_ENV['LARAVEL_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 = $this->runArtisanCommand(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
            if (self::kill($pid, SIGTERM)) {
175
                // Make sure that master process quit
176
                $time = 1;
177
                $waitTime = isset($config['server']['swoole']['max_wait_time']) ? $config['server']['swoole']['max_wait_time'] : 60;
178
                $this->info("The max time of waiting to forcibly stop is {$waitTime}s.");
179
                while (self::kill($pid, 0)) {
180
                    if ($time > $waitTime) {
181
                        $this->warning("Swoole [PID={$pid}] cannot be stopped gracefully in {$waitTime}s, will be stopped forced right now.");
182
                        return 1;
183
                    }
184
                    $this->info("Waiting Swoole[PID={$pid}] to stop. [{$time}]");
185
                    sleep(1);
186
                    $time++;
187
                }
188
                $basePath = dirname($pidFile);
189
                $deleteFiles = [
190
                    $pidFile,
191
                    $basePath . '/laravels-custom-processes.pid',
192
                    $basePath . '/laravels-timer-process.pid',
193
                ];
194
                foreach ($deleteFiles as $deleteFile) {
195
                    if (file_exists($deleteFile)) {
196
                        unlink($deleteFile);
197
                    }
198
                }
199
                $this->info("Swoole [PID={$pid}] is stopped.");
200
                return 0;
201
            } else {
202
                $this->error("Swoole [PID={$pid}] is stopped failed.");
203
                return 1;
204
            }
205
        } else {
206
            $this->warning("Swoole [PID={$pid}] does not exist, or permission denied.");
207
            return 0;
208
        }
209
    }
210
211
    public function restart()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function restart()
Loading history...
212
    {
213
        $code = $this->stop();
214
        if ($code !== 0) {
215
            return $code;
216
        }
217
        return $this->start();
218
    }
219
220
    public function reload()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function reload()
Loading history...
221
    {
222
        $config = $this->getConfig();
223
        $pidFile = $config['server']['swoole']['pid_file'];
224
        if (!file_exists($pidFile)) {
225
            $this->error('It seems that Swoole is not running.');
226
            return 1;
227
        }
228
229
        // Reload worker processes
230
        $pid = file_get_contents($pidFile);
231
        if (!$pid || !self::kill($pid, 0)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::kill($pid, 0) of type false|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
232
            $this->error("Swoole [PID={$pid}] does not exist, or permission denied.");
233
            return 1;
234
        }
235
        if (self::kill($pid, SIGUSR1)) {
236
            $this->info("Swoole [PID={$pid}] is reloaded.");
237
        } else {
238
            $this->error("Swoole [PID={$pid}] is reloaded failed.");
239
        }
240
241
        // Reload custom processes
242
        $pidFile = dirname($pidFile) . '/laravels-custom-processes.pid';
243
        if (file_exists($pidFile)) {
244
            $pids = (array)explode("\n", trim(file_get_contents($pidFile)));
245
            unlink($pidFile);
246
            foreach ($pids as $pid) {
247
                if (!$pid || !self::kill($pid, 0)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::kill($pid, 0) of type false|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
248
                    $this->error("Custom process[PID={$pid}] does not exist, or permission denied.");
249
                    continue;
250
                }
251
252
                if (self::kill($pid, SIGUSR1)) {
253
                    $this->info("Custom process[PID={$pid}] is reloaded.");
254
                } else {
255
                    $this->error("Custom process[PID={$pid}] is reloaded failed.");
256
                }
257
            }
258
        }
259
260
        // Reload timer process
261
        if (!empty($config['server']['timer']['enable']) && !empty($config['server']['timer']['jobs'])) {
262
            $pidFile = dirname($pidFile) . '/laravels-timer-process.pid';
263
            $pid = file_get_contents($pidFile);
264
            if (!$pid || !self::kill($pid, 0)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::kill($pid, 0) of type false|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
265
                $this->error("Timer process[PID={$pid}] does not exist, or permission denied.");
266
                return 1;
267
            }
268
269
            if (self::kill($pid, SIGUSR1)) {
270
                $this->info("Timer process[PID={$pid}] is reloaded.");
271
            } else {
272
                $this->error("Timer process[PID={$pid}] is reloaded failed.");
273
            }
274
        }
275
        return 0;
276
    }
277
278
    public function showInfo()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function showInfo()
Loading history...
279
    {
280
        return $this->runArtisanCommand('laravels info');
281
    }
282
283
    public function loadApollo(array $options)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function loadApollo()
Loading history...
284
    {
285
        Client::putCommandOptionsToEnv($options);
286
        $envFile = $this->basePath . '/.env';
287
        if (isset($options['env'])) {
288
            $envFile .= '.' . $options['env'];
289
        }
290
        Client::createFromCommandOptions($options)->pullAllAndSave($envFile);
291
    }
292
293
    public function makeArtisanCmd($subCmd)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function makeArtisanCmd()
Loading history...
294
    {
295
        $phpCmd = sprintf('%s -c "%s"', PHP_BINARY, php_ini_loaded_file());
296
        $env = $this->input->getOption('env');
297
        $envs = $env ? "APP_ENV={$env}" : '';
298
        return trim(sprintf('%s %s %s/artisan %s', $envs, $phpCmd, $this->basePath, $subCmd));
299
    }
300
301
    public function runArtisanCommand($cmd)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function runArtisanCommand()
Loading history...
302
    {
303
        $cmd = $this->makeArtisanCmd($cmd);
304
        return self::runCommand($cmd);
305
    }
306
307
    public function getConfig()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getConfig()
Loading history...
308
    {
309
        return unserialize(file_get_contents($this->getConfPath()));
310
    }
311
312
    protected function getConfPath()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getConfPath()
Loading history...
313
    {
314
        return $this->basePath . '/storage/laravels.conf';
315
    }
316
317
    public static function runCommand($cmd, $input = null)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function runCommand()
Loading history...
318
    {
319
        $fp = popen($cmd, 'w');
320
        if ($fp === false) {
321
            return false;
322
        }
323
        if ($input !== null) {
324
            $bytes = fwrite($fp, $input);
325
            if ($bytes === false) {
326
                return 1;
327
            }
328
        }
329
        return pclose($fp);
330
    }
331
332
    public static function kill($pid, $sig)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function kill()
Loading history...
333
    {
334
        try {
335
            return Process::kill((int)$pid, $sig);
0 ignored issues
show
Bug introduced by
Are you sure the usage of Swoole\Process::kill((int)$pid, $sig) targeting Swoole\Process::kill() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
336
        } catch (\Exception $e) {
337
            return false;
338
        }
339
    }
340
}
341