Completed
Push — master ( 4844de...153469 )
by Biao
04:20
created

Portal::artisanCmd()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace Hhxsv5\LaravelS\Console;
4
5
use Hhxsv5\LaravelS\Illuminate\LogTrait;
6
use Hhxsv5\LaravelS\LaravelS;
7
use Swoole\Process;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\Console\Style\SymfonyStyle;
14
15
class Portal extends Command
16
{
17
    use LogTrait;
18
19
20
    /**@var string */
21
    protected $basePath;
22
23
    /**@var InputInterface */
24
    protected $input;
25
26
    /**@var OutputInterface */
27
    protected $output;
28
29
    public function __construct($basePath)
30
    {
31
        parent::__construct('laravels');
32
        $this->basePath = $basePath;
33
    }
34
35
    protected function configure()
36
    {
37
        $this->setDescription('LaravelS console tool');
38
        $this->setHelp('LaravelS console tool');
39
40
        $this->addArgument('action', InputArgument::OPTIONAL, 'start|stop|restart|reload|info|help', 'help');
41
        $this->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'The environment the command should run under, this feature requires Laravel 5.2+');
42
        $this->addOption('daemonize', 'd', InputOption::VALUE_NONE, 'Whether run as a daemon for "start & restart"');
43
        $this->addOption('ignore', 'i', InputOption::VALUE_NONE, 'Whether ignore checking process pid for "start & restart"');
44
    }
45
46
    protected function execute(InputInterface $input, OutputInterface $output)
47
    {
48
        $this->input = $input;
49
        $this->output = $output;
50
        LaravelS::setOutputStyle(new SymfonyStyle($this->input, $this->output));
51
52
        try {
53
            $action = $input->getArgument('action');
54
            switch ($action) {
55
                case 'start':
56
                    return $this->start();
57
                case 'stop':
58
                    return $this->stop();
59
                case 'restart':
60
                    return $this->restart();
61
                case 'reload':
62
                    return $this->reload();
63
                case 'info':
64
                    return $this->showInfo();
65
                default:
66
                    $help = <<<EOS
67
68
Usage: 
69
  [%s] ./bin/laravels [options] <action>
70
71
Arguments:
72
  action                start|stop|restart|reload|info|help
73
74
Options:
75
  -e, --env             The environment the command should run under, this feature requires Laravel 5.2+
76
  -d, --daemonize       Whether run as a daemon for "start & restart"
77
  -i, --ignore          Whether ignore checking process pid for "start & restart"
78
EOS;
79
80
                    $this->info(sprintf($help, PHP_BINARY));
81
                    return 0;
82
            }
83
        } catch (\Exception $e) {
84
            $error = sprintf(
85
                'Uncaught exception "%s"([%d]%s) at %s:%s, %s%s',
86
                get_class($e),
87
                $e->getCode(),
88
                $e->getMessage(),
89
                $e->getFile(),
90
                $e->getLine(),
91
                PHP_EOL,
92
                $e->getTraceAsString()
93
            );
94
            $this->error($error);
95
            return 1;
96
        }
97
    }
98
99
    public function start()
100
    {
101
        if (!extension_loaded('swoole')) {
102
            $this->error('LaravelS requires swoole extension, try to `pecl install swoole` and `php --ri swoole`.');
103
            return 1;
104
        }
105
106
        // Initialize configuration config/laravels.json
107
        $options = $this->input->getOptions();
108
        unset($options['env']);
109
        $options = array_filter($options);
110
        $optionStr = '';
111
        foreach ($options as $key => $value) {
112
            $optionStr .= sprintf('--%s%s ', $key, is_bool($value) ? '' : ('=' . $value));
113
        }
114
        $statusCode = $this->runArtisanCommand(trim('laravels config ' . $optionStr));
115
        if ($statusCode !== 0) {
116
            return $statusCode;
117
        }
118
119
        // Here we go...
120
        $config = $this->getConfig();
121
122
        if (!$config['server']['ignore_check_pid'] && file_exists($config['server']['swoole']['pid_file'])) {
123
            $pid = (int)file_get_contents($config['server']['swoole']['pid_file']);
124
            if ($pid > 0 && self::kill($pid, 0)) {
125
                $this->warning(sprintf('Swoole[PID=%d] is already running.', $pid));
126
                return 1;
127
            }
128
        }
129
130
        if ($config['server']['swoole']['daemonize']) {
131
            $this->trace('Swoole is running in daemon mode, see "ps -ef|grep laravels".');
132
        } else {
133
            $this->trace('Swoole is running, press Ctrl+C to quit.');
134
        }
135
136
        (new LaravelS($config['server'], $config['laravel']))->run();
137
138
        return 0;
139
    }
140
141
    public function stop()
142
    {
143
        $config = $this->getConfig();
144
        $pidFile = $config['server']['swoole']['pid_file'];
145
        if (!file_exists($pidFile)) {
146
            $this->warning('It seems that Swoole is not running.');
147
            return 0;
148
        }
149
150
        $pid = file_get_contents($pidFile);
151
        if (self::kill($pid, 0)) {
152
            if (self::kill($pid, SIGTERM)) {
153
                // Make sure that master process quit
154
                $time = 1;
155
                $waitTime = isset($config['server']['swoole']['max_wait_time']) ? $config['server']['swoole']['max_wait_time'] : 60;
156
                $this->info("The max time of waiting to forcibly stop is {$waitTime}s.");
157
                while (self::kill($pid, 0)) {
158
                    if ($time > $waitTime) {
159
                        $this->warning("Swoole [PID={$pid}] cannot be stopped gracefully in {$waitTime}s, will be stopped forced right now.");
160
                        return 1;
161
                    }
162
                    $this->info("Waiting Swoole[PID={$pid}] to stop. [{$time}]");
163
                    sleep(1);
164
                    $time++;
165
                }
166
                $basePath = dirname($pidFile);
167
                $deleteFiles = [
168
                    $pidFile,
169
                    $basePath . '/laravels-custom-processes.pid',
170
                    $basePath . '/laravels-timer-process.pid',
171
                ];
172
                foreach ($deleteFiles as $deleteFile) {
173
                    if (file_exists($deleteFile)) {
174
                        unlink($deleteFile);
175
                    }
176
                }
177
                $this->info("Swoole [PID={$pid}] is stopped.");
178
                return 0;
179
            } else {
180
                $this->error("Swoole [PID={$pid}] is stopped failed.");
181
                return 1;
182
            }
183
        } else {
184
            $this->warning("Swoole [PID={$pid}] does not exist, or permission denied.");
185
            return 0;
186
        }
187
    }
188
189
    public function restart()
190
    {
191
        $code = $this->stop();
192
        if ($code !== 0) {
193
            return $code;
194
        }
195
        return $this->start();
196
    }
197
198
    public function reload()
199
    {
200
        $config = $this->getConfig();
201
        $pidFile = $config['server']['swoole']['pid_file'];
202
        if (!file_exists($pidFile)) {
203
            $this->error('It seems that Swoole is not running.');
204
            return 1;
205
        }
206
207
        // Reload worker processes
208
        $pid = file_get_contents($pidFile);
209
        if (!$pid || !self::kill($pid, 0)) {
210
            $this->error("Swoole [PID={$pid}] does not exist, or permission denied.");
211
            return 1;
212
        }
213
        if (self::kill($pid, SIGUSR1)) {
214
            $this->info("Swoole [PID={$pid}] is reloaded.");
215
        } else {
216
            $this->error("Swoole [PID={$pid}] is reloaded failed.");
217
        }
218
219
        // Reload custom processes
220
        $pidFile = dirname($pidFile) . '/laravels-custom-processes.pid';
221
        if (file_exists($pidFile)) {
222
            $pids = (array)explode("\n", trim(file_get_contents($pidFile)));
223
            unlink($pidFile);
224
            foreach ($pids as $pid) {
225
                if (!$pid || !self::kill($pid, 0)) {
226
                    $this->error("Custom process[PID={$pid}] does not exist, or permission denied.");
227
                    continue;
228
                }
229
230
                if (self::kill($pid, SIGUSR1)) {
231
                    $this->info("Custom process[PID={$pid}] is reloaded.");
232
                } else {
233
                    $this->error("Custom process[PID={$pid}] is reloaded failed.");
234
                }
235
            }
236
        }
237
238
        // Reload timer process
239
        if (!empty($config['server']['timer']['enable'])) {
240
            $pidFile = dirname($pidFile) . '/laravels-timer-process.pid';
241
            $pid = file_get_contents($pidFile);
242
            if (!$pid || !self::kill($pid, 0)) {
243
                $this->error("Timer process[PID={$pid}] does not exist, or permission denied.");
244
                return 1;
245
            }
246
247
            if (self::kill($pid, SIGUSR1)) {
248
                $this->info("Timer process[PID={$pid}] is reloaded.");
249
            } else {
250
                $this->error("Timer process[PID={$pid}] is reloaded failed.");
251
            }
252
        }
253
        return 0;
254
    }
255
256
    public function showInfo()
257
    {
258
        return $this->runArtisanCommand('laravels info');
259
    }
260
261
    public function makeArtisanCmd($subCmd)
262
    {
263
        $phpCmd = sprintf('%s -c "%s"', PHP_BINARY, php_ini_loaded_file());
264
        $env = $this->input->getOption('env');
265
        $envs = $env ? "APP_ENV={$env}" : '';
266
        return trim(sprintf('%s %s %s/artisan %s', $envs, $phpCmd, $this->basePath, $subCmd));
267
    }
268
269
    public function runArtisanCommand($cmd)
270
    {
271
        $cmd = $this->makeArtisanCmd($cmd);
272
        return self::runCommand($cmd);
273
    }
274
275
    public function getConfig()
276
    {
277
        $json = file_get_contents($this->basePath . '/storage/laravels.json');
278
        return (array)json_decode($json, true);
279
    }
280
281
    public static function runCommand($cmd, $input = null)
282
    {
283
        $fp = popen($cmd, 'w');
284
        if ($fp === false) {
285
            return false;
286
        }
287
        if ($input !== null) {
288
            $bytes = fwrite($fp, $input);
289
            if ($bytes === false) {
290
                return 1;
291
            }
292
        }
293
        return pclose($fp);
294
    }
295
296
    public static function kill($pid, $sig)
297
    {
298
        try {
299
            return Process::kill((int)$pid, $sig);
300
        } catch (\Exception $e) {
301
            return false;
302
        }
303
    }
304
}
305