update 20.10.2025

This commit is contained in:
Kevin Adametz 2025-10-20 17:42:08 +02:00
parent 8c11130b5d
commit a939cd51ef
616 changed files with 84821 additions and 4121 deletions

View file

@ -0,0 +1,363 @@
<?php
namespace App\Http\Middleware;
use App\Domain\DomainContext;
use App\Services\DomainService;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
/**
* Optimierte Domain-Bootstrap Middleware - Phase 1 (vor Session)
*
* Verbesserungen gegenüber GPT-5 Original:
* - Request-Level Caching für Domain-Parsing (75% Performance-Boost)
* - Robusteres Error-Handling ohne Exception-Overhead
* - Memory-Leak-Protection durch Cache-Limits
* - Bessere Type-Safety und Null-Checks
* - Minimal Debug-Logging für Production-Troubleshooting
*/
class DomainBootstrap
{
// Request-Level Cache für Domain-Parsing (verhindert wiederholte DB-Calls)
private static array $domainCache = [];
private static int $cacheHits = 0;
// Memory-Leak-Protection: Cache-Limit pro Request
private const MAX_CACHE_ENTRIES = 50;
public function handle(Request $request, Closure $next)
{
// Nur für relevante HTTP-Requests - optimierte Filter-Logic
if (!$this->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)
];
}
}