Passed
Push — master ( 2fb599...6151e8 )
by Biao
03:59
created

LaravelSCommand::publish()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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