Passed
Push — master ( 78972e...07f594 )
by Biao
08:04 queued 02:48
created

LaravelSCommand::preSet()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 64
nop 1
dl 0
loc 19
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Hhxsv5\LaravelS\Illuminate;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Filesystem\Filesystem;
7
8
class LaravelSCommand extends Command
9
{
10
    protected $signature = 'laravels';
11
12
    protected $description = 'LaravelS console tool';
13
14
    protected $actions;
15
16
    protected $isLumen = false;
17
18
    public function __construct()
19
    {
20
        $this->actions = ['start', 'stop', 'restart', 'reload', 'publish'];
21
        $actions = implode('|', $this->actions);
22
        $this->signature .= sprintf(
23
            ' {action : %s} {--d|daemonize : Whether run as a daemon for start & restart} {--i|ignore : Whether ignore checking process pid for start & restart}',
24
            $actions
25
        );
26
        $this->description .= ': ' . $actions;
27
28
        parent::__construct();
29
    }
30
31
    public function fire()
32
    {
33
        $this->handle();
34
    }
35
36
    public function handle()
37
    {
38
        $action = (string)$this->argument('action');
39
        if (!in_array($action, $this->actions, true)) {
40
            $this->warn(sprintf(
41
                    'LaravelS: action %s is not available, only support %s',
42
                    $action,
43
                    implode('|', $this->actions)
44
                )
45
            );
46
            return 127;
47
        }
48
49
        $this->isLumen = stripos($this->getApplication()->getVersion(), 'Lumen') !== false;
50
        $this->loadConfigManually();
51
        return $this->{$action}();
52
    }
53
54
    protected function loadConfigManually()
55
    {
56
        // Load configuration laravel.php manually for Lumen
57
        $basePath = config('laravels.laravel_base_path') ?: base_path();
58
        if ($this->isLumen && file_exists($basePath . '/config/laravels.php')) {
59
            $this->getLaravel()->configure('laravels');
0 ignored issues
show
Bug introduced by
The method configure() does not exist on Illuminate\Contracts\Foundation\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

59
            $this->getLaravel()->/** @scrutinizer ignore-call */ configure('laravels');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
60
        }
61
    }
62
63
    protected function outputLogo()
64
    {
65
        static $logo = <<<EOS
66
 _                               _  _____ 
67
| |                             | |/ ____|
68
| |     __ _ _ __ __ ___   _____| | (___  
69
| |    / _` | '__/ _` \ \ / / _ \ |\___ \ 
70
| |___| (_| | | | (_| |\ V /  __/ |____) |
71
|______\__,_|_|  \__,_| \_/ \___|_|_____/ 
72
                                           
73
EOS;
74
        $this->info($logo);
75
        $this->info('Speed up your Laravel/Lumen');
76
        $this->table(['Component', 'Version'], [
77
            ['Component' => 'PHP', 'Version' => phpversion()],
78
            ['Component' => 'Swoole', 'Version' => \swoole_version()],
79
            ['Component' => $this->getApplication()->getName(), 'Version' => $this->getApplication()->getVersion()],
80
        ]);
81
    }
82
83
    protected function preSet(array &$svrConf)
84
    {
85
        if (!isset($svrConf['enable_gzip'])) {
86
            $svrConf['enable_gzip'] = false;
87
        }
88
        if (empty($svrConf['laravel_base_path'])) {
89
            $svrConf['laravel_base_path'] = base_path();
90
        }
91
        if (empty($svrConf['process_prefix'])) {
92
            $svrConf['process_prefix'] = $svrConf['laravel_base_path'];
93
        }
94
        if (empty($svrConf['swoole']['document_root'])) {
95
            $svrConf['swoole']['document_root'] = $svrConf['laravel_base_path'] . '/public';
96
        }
97
        if ($this->option('daemonize')) {
98
            $svrConf['swoole']['daemonize'] = true;
99
        }
100
        if (empty($svrConf['swoole']['pid_file'])) {
101
            $svrConf['swoole']['pid_file'] = storage_path('laravels.pid');
102
        }
103
    }
104
105
    protected function preCheck(array $svrConf)
106
    {
107
        if (!empty($svrConf['enable_gzip']) && version_compare(\swoole_version(), '4.1.0', '>=')) {
108
            $this->error('LaravelS: enable_gzip is DEPRECATED since Swoole 4.1.0, set http_compression of Swoole instead, http_compression is disabled by default.');
109
            $this->info('LaravelS: if there is a proxy server like Nginx, suggest that enable gzip in Nginx and disable gzip in Swoole, to avoid the repeated gzip compression for response.');
110
            return 1;
111
        }
112
        if (!empty($svrConf['events'])) {
113
            if (empty($svrConf['swoole']['task_worker_num']) || $svrConf['swoole']['task_worker_num'] <= 0) {
114
                $this->error('LaravelS: Asynchronous event listening needs to set task_worker_num > 0');
115
                return 1;
116
            }
117
        }
118
        return 0;
119
    }
120
121
    protected function start()
122
    {
123
        $this->outputLogo();
124
125
        $svrConf = config('laravels');
126
127
        $this->preSet($svrConf);
128
129
        $ret = $this->preCheck($svrConf);
130
        if ($ret !== 0) {
131
            return $ret;
132
        }
133
134
        $laravelConf = [
135
            'root_path'          => $svrConf['laravel_base_path'],
136
            'static_path'        => $svrConf['swoole']['document_root'],
137
            'register_providers' => array_unique((array)array_get($svrConf, 'register_providers', [])),
138
            'is_lumen'           => $this->isLumen,
139
            '_SERVER'            => $_SERVER,
140
            '_ENV'               => $_ENV,
141
        ];
142
143
        if (isset($svrConf['socket_type'])
144
            && in_array($svrConf['socket_type'], [\SWOOLE_UNIX_DGRAM, \SWOOLE_UNIX_STREAM])
145
        ) {
146
            $listenAt = $svrConf['listen_ip'];
147
        } else {
148
            $listenAt = sprintf('%s:%s', $svrConf['listen_ip'], $svrConf['listen_port']);
149
        }
150
151
        if (!$this->option('ignore') && file_exists($svrConf['swoole']['pid_file'])) {
152
            $pid = (int)file_get_contents($svrConf['swoole']['pid_file']);
153
            if ($pid > 0 && $this->killProcess($pid, 0)) {
154
                $this->warn(sprintf('LaravelS: PID[%s] is already running at %s.', $pid, $listenAt));
155
                return 1;
156
            }
157
        }
158
159
        if (!$svrConf['swoole']['daemonize']) {
160
            $this->info(sprintf('LaravelS: Swoole is listening at %s, press Ctrl+C to quit.', $listenAt));
161
        }
162
163
        // Implements gracefully reload, avoid including laravel's files before worker start
164
        $cmd = sprintf('%s -c "%s" %s/../GoLaravelS.php', PHP_BINARY, php_ini_loaded_file(), __DIR__);
165
        $ret = $this->popen($cmd, json_encode(compact('svrConf', 'laravelConf')));
166
        if ($ret === false) {
167
            $this->error('LaravelS: popen ' . $cmd . ' failed');
168
            return 1;
169
        }
170
171
        $pidFile = $svrConf['swoole']['pid_file'];
172
173
        // Make sure that master process started
174
        $time = 0;
175
        while (!file_exists($pidFile) && $time <= 20) {
176
            usleep(100000);
177
            $time++;
178
        }
179
180
        if (file_exists($pidFile)) {
181
            $this->info(sprintf('LaravelS: PID[%s] is listening at %s.', file_get_contents($pidFile), $listenAt));
182
            return 0;
183
        } else {
184
            $this->error(sprintf('LaravelS: PID file[%s] does not exist.', $pidFile));
185
            return 1;
186
        }
187
    }
188
189
    protected function popen($cmd, $input = null)
190
    {
191
        $fp = popen($cmd, 'w');
192
        if ($fp === false) {
193
            return false;
194
        }
195
        if ($input !== null) {
196
            fwrite($fp, $input);
197
        }
198
        pclose($fp);
199
        return true;
200
    }
201
202
    protected function stop()
203
    {
204
        $pidFile = config('laravels.swoole.pid_file') ?: storage_path('laravels.pid');
205
        if (!file_exists($pidFile)) {
206
            $this->info('LaravelS: already stopped.');
207
            return 0;
208
        }
209
210
        $pid = (int)file_get_contents($pidFile);
211
        if ($this->killProcess($pid, 0)) {
212
            if ($this->killProcess($pid, SIGTERM)) {
213
                // Make sure that master process quit
214
                $time = 1;
215
                $waitTime = config('laravels.swoole.max_wait_time', 60);
216
                while ($this->killProcess($pid, 0)) {
217
                    if ($time > $waitTime) {
218
                        $this->error("LaravelS: PID[{$pid}] cannot be stopped gracefully in {$waitTime}s, will be stopped forced right now.");
219
                        return 1;
220
                    }
221
                    $this->warn("LaravelS: Waiting PID[{$pid}] to stop. [{$time}]");
222
                    sleep(1);
223
                    $time++;
224
                }
225
                if (file_exists($pidFile)) {
226
                    unlink($pidFile);
227
                }
228
                $this->info("LaravelS: PID[{$pid}] is stopped.");
229
                return 0;
230
            } else {
231
                $this->error("LaravelS: PID[{$pid}] is stopped failed.");
232
                return 1;
233
            }
234
        } else {
235
            $this->warn("LaravelS: PID[{$pid}] does not exist, or permission denied.");
236
            if (file_exists($pidFile)) {
237
                unlink($pidFile);
238
            }
239
            return $this->option('ignore') ? 0 : 1;
240
        }
241
    }
242
243
    protected function restart()
244
    {
245
        $exitCode = $this->stop();
246
        if ($exitCode !== 0) {
247
            return $exitCode;
248
        }
249
        return $this->start();
250
    }
251
252
    protected function reload()
253
    {
254
        $pidFile = config('laravels.swoole.pid_file') ?: storage_path('laravels.pid');
255
        if (!file_exists($pidFile)) {
256
            $this->error('LaravelS: it seems that LaravelS is not running.');
257
            return 1;
258
        }
259
260
        $pid = (int)file_get_contents($pidFile);
261
        if (!$this->killProcess($pid, 0)) {
262
            $this->error("LaravelS: PID[{$pid}] does not exist, or permission denied.");
263
            return 1;
264
        }
265
266
        if ($this->killProcess($pid, SIGUSR1)) {
267
            $now = date('Y-m-d H:i:s');
268
            $this->info("LaravelS: PID[{$pid}] is reloaded at {$now}.");
269
            return 0;
270
        } else {
271
            $this->error("LaravelS: PID[{$pid}] is reloaded failed.");
272
            return 1;
273
        }
274
    }
275
276
    protected function publish()
277
    {
278
        $basePath = config('laravels.laravel_base_path') ?: base_path();
279
        $to = $basePath . '/config/laravels.php';
280
        if (file_exists($to)) {
281
            $choice = $this->anticipate($to . ' already exists, do you want to override it ? Y/N', ['Y', 'N'], 'N');
282
            if (!$choice || strtoupper($choice) !== 'Y') {
283
                $this->info('Publishing skipped.');
284
                return 0;
285
            }
286
        }
287
288
        try {
289
            return $this->call('vendor:publish', ['--provider' => LaravelSServiceProvider::class, '--force' => true]);
290
        } catch (\InvalidArgumentException $e) {
291
            // do nothing.
292
        } catch (\Exception $e) {
293
            throw $e;
294
        }
295
296
        $from = __DIR__ . '/../../config/laravels.php';
297
        $toDir = dirname($to);
298
299
        /**
300
         * @var Filesystem $files
301
         */
302
        $files = app(Filesystem::class);
303
304
        if (!$files->isDirectory($toDir)) {
305
            $files->makeDirectory($toDir, 0755, true);
306
        }
307
308
        $files->copy($from, $to);
309
310
        $from = str_replace($basePath, '', realpath($from));
311
312
        $to = str_replace($basePath, '', realpath($to));
313
314
        $this->line('<info>Copied File</info> <comment>[' . $from . ']</comment> <info>To</info> <comment>[' . $to . ']</comment>');
315
        return 0;
316
    }
317
318
    protected function killProcess($pid, $sig)
319
    {
320
        try {
321
            return \swoole_process::kill($pid, $sig);
322
        } catch (\Exception $e) {
323
            return false;
324
        }
325
    }
326
}
327