쿼리 파라미터 > 기본값) $requestedVersion = $this->getRequestedVersion($request); // 2. 실제 사용할 버전 결정 (fallback 적용) $actualVersion = $this->resolveVersion($request, $requestedVersion); // 3. 요청에 버전 정보 저장 (컨트롤러에서 사용 가능) $request->attributes->set('api_version', $actualVersion); $request->attributes->set('api_version_requested', $requestedVersion); $request->attributes->set('api_version_fallback', $actualVersion !== $requestedVersion); // 4. 요청 처리 $response = $next($request); // 5. 응답 헤더에 버전 정보 추가 $response->headers->set('X-API-Version', $actualVersion); if ($actualVersion !== $requestedVersion) { $response->headers->set('X-API-Version-Fallback', 'true'); $response->headers->set('X-API-Version-Requested', $requestedVersion); } return $response; } /** * 요청에서 버전 정보 추출 */ protected function getRequestedVersion(Request $request): string { // 1. Accept-Version 헤더 (권장) $version = $request->header('Accept-Version'); if ($version && $this->isValidVersion($version)) { return $version; } // 2. X-API-Version 헤더 (대안) $version = $request->header('X-API-Version'); if ($version && $this->isValidVersion($version)) { return $version; } // 3. 쿼리 파라미터 (테스트용) $version = $request->query('api_version'); if ($version && $this->isValidVersion($version)) { return $version; } return $this->defaultVersion; } /** * 유효한 버전인지 확인 */ protected function isValidVersion(string $version): bool { return in_array($version, $this->supportedVersions, true); } /** * 실제 사용할 버전 결정 (fallback 로직) */ protected function resolveVersion(Request $request, string $requestedVersion): string { // 요청된 버전부터 하위 버전까지 순차 확인 $startIndex = array_search($requestedVersion, $this->supportedVersions, true); if ($startIndex === false) { return $this->defaultVersion; } // 요청된 버전부터 하위 버전까지 체크 for ($i = $startIndex; $i < count($this->supportedVersions); $i++) { $version = $this->supportedVersions[$i]; if ($this->versionRouteExists($request, $version)) { return $version; } } // 모든 버전에서 라우트를 찾지 못하면 기본값 반환 return $this->defaultVersion; } /** * 해당 버전의 라우트가 존재하는지 확인 */ protected function versionRouteExists(Request $request, string $version): bool { $path = $request->path(); // URL에서 버전 부분 교체 // /api/v1/users → /api/v2/users $versionedPath = preg_replace('/^api\/v\d+/', "api/{$version}", $path); // 해당 경로의 라우트가 존재하는지 확인 $routes = Route::getRoutes(); foreach ($routes as $route) { $routeUri = $route->uri(); // 정확히 일치하거나 파라미터 패턴 매칭 if ($this->matchesRoute($versionedPath, $routeUri, $request->method())) { return true; } } return false; } /** * 경로가 라우트와 일치하는지 확인 */ protected function matchesRoute(string $path, string $routeUri, string $method): bool { // 라우트 URI의 파라미터를 정규식으로 변환 // {id} → [^/]+ $pattern = preg_replace('/\{[^}]+\}/', '[^/]+', $routeUri); $pattern = '#^'.$pattern.'$#'; return (bool) preg_match($pattern, $path); } /** * 지원 버전 목록 반환 (외부에서 사용) */ public function getSupportedVersions(): array { return $this->supportedVersions; } /** * 기본 버전 반환 */ public function getDefaultVersion(): string { return $this->defaultVersion; } }