$headers */ private function __construct( public readonly string $method, public readonly string $path, public readonly array $query, public readonly array $body, private readonly array $headers, ) { } public static function fromGlobals(): self { $method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); $uri = $_SERVER['REQUEST_URI'] ?? '/'; $path = rtrim((string) parse_url($uri, PHP_URL_PATH), '/'); if ($path === '') { $path = '/'; } parse_str((string) (parse_url($uri, PHP_URL_QUERY) ?? ''), $query); $rawBody = file_get_contents('php://input') ?: ''; $body = []; if ($rawBody !== '') { $decoded = json_decode($rawBody, true); if (is_array($decoded)) { $body = $decoded; } } $headers = []; foreach ($_SERVER as $key => $value) { if (str_starts_with($key, 'HTTP_')) { $name = str_replace('_', '-', strtolower(substr($key, 5))); $headers[$name] = (string) $value; } } if (isset($_SERVER['CONTENT_TYPE'])) { $headers['content-type'] = (string) $_SERVER['CONTENT_TYPE']; } return new self($method, $path, $query, $body, $headers); } /** @param array $body */ public static function create(string $method, string $path, array $query = [], array $body = [], array $headers = []): self { $normalized = []; foreach ($headers as $name => $value) { $normalized[strtolower($name)] = $value; } return new self($method, rtrim($path, '/') ?: '/', $query, $body, $normalized); } public function header(string $name): ?string { return $this->headers[strtolower($name)] ?? null; } }