Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Foundation\Console;
4
 
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Carbon;
7
use Illuminate\Support\Env;
8
use Symfony\Component\Console\Attribute\AsCommand;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Process\PhpExecutableFinder;
11
use Symfony\Component\Process\Process;
991 lars 12
 
148 lars 13
use function Termwind\terminal;
14
 
15
#[AsCommand(name: 'serve')]
16
class ServeCommand extends Command
17
{
18
    /**
19
     * The console command name.
20
     *
21
     * @var string
22
     */
23
    protected $name = 'serve';
24
 
25
    /**
26
     * The name of the console command.
27
     *
28
     * This name is used to identify the command during lazy loading.
29
     *
30
     * @var string|null
31
     *
32
     * @deprecated
33
     */
34
    protected static $defaultName = 'serve';
35
 
36
    /**
37
     * The console command description.
38
     *
39
     * @var string
40
     */
41
    protected $description = 'Serve the application on the PHP development server';
42
 
43
    /**
44
     * The current port offset.
45
     *
46
     * @var int
47
     */
48
    protected $portOffset = 0;
49
 
50
    /**
51
     * The list of requests being handled and their start time.
52
     *
53
     * @var array<int, \Illuminate\Support\Carbon>
54
     */
55
    protected $requestsPool;
56
 
57
    /**
58
     * Indicates if the "Server running on..." output message has been displayed.
59
     *
60
     * @var bool
61
     */
62
    protected $serverRunningHasBeenDisplayed = false;
63
 
64
    /**
65
     * The environment variables that should be passed from host machine to the PHP server process.
66
     *
67
     * @var string[]
68
     */
69
    public static $passthroughVariables = [
70
        'APP_ENV',
71
        'LARAVEL_SAIL',
72
        'PATH',
73
        'PHP_CLI_SERVER_WORKERS',
74
        'PHP_IDE_CONFIG',
75
        'SYSTEMROOT',
76
        'XDEBUG_CONFIG',
77
        'XDEBUG_MODE',
78
        'XDEBUG_SESSION',
79
    ];
80
 
81
    /**
82
     * Execute the console command.
83
     *
84
     * @return int
85
     *
86
     * @throws \Exception
87
     */
88
    public function handle()
89
    {
90
        $environmentFile = $this->option('env')
91
                            ? base_path('.env').'.'.$this->option('env')
92
                            : base_path('.env');
93
 
94
        $hasEnvironment = file_exists($environmentFile);
95
 
96
        $environmentLastModified = $hasEnvironment
97
                            ? filemtime($environmentFile)
98
                            : now()->addDays(30)->getTimestamp();
99
 
100
        $process = $this->startProcess($hasEnvironment);
101
 
102
        while ($process->isRunning()) {
103
            if ($hasEnvironment) {
104
                clearstatcache(false, $environmentFile);
105
            }
106
 
107
            if (! $this->option('no-reload') &&
108
                $hasEnvironment &&
109
                filemtime($environmentFile) > $environmentLastModified) {
110
                $environmentLastModified = filemtime($environmentFile);
111
 
112
                $this->newLine();
113
 
114
                $this->components->info('Environment modified. Restarting server...');
115
 
116
                $process->stop(5);
117
 
118
                $this->serverRunningHasBeenDisplayed = false;
119
 
120
                $process = $this->startProcess($hasEnvironment);
121
            }
122
 
123
            usleep(500 * 1000);
124
        }
125
 
126
        $status = $process->getExitCode();
127
 
128
        if ($status && $this->canTryAnotherPort()) {
129
            $this->portOffset += 1;
130
 
131
            return $this->handle();
132
        }
133
 
134
        return $status;
135
    }
136
 
137
    /**
138
     * Start a new server process.
139
     *
140
     * @param  bool  $hasEnvironment
141
     * @return \Symfony\Component\Process\Process
142
     */
143
    protected function startProcess($hasEnvironment)
144
    {
145
        $process = new Process($this->serverCommand(), public_path(), collect($_ENV)->mapWithKeys(function ($value, $key) use ($hasEnvironment) {
146
            if ($this->option('no-reload') || ! $hasEnvironment) {
147
                return [$key => $value];
148
            }
149
 
150
            return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false];
151
        })->all());
152
 
153
        $process->start($this->handleProcessOutput());
154
 
155
        return $process;
156
    }
157
 
158
    /**
159
     * Get the full server command.
160
     *
161
     * @return array
162
     */
163
    protected function serverCommand()
164
    {
165
        $server = file_exists(base_path('server.php'))
166
            ? base_path('server.php')
167
            : __DIR__.'/../resources/server.php';
168
 
169
        return [
170
            (new PhpExecutableFinder)->find(false),
171
            '-S',
172
            $this->host().':'.$this->port(),
173
            $server,
174
        ];
175
    }
176
 
177
    /**
178
     * Get the host for the command.
179
     *
180
     * @return string
181
     */
182
    protected function host()
183
    {
184
        [$host] = $this->getHostAndPort();
185
 
186
        return $host;
187
    }
188
 
189
    /**
190
     * Get the port for the command.
191
     *
192
     * @return string
193
     */
194
    protected function port()
195
    {
196
        $port = $this->input->getOption('port');
197
 
198
        if (is_null($port)) {
199
            [, $port] = $this->getHostAndPort();
200
        }
201
 
202
        $port = $port ?: 8000;
203
 
204
        return $port + $this->portOffset;
205
    }
206
 
207
    /**
208
     * Get the host and port from the host option string.
209
     *
210
     * @return array
211
     */
212
    protected function getHostAndPort()
213
    {
214
        $hostParts = explode(':', $this->input->getOption('host'));
215
 
216
        return [
217
            $hostParts[0],
218
            $hostParts[1] ?? null,
219
        ];
220
    }
221
 
222
    /**
223
     * Check if the command has reached its maximum number of port tries.
224
     *
225
     * @return bool
226
     */
227
    protected function canTryAnotherPort()
228
    {
229
        return is_null($this->input->getOption('port')) &&
230
               ($this->input->getOption('tries') > $this->portOffset);
231
    }
232
 
233
    /**
234
     * Returns a "callable" to handle the process output.
235
     *
236
     * @return callable(string, string): void
237
     */
238
    protected function handleProcessOutput()
239
    {
240
        return fn ($type, $buffer) => str($buffer)->explode("\n")->each(function ($line) {
241
            if (str($line)->contains('Development Server (http')) {
242
                if ($this->serverRunningHasBeenDisplayed) {
243
                    return;
244
                }
245
 
246
                $this->components->info("Server running on [http://{$this->host()}:{$this->port()}].");
247
                $this->comment('  <fg=yellow;options=bold>Press Ctrl+C to stop the server</>');
248
 
249
                $this->newLine();
250
 
251
                $this->serverRunningHasBeenDisplayed = true;
252
            } elseif (str($line)->contains(' Accepted')) {
253
                $requestPort = $this->getRequestPortFromLine($line);
254
 
255
                $this->requestsPool[$requestPort] = [
256
                    $this->getDateFromLine($line),
257
                    false,
258
                ];
259
            } elseif (str($line)->contains([' [200]: GET '])) {
260
                $requestPort = $this->getRequestPortFromLine($line);
261
 
262
                $this->requestsPool[$requestPort][1] = trim(explode('[200]: GET', $line)[1]);
263
            } elseif (str($line)->contains(' Closing')) {
264
                $requestPort = $this->getRequestPortFromLine($line);
265
                $request = $this->requestsPool[$requestPort];
266
 
267
                [$startDate, $file] = $request;
268
 
269
                $formattedStartedAt = $startDate->format('Y-m-d H:i:s');
270
 
271
                unset($this->requestsPool[$requestPort]);
272
 
273
                [$date, $time] = explode(' ', $formattedStartedAt);
274
 
275
                $this->output->write("  <fg=gray>$date</> $time");
276
 
277
                $runTime = $this->getDateFromLine($line)->diffInSeconds($startDate);
278
 
279
                if ($file) {
280
                    $this->output->write($file = " $file");
281
                }
282
 
283
                $dots = max(terminal()->width() - mb_strlen($formattedStartedAt) - mb_strlen($file) - mb_strlen($runTime) - 9, 0);
284
 
285
                $this->output->write(' '.str_repeat('<fg=gray>.</>', $dots));
286
                $this->output->writeln(" <fg=gray>~ {$runTime}s</>");
287
            } elseif (str($line)->contains(['Closed without sending a request'])) {
288
                // ...
289
            } elseif (! empty($line)) {
290
                $warning = explode('] ', $line);
291
                $this->components->warn(count($warning) > 1 ? $warning[1] : $warning[0]);
292
            }
293
        });
294
    }
295
 
296
    /**
297
     * Get the date from the given PHP server output.
298
     *
299
     * @param  string  $line
300
     * @return \Illuminate\Support\Carbon
301
     */
302
    protected function getDateFromLine($line)
303
    {
304
        $regex = env('PHP_CLI_SERVER_WORKERS', 1) > 1
305
            ? '/^\[\d+]\s\[([a-zA-Z0-9: ]+)\]/'
306
            : '/^\[([^\]]+)\]/';
307
 
308
        preg_match($regex, $line, $matches);
309
 
310
        return Carbon::createFromFormat('D M d H:i:s Y', $matches[1]);
311
    }
312
 
313
    /**
314
     * Get the request port from the given PHP server output.
315
     *
316
     * @param  string  $line
317
     * @return int
318
     */
319
    protected function getRequestPortFromLine($line)
320
    {
321
        preg_match('/:(\d+)\s(?:(?:\w+$)|(?:\[.*))/', $line, $matches);
322
 
323
        return (int) $matches[1];
324
    }
325
 
326
    /**
327
     * Get the console command options.
328
     *
329
     * @return array
330
     */
331
    protected function getOptions()
332
    {
333
        return [
334
            ['host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on', Env::get('SERVER_HOST', '127.0.0.1')],
335
            ['port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on', Env::get('SERVER_PORT')],
336
            ['tries', null, InputOption::VALUE_OPTIONAL, 'The max number of ports to attempt to serve from', 10],
337
            ['no-reload', null, InputOption::VALUE_NONE, 'Do not reload the development server on .env file changes'],
338
        ];
339
    }
340
}