Skip to content

Commit a52f623

Browse files
[9.x] Improves serve Artisan command (#43375)
* Improves `serve` Artisan command * Handles better edge cases * Removes assets requests, and displays unexpected output as "warn" * Simplifies command and handles assets * Better captures * formatting Co-authored-by: Taylor Otwell <[email protected]>
1 parent 990f43e commit a52f623

File tree

1 file changed

+73
-6
lines changed

1 file changed

+73
-6
lines changed

src/Illuminate/Foundation/Console/ServeCommand.php

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
namespace Illuminate\Foundation\Console;
44

55
use Illuminate\Console\Command;
6+
use Illuminate\Support\Carbon;
67
use Illuminate\Support\Env;
78
use Symfony\Component\Console\Attribute\AsCommand;
89
use Symfony\Component\Console\Input\InputOption;
910
use Symfony\Component\Process\PhpExecutableFinder;
1011
use Symfony\Component\Process\Process;
12+
use function Termwind\terminal;
1113

1214
#[AsCommand(name: 'serve')]
1315
class ServeCommand extends Command
@@ -44,6 +46,13 @@ class ServeCommand extends Command
4446
*/
4547
protected $portOffset = 0;
4648

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+
4756
/**
4857
* The environment variables that should be passed from host machine to the PHP server process.
4958
*
@@ -69,8 +78,6 @@ class ServeCommand extends Command
6978
*/
7079
public function handle()
7180
{
72-
$this->line("<info>Starting Laravel development server:</info> http://{$this->host()}:{$this->port()}");
73-
7481
$environmentFile = $this->option('env')
7582
? base_path('.env').'.'.$this->option('env')
7683
: base_path('.env');
@@ -93,7 +100,9 @@ public function handle()
93100
filemtime($environmentFile) > $environmentLastModified) {
94101
$environmentLastModified = filemtime($environmentFile);
95102

96-
$this->comment('Environment modified. Restarting server...');
103+
$this->newLine();
104+
105+
$this->components->info('Environment modified. Restarting server...');
97106

98107
$process->stop(5);
99108

@@ -130,9 +139,7 @@ protected function startProcess($hasEnvironment)
130139
return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false];
131140
})->all());
132141

133-
$process->start(function ($type, $buffer) {
134-
$this->output->write($buffer);
135-
});
142+
$process->start($this->handleProcessOutput());
136143

137144
return $process;
138145
}
@@ -212,6 +219,66 @@ protected function canTryAnotherPort()
212219
($this->input->getOption('tries') > $this->portOffset);
213220
}
214221

222+
/**
223+
* Returns a "callable" to handle the process output.
224+
*
225+
* @return callable(string, string): void
226+
*/
227+
protected function handleProcessOutput()
228+
{
229+
return fn ($type, $buffer) => str($buffer)->explode(PHP_EOL)->each(function ($line) {
230+
$parts = explode(']', $line);
231+
232+
if (str($line)->contains('Development Server (http')) {
233+
$this->components->info("Server running on [http://{$this->host()}:{$this->port()}].");
234+
$this->comment(' <fg=yellow;options=bold>Press Ctrl+C to stop the server</>');
235+
236+
$this->newLine();
237+
} elseif (str($line)->contains(' Accepted')) {
238+
$startDate = Carbon::createFromFormat('D M d H:i:s Y', ltrim($parts[0], '['));
239+
240+
preg_match('/\:(\d+)/', $parts[1], $matches);
241+
242+
$this->requestsPool[$matches[1]] = [$startDate, false];
243+
} elseif (str($line)->contains([' [200]: GET '])) {
244+
preg_match('/\:(\d+)/', $parts[1], $matches);
245+
246+
$this->requestsPool[$matches[1]][1] = trim(explode('[200]: GET', $line)[1]);
247+
} elseif (str($line)->contains(' Closing')) {
248+
preg_match('/\:(\d+)/', $parts[1], $matches);
249+
250+
$request = $this->requestsPool[$matches[1]];
251+
252+
[$startDate, $file] = $request;
253+
$formattedStartedAt = $startDate->format('Y-m-d H:i:s');
254+
255+
unset($this->requestsPool[$matches[1]]);
256+
257+
[$date, $time] = explode(' ', $formattedStartedAt);
258+
259+
$this->output->write(" <fg=gray>$date</> $time");
260+
261+
$runTime = Carbon::createFromFormat('D M d H:i:s Y', ltrim($parts[0], '['))
262+
->diffInSeconds($startDate);
263+
264+
if ($file) {
265+
$this->output->write($file = " $file");
266+
}
267+
268+
$dots = max(terminal()->width() - mb_strlen($formattedStartedAt) - mb_strlen($file) - mb_strlen($runTime) - 9, 0);
269+
270+
$this->output->write(' '.str_repeat('<fg=gray>.</>', $dots));
271+
$this->output->writeln(" <fg=gray>~ {$runTime}s</>");
272+
} elseif (str($line)->contains(['Closed without sending a request', ']: '])) {
273+
// ...
274+
} elseif (isset($parts[1])) {
275+
$this->components->warn($parts[1]);
276+
} elseif (! empty($line)) {
277+
$this->components->warn($line);
278+
}
279+
});
280+
}
281+
215282
/**
216283
* Get the console command options.
217284
*

0 commit comments

Comments
 (0)