Completed
Push — master ( d61801...46edea )
by Biao
05:21
created

Portal::runArtisanCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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