From 75d9e6c2ac7f7a0a4b73bbdffb7b49ec1c41609f Mon Sep 17 00:00:00 2001 From: Julian Prieber Date: Mon, 15 May 2023 19:31:16 +0200 Subject: [PATCH] Restored serve command Use: `php artisan serve` to provide a temporary development server --- .../Foundation/Console/ServeCommand.php | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php new file mode 100644 index 0000000..acf0d1d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php @@ -0,0 +1,340 @@ + + */ + protected $requestsPool; + + /** + * Indicates if the "Server running on..." output message has been displayed. + * + * @var bool + */ + protected $serverRunningHasBeenDisplayed = false; + + /** + * The environment variables that should be passed from host machine to the PHP server process. + * + * @var string[] + */ + public static $passthroughVariables = [ + 'APP_ENV', + 'LARAVEL_SAIL', + 'PATH', + 'PHP_CLI_SERVER_WORKERS', + 'PHP_IDE_CONFIG', + 'SYSTEMROOT', + 'XDEBUG_CONFIG', + 'XDEBUG_MODE', + 'XDEBUG_SESSION', + ]; + + /** + * Execute the console command. + * + * @return int + * + * @throws \Exception + */ + public function handle() + { + $environmentFile = $this->option('env') + ? base_path('.env').'.'.$this->option('env') + : base_path('.env'); + + $hasEnvironment = file_exists($environmentFile); + + $environmentLastModified = $hasEnvironment + ? filemtime($environmentFile) + : now()->addDays(30)->getTimestamp(); + + $process = $this->startProcess($hasEnvironment); + + while ($process->isRunning()) { + if ($hasEnvironment) { + clearstatcache(false, $environmentFile); + } + + if (! $this->option('no-reload') && + $hasEnvironment && + filemtime($environmentFile) > $environmentLastModified) { + $environmentLastModified = filemtime($environmentFile); + + $this->newLine(); + + $this->components->info('Environment modified. Restarting server...'); + + $process->stop(5); + + $this->serverRunningHasBeenDisplayed = false; + + $process = $this->startProcess($hasEnvironment); + } + + usleep(500 * 1000); + } + + $status = $process->getExitCode(); + + if ($status && $this->canTryAnotherPort()) { + $this->portOffset += 1; + + return $this->handle(); + } + + return $status; + } + + /** + * Start a new server process. + * + * @param bool $hasEnvironment + * @return \Symfony\Component\Process\Process + */ + protected function startProcess($hasEnvironment) + { + $process = new Process($this->serverCommand(), base_path(), collect($_ENV)->mapWithKeys(function ($value, $key) use ($hasEnvironment) { + if ($this->option('no-reload') || ! $hasEnvironment) { + return [$key => $value]; + } + + return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false]; + })->all()); + + $process->start($this->handleProcessOutput()); + + return $process; + } + + /** + * Get the full server command. + * + * @return array + */ + protected function serverCommand() + { + $server = file_exists(base_path('server.php')) + ? base_path('server.php') + : __DIR__.'/../resources/server.php'; + + return [ + (new PhpExecutableFinder)->find(false), + '-S', + $this->host().':'.$this->port(), + $server, + ]; + } + + /** + * Get the host for the command. + * + * @return string + */ + protected function host() + { + [$host] = $this->getHostAndPort(); + + return $host; + } + + /** + * Get the port for the command. + * + * @return string + */ + protected function port() + { + $port = $this->input->getOption('port'); + + if (is_null($port)) { + [, $port] = $this->getHostAndPort(); + } + + $port = $port ?: 8000; + + return $port + $this->portOffset; + } + + /** + * Get the host and port from the host option string. + * + * @return array + */ + protected function getHostAndPort() + { + $hostParts = explode(':', $this->input->getOption('host')); + + return [ + $hostParts[0], + $hostParts[1] ?? null, + ]; + } + + /** + * Check if the command has reached its maximum number of port tries. + * + * @return bool + */ + protected function canTryAnotherPort() + { + return is_null($this->input->getOption('port')) && + ($this->input->getOption('tries') > $this->portOffset); + } + + /** + * Returns a "callable" to handle the process output. + * + * @return callable(string, string): void + */ + protected function handleProcessOutput() + { + return fn ($type, $buffer) => str($buffer)->explode("\n")->each(function ($line) { + if (str($line)->contains('Development Server (http')) { + if ($this->serverRunningHasBeenDisplayed) { + return; + } + + $this->components->info("Server running on [http://{$this->host()}:{$this->port()}]."); + $this->comment(' Press Ctrl+C to stop the server'); + + $this->newLine(); + + $this->serverRunningHasBeenDisplayed = true; + } elseif (str($line)->contains(' Accepted')) { + $requestPort = $this->getRequestPortFromLine($line); + + $this->requestsPool[$requestPort] = [ + $this->getDateFromLine($line), + false, + ]; + } elseif (str($line)->contains([' [200]: GET '])) { + $requestPort = $this->getRequestPortFromLine($line); + + $this->requestsPool[$requestPort][1] = trim(explode('[200]: GET', $line)[1]); + } elseif (str($line)->contains(' Closing')) { + $requestPort = $this->getRequestPortFromLine($line); + $request = $this->requestsPool[$requestPort]; + + [$startDate, $file] = $request; + + $formattedStartedAt = $startDate->format('Y-m-d H:i:s'); + + unset($this->requestsPool[$requestPort]); + + [$date, $time] = explode(' ', $formattedStartedAt); + + $this->output->write(" $date $time"); + + $runTime = $this->getDateFromLine($line)->diffInSeconds($startDate); + + if ($file) { + $this->output->write($file = " $file"); + } + + $dots = max(terminal()->width() - mb_strlen($formattedStartedAt) - mb_strlen($file) - mb_strlen($runTime) - 9, 0); + + $this->output->write(' '.str_repeat('.', $dots)); + $this->output->writeln(" ~ {$runTime}s"); + } elseif (str($line)->contains(['Closed without sending a request'])) { + // ... + } elseif (! empty($line)) { + $warning = explode('] ', $line); + $this->components->warn(count($warning) > 1 ? $warning[1] : $warning[0]); + } + }); + } + + /** + * Get the date from the given PHP server output. + * + * @param string $line + * @return \Illuminate\Support\Carbon + */ + protected function getDateFromLine($line) + { + $regex = env('PHP_CLI_SERVER_WORKERS', 1) > 1 + ? '/^\[\d+]\s\[([a-zA-Z0-9: ]+)\]/' + : '/^\[([^\]]+)\]/'; + + preg_match($regex, $line, $matches); + + return Carbon::createFromFormat('D M d H:i:s Y', $matches[1]); + } + + /** + * Get the request port from the given PHP server output. + * + * @param string $line + * @return int + */ + protected function getRequestPortFromLine($line) + { + preg_match('/:(\d+)\s(?:(?:\w+$)|(?:\[.*))/', $line, $matches); + + return (int) $matches[1]; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on', Env::get('SERVER_HOST', '127.0.0.1')], + ['port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on', Env::get('SERVER_PORT')], + ['tries', null, InputOption::VALUE_OPTIONAL, 'The max number of ports to attempt to serve from', 10], + ['no-reload', null, InputOption::VALUE_NONE, 'Do not reload the development server on .env file changes'], + ]; + } +}