Passed
Push — master ( 18281d...3cf8e5 )
by Biao
03:28
created

Portal::start()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 23
nc 10
nop 0
dl 0
loc 38
rs 8.6186
c 0
b 0
f 0
1
<?php
2
3
namespace Hhxsv5\LaravelS\Console;
4
5
use Swoole\Process;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Input\InputArgument;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Console\Style\OutputStyle;
12
use Symfony\Component\Console\Style\SymfonyStyle;
13
14
class Portal extends Command
15
{
16
    protected $basePath;
17
18
    /**
19
     * @var array
20
     */
21
    protected $config = [];
22
23
    /**
24
     * @var InputInterface $input
25
     */
26
    protected $input;
27
28
    /**
29
     * @var OutputInterface $output
30
     */
31
    protected $output;
32
33
    /**
34
     * @var OutputStyle $output
35
     */
36
    protected $outputStyle;
37
38
    public function __construct($basePath)
39
    {
40
        parent::__construct('laravels');
41
        $this->basePath = $basePath;
42
    }
43
44
    protected function configure()
45
    {
46
        $this->setDescription('LaravelS console tool');
47
        $this->setHelp('LaravelS console tool');
48
49
        $this->addArgument('action', InputArgument::OPTIONAL, 'start|stop|restart|reload|info|help', 'help');
50
        $this->addOption('daemonize', 'd', InputOption::VALUE_NONE, 'Whether run as a daemon for "start & restart"');
51
        $this->addOption('ignore', 'i', InputOption::VALUE_NONE, 'Whether ignore checking process pid for "start & restart"');
52
    }
53
54
    protected function execute(InputInterface $input, OutputInterface $output)
55
    {
56
        $this->input = $input;
57
        $this->output = $output;
58
        $this->outputStyle = new SymfonyStyle($this->input, $this->output);
59
60
        try {
61
            $action = $input->getArgument('action');
62
            switch ($action) {
63
                case 'start':
64
                    $this->start();
65
                    break;
66
                case 'stop':
67
                    $this->stop();
68
                    break;
69
                case 'restart':
70
                    $this->restart();
71
                    break;
72
                case 'reload':
73
                    $this->reload();
74
                    break;
75
                case 'info':
76
                    $this->showInfo();
77
                    break;
78
                default:
79
                    $this->outputStyle->note('Usage: [/usr/local/bin/php] bin/laravels start|stop|restart|reload|info');
80
                    break;
81
            }
82
        } catch (\Exception $e) {
83
            $error = sprintf(
84
                'onRequest: Uncaught exception "%s"([%d]%s) at %s:%s, %s%s',
85
                get_class($e),
86
                $e->getCode(),
87
                $e->getMessage(),
88
                $e->getFile(),
89
                $e->getLine(),
90
                PHP_EOL,
91
                $e->getTraceAsString()
92
            );
93
            $this->outputStyle->error($error);
94
        }
95
    }
96
97
    public function start()
98
    {
99
        // Initialize configuration config/laravels.json
100
        $options = $this->input->getOptions();
101
        $options = array_filter($options);
102
        array_walk($options, function (&$value, $key) {
103
            $value = '--' . $key;
104
        });
105
        $cmd = trim('laravels config ' . implode(' ', $options));
106
        $this->runArtisanCommand($cmd);
107
108
        $this->showInfo();
109
110
        // Here we go...
111
        $config = $this->getConfig();
112
113
        if (in_array($config['svrConf']['socket_type'], [SWOOLE_SOCK_UNIX_DGRAM, SWOOLE_SOCK_UNIX_STREAM])) {
114
            $listenAt = $config['svrConf']['listen_ip'];
115
        } else {
116
            $listenAt = sprintf('%s:%s', $config['svrConf']['listen_ip'], $config['svrConf']['listen_port']);
117
        }
118
119
        if (!$config['svrConf']['ignore_check_pid'] && file_exists($config['svrConf']['swoole']['pid_file'])) {
120
            $pid = (int)file_get_contents($config['svrConf']['swoole']['pid_file']);
121
            if ($pid > 0 && self::kill($pid, 0)) {
122
                $this->outputStyle->warning(sprintf('Swoole[PID=%d] is already running at %s.', $pid, $listenAt));
123
                return 1;
124
            }
125
        }
126
127
        if (!$config['svrConf']['swoole']['daemonize']) {
128
            $this->outputStyle->note(sprintf('Swoole is listening at %s, press Ctrl+C to quit.', $listenAt));
129
        }
130
131
        $lvs = new \Hhxsv5\LaravelS\LaravelS($config['svrConf'], $config['laravelConf']);
132
        $lvs->setOutputStyle($this->outputStyle);
133
        $lvs->run();
134
        return 0;
135
    }
136
137
    public function stop()
138
    {
139
        $config = $this->getConfig();
140
        $pidFile = $config['svrConf']['swoole']['pid_file'];
141
        if (!file_exists($pidFile)) {
142
            $this->outputStyle->warning('It seems that LaravelS is not running.');
143
            return 1;
144
        }
145
146
        $pid = file_get_contents($pidFile);
147
        if (self::kill($pid, 0)) {
148
            if (self::kill($pid, SIGTERM)) {
149
                // Make sure that master process quit
150
                $time = 1;
151
                $waitTime = isset($config['svrConf']['swoole']['max_wait_time']) ? $config['svrConf']['swoole']['max_wait_time'] : 60;
152
                while (self::kill($pid, 0)) {
153
                    if ($time > $waitTime) {
154
                        $this->outputStyle->warning("PID[{$pid}] cannot be stopped gracefully in {$waitTime}s, will be stopped forced right now.");
155
                        return 1;
156
                    }
157
                    $this->outputStyle->note("Waiting PID[{$pid}] to stop. [{$time}]");
158
                    sleep(1);
159
                    $time++;
160
                }
161
                if (file_exists($pidFile)) {
162
                    unlink($pidFile);
163
                }
164
                $this->outputStyle->note("PID[{$pid}] is stopped.");
165
                return 0;
166
            } else {
167
                $this->outputStyle->error("PID[{$pid}] is stopped failed.");
168
                return 1;
169
            }
170
        } else {
171
            $this->outputStyle->error("PID[{$pid}] does not exist, or permission denied.");
172
            return 1;
173
        }
174
    }
175
176
    public function restart()
177
    {
178
        $code = $this->stop();
179
        if ($code !== 0) {
180
            return $code;
181
        }
182
        return $this->start();
183
    }
184
185
    public function reload()
186
    {
187
        $config = $this->getConfig();
188
        $pidFile = $config['svrConf']['swoole']['pid_file'];
189
        if (!file_exists($pidFile)) {
190
            $this->outputStyle->error('It seems that Swoole is not running.');
191
            return;
192
        }
193
194
        $pid = file_get_contents($pidFile);
195
        if (!$pid || !self::kill($pid, 0)) {
196
            $this->outputStyle->error("PID[{$pid}] does not exist, or permission denied.");
197
            return;
198
        }
199
200
        if (self::kill($pid, SIGUSR1)) {
201
            $now = date('Y-m-d H:i:s');
202
            $this->outputStyle->note("PID[{$pid}] is reloaded at {$now}.");
203
        } else {
204
            $this->outputStyle->error("PID[{$pid}] is reloaded failed.");
205
        }
206
    }
207
208
    public function showInfo()
209
    {
210
        $this->runArtisanCommand('laravels info');
211
    }
212
213
    public function artisanCmd($subCmd = null)
214
    {
215
        $phpCmd = sprintf('%s -c "%s"', PHP_BINARY, php_ini_loaded_file());
216
        $artisanCmd = sprintf('%s %s/artisan', $phpCmd, $this->basePath);
217
        if ($subCmd !== null) {
218
            $artisanCmd .= ' ' . $subCmd;
219
        }
220
        return $artisanCmd;
221
    }
222
223
    public function runArtisanCommand($cmd)
224
    {
225
        $cmd = $this->artisanCmd($cmd);
226
        self::runCommand($cmd);
227
    }
228
229
    public function getConfig()
230
    {
231
        if (empty($this->config)) {
232
            $json = file_get_contents($this->basePath . '/storage/laravels.json');
233
            $this->config = (array)json_decode($json, true);
234
        }
235
        return $this->config;
236
    }
237
238
    public static function runCommand($cmd, $input = null)
239
    {
240
        $fp = popen($cmd, 'w');
241
        if ($fp === false) {
242
            return false;
243
        }
244
        if ($input !== null) {
245
            fwrite($fp, $input);
246
        }
247
        pclose($fp);
248
        return true;
249
    }
250
251
    public static function kill($pid, $sig)
252
    {
253
        try {
254
            return Process::kill($pid, $sig);
255
        } catch (\Exception $e) {
256
            return false;
257
        }
258
    }
259
260
}