Subversion-Projekte lars-tiefland.laravel_shop

Revision

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