shouldHandle($request)) { return $next($request); } $host = $request->getHost(); try { // Domain-Context mit Caching erstellen (KEIN Session-Zugriff!) $context = $this->resolveDomainContext($host); // Frühe Konfiguration ohne Session-Zugriff $this->configureApplication($context); // UserShop-Domains: PostRoute für korrekte Card-URLs setzen $this->configurePostRoute($context); // Context verfügbar machen $this->registerContext($context, $request); // UserShop-Routing: subdomain aus Route-Parametern entfernen $this->cleanupRouteParameters($request, $context); // Minimal Debug-Logging für Production $this->logDomainResolution($context, $host); } catch (\Throwable $e) { // Graceful Degradation: Bei Fehlern System nicht stoppen Log::error('DomainBootstrap failed', [ 'host' => $host, 'error' => $e->getMessage(), 'fallback' => 'using_main_domain' ]); // Fallback: Main-Domain Context $context = $this->createFallbackContext($host); $this->registerContext($context, $request); } return $next($request); } /** * Domain-Context mit Request-Level Caching auflösen */ private function resolveDomainContext(string $host): DomainContext { // Request-Level Cache-Check (verhindert wiederholte Domain-Resolution) $cacheKey = 'domain_' . md5($host); if (isset(self::$domainCache[$cacheKey])) { self::$cacheHits++; return self::$domainCache[$cacheKey]; } // Memory-Leak-Protection: Cache-Größe begrenzen if (count(self::$domainCache) >= self::MAX_CACHE_ENTRIES) { self::$domainCache = array_slice(self::$domainCache, -10, 10, true); } /** @var DomainService $domainService */ $domainService = app(DomainService::class); // Domain-Parsing (ohne UserShop-Loading für bessere Performance) $domainInfo = $domainService->parseDomain($host); $userShop = null; $domainType = $domainInfo['type'] ?? 'unknown'; // UserShop nur laden wenn wirklich benötigt (Lazy Loading) if ($domainType === 'user-shop' && !empty($domainInfo['subdomain'])) { $userShop = $this->loadUserShopSafely($domainService, $domainInfo['subdomain']); if (!$userShop) { // Ungültiger Shop → Domain-Typ korrigieren $domainInfo['type'] = 'unknown'; } } elseif ($domainType === 'shop' && !empty($domainInfo['default_user_shop'])) { // Fallback-Shop für Hauptdomain (Fix: Type-Mismatch) $userShop = $this->loadUserShopSafely($domainService, $domainInfo['default_user_shop']); } $context = DomainContext::fromArray($domainInfo, $userShop); // In Cache speichern self::$domainCache[$cacheKey] = $context; return $context; } /** * UserShop sicher laden ohne Exception-Risk */ private function loadUserShopSafely(DomainService $domainService, string $slug): ?object { try { return $domainService->getUserShop($slug); } catch (\Throwable $e) { // Fehler beim UserShop-Loading nicht propagieren Log::warning('UserShop loading failed', [ 'slug' => $slug, 'error' => $e->getMessage() ]); return null; } } /** * Fallback-Context für Fehlerbehandlung */ private function createFallbackContext(string $host): DomainContext { return DomainContext::fromArray([ 'type' => 'main', 'host' => $host, 'subdomain' => null, 'domain' => config('app.domain', 'mivita'), 'tld' => config('app.tld_care', '.care'), ]); } /** * Optimierter Request-Filter (reduziert unnötige Verarbeitung) */ private function shouldHandle(Request $request): bool { // Schnelle Ausschluss-Checks zuerst if ($request->is('api/*')) { return false; } // Asset-Requests mit optimiertem Pattern if ($request->isMethod('GET') && $this->isStaticAsset($request->path())) { return false; } // Laravel-interne und Monitoring-Requests $skipPaths = ['_debugbar', '_ignition', 'telescope', 'health', 'status', 'ping']; foreach ($skipPaths as $path) { if ($request->is($path) || $request->is($path . '/*')) { return false; } } return true; } /** * Optimierte Asset-Erkennung */ private function isStaticAsset(string $path): bool { // Datei-Endungen (Original-Logic) if (preg_match('/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot|map|json)$/i', $path)) { return true; } // Pfad-basierte Assets (häufige Laravel-Patterns) $assetPaths = [ 'css/', 'js/', 'fonts/', 'images/', 'img/', 'assets/', 'storage/', 'mix-manifest', 'favicon', 'robots.txt', 'sitemap', '.well-known/', 'shop/product/image/' ]; foreach ($assetPaths as $assetPath) { if (str_starts_with($path, $assetPath) || str_contains($path, $assetPath)) { return true; } } return false; } /** * Anwendungs-Konfiguration setzen (ohne Session-Zugriff) */ private function configureApplication(DomainContext $context): void { // Session-Domain optimiert setzen $sessionDomain = $this->getSessionDomain($context); Config::set('session.domain', $sessionDomain); // App-URL für URL-Generierung if (!empty($context->host)) { $protocol = $this->getProtocol(); Config::set('app.url', $protocol . $context->host); } } /** * Session-Domain intelligenter bestimmen */ private function getSessionDomain(DomainContext $context): string { $baseDomain = config('app.domain', 'mivita'); if ($context->type === 'shop') { return '.' . $baseDomain . config('app.tld_shop', '.shop'); } return '.' . $baseDomain . config('app.tld_care', '.care'); } /** * Protocol-Detection für app.url */ private function getProtocol(): string { return (config('app.env') === 'production' || request()->isSecure()) ? 'https://' : 'http://'; } /** * Context in Container und Request registrieren */ private function registerContext(DomainContext $context, Request $request): void { // Container-Binding (für Dependency Injection) app()->instance(DomainContext::class, $context); // Request-Attribut (für direkten Zugriff) - Fix: Einheitlicher Key für Interoperabilität $request->attributes->set('domain_context', $context); } /** * Minimal Debug-Logging (nur bei Bedarf) */ private function logDomainResolution(DomainContext $context, string $host): void { if (!config('subdomain.debug.log_domain_switches', false)) { return; } Log::debug('Domain resolved', [ 'host' => $host, 'type' => $context->type ?? 'unknown', 'subdomain' => $context->subdomain, 'user_shop' => $context->userShop?->slug, 'cache_hits' => self::$cacheHits, 'cache_size' => count(self::$domainCache) ]); } /** * UserShop-Domains: PostRoute für korrekte URL-Generierung konfigurieren * * Das Problem: Util::getPostRoute() ist standardmäßig 'base.' was zu URLs wie * base.card/add/... führt. Diese Routes sind auskommentiert → 404 * * Lösung: Für UserShop-Domains PostRoute auf 'user/' setzen für URLs wie * user/card/add/... die in den UserShop-Routes definiert sind. */ private function configurePostRoute(DomainContext $context): void { // Nur für UserShop-Domains PostRoute anpassen if ($context->type !== 'user-shop') { return; } // PostRoute für UserShop-URLs setzen \App\Services\Util::setPostRoute('user/'); // Debug-Logging (optional) if (config('subdomain.debug.log_domain_switches', false)) { Log::debug('UserShop PostRoute configured', [ 'user_shop_slug' => $context->userShop?->slug ?? 'unknown', 'post_route' => 'user/', 'impact' => 'Card URLs now generate user/card/add/... instead of base.card/add/...' ]); } } /** * UserShop-Routing: subdomain aus Route-Parametern entfernen * * Wenn ein UserShop erkannt wird, muss die subdomain aus den Route-Parametern * entfernt werden, damit sie nicht in die Controller-Parameter weitergegeben wird. * * Route-Beispiel: /{site}/{subsite?}/{product_slug?} * Erwartet: site, subsite, product_slug - NICHT subdomain! */ private function cleanupRouteParameters(Request $request, DomainContext $context): void { // Nur bei UserShop-Domains Route-Parameter bereinigen if ($context->type !== 'user-shop') { return; } // Route muss existieren und subdomain Parameter haben if (!$request->route() || !$request->route('subdomain')) { return; } try { // subdomain aus Route-Parametern entfernen $request->route()->forgetParameter('subdomain'); // Optional: Debug-Logging in Development if (config('subdomain.debug.log_domain_switches', false)) { Log::debug('UserShop routing: subdomain parameter removed', [ 'user_shop_slug' => $context->userShop?->slug ?? 'unknown', 'remaining_route_params' => $request->route()->parameters() ]); } } catch (\Throwable $e) { // Fehler beim Route-Parameter-Cleanup nicht kritisch Log::warning('Failed to cleanup route parameters', [ 'user_shop_slug' => $context->userShop?->slug ?? 'unknown', 'error' => $e->getMessage() ]); } } /** * Cache-Statistiken für Debugging (optional) */ public static function getCacheStats(): array { return [ 'hits' => self::$cacheHits, 'entries' => count(self::$domainCache), 'memory_kb' => round(memory_get_usage() / 1024, 2) ]; } }