Files
michaelschiemer/src/Framework/Http/MiddlewareManager.php
Michael Schiemer 70e45fb56e fix(Discovery): Add comprehensive debug logging for router initialization
- Add initializer count logging in DiscoveryServiceBootstrapper
- Add route structure analysis in RouterSetup
- Add request parameter logging in HttpRouter
- Update PHP production config for better OPcache handling
- Fix various config and error handling improvements
2025-10-27 22:23:18 +01:00

383 lines
16 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Http;
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DI\Container;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Http\Middlewares\DDoSProtectionMiddleware;
use App\Framework\Http\Middlewares\RateLimitMiddleware;
use App\Framework\Http\Middlewares\WafMiddleware;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Reflection\ReflectionProvider;
final readonly class MiddlewareManager implements MiddlewareManagerInterface
{
public HttpMiddlewareChain $chain;
public function __construct(
private Container $container,
private DiscoveryRegistry $discoveryRegistry,
private ReflectionProvider $reflectionProvider,
private Cache $cache,
) {
$middlewares = $this->buildMiddlewareStack();
// Debug logging mit strukturiertem Logger falls verfügbar
if ($this->container->has(Logger::class)) {
try {
$logger = $this->container->get(Logger::class);
$logger->debug('Middleware stack initialized', LogContext::withData([
'middleware_count' => count($middlewares),
'middleware_stack' => array_map(fn ($m) => basename($m), $middlewares),
'component' => 'MiddlewareManager',
]));
} catch (\Throwable $e) {
// Ignore logger errors during initialization
}
}
$this->chain = new HttpMiddlewareChain(
$middlewares,
$this->container
);
}
/**
* @return array<int, string>
*/
private function buildMiddlewareStack(): array
{
// Explizite Reihenfolge definieren - wichtigste zuerst
/** @var array<int, string> */
$explicitOrder = [
// 0. Exception Handling - MUSS absolut zuerst kommen, um alle Exceptions zu fangen
\App\Framework\Http\Middlewares\ExceptionHandlingMiddleware::class,
// 1. System und Error Handling
\App\Framework\Http\Middlewares\RequestIdMiddleware::class,
// 2. Production Security - MUST come early to block routes
\App\Framework\Http\Middlewares\ProductionSecurityMiddleware::class,
// 3. Security und DDoS Protection
DDoSProtectionMiddleware::class,
WafMiddleware::class,
// 4. Session - MUSS vor Auth und CSRF kommen!
\App\Framework\Http\Session\SessionMiddleware::class,
// 5. Security und Rate Limiting
//RateLimitMiddleware::class,
#\App\Application\Security\Middleware\SecurityEventMiddleware::class,
// 6. Headers und CORS
\App\Framework\Http\Middlewares\SecurityHeaderMiddleware::class,
\App\Framework\Http\Middlewares\RemovePoweredByMiddleware::class,
\App\Framework\Http\Middlewares\CORSMiddleware::class,
// 7. Authentication und CSRF (brauchen Session)
\App\Framework\Http\Middlewares\AuthMiddleware::class,
\App\Framework\Http\Middlewares\CsrfMiddleware::class,
\App\Framework\Http\Middlewares\HoneypotMiddleware::class,
// 8. Routing und Request Processing
\App\Framework\Http\Middlewares\RoutingMiddleware::class,
\App\Framework\Http\Middlewares\ControllerRequestMiddleware::class,
// 9. Content und Static Files
#\App\Framework\Http\Middlewares\ServeStaticFilesMiddleware::class,
\App\Framework\Http\Middlewares\ResponseGeneratorMiddleware::class,
\App\Framework\Http\Middlewares\FormDataResponseMiddleware::class, // Temporarily disabled
// 10. Monitoring und Analytics
\App\Framework\Analytics\Middleware\AnalyticsMiddleware::class,
\App\Framework\Performance\Middleware\RequestPerformanceMiddleware::class,
\App\Framework\Performance\Middleware\RoutingPerformanceMiddleware::class,
\App\Framework\Performance\Middleware\ControllerPerformanceMiddleware::class,
\App\Framework\Tracing\TracingMiddleware::class,
// 11. Logging (am Ende)
\App\Framework\Http\Middlewares\RequestLoggingMiddleware::class,
\App\Framework\Http\Middlewares\LoggingMiddleware::class,
\App\Framework\Performance\Middleware\PerformanceDebugMiddleware::class,
\App\Framework\Security\RequestSigning\RequestSigningMiddleware::class,
// 12. FALLBACK - Absolut letztes Middleware (nur wenn keine Response vorhanden)
\App\Framework\Http\Middlewares\DefaultResponseMiddleware::class,
];
// Use the refactored robust dependency resolution
try {
$resolved = $this->resolveDependencies($explicitOrder);
#error_log("MiddlewareManager: Using dependency resolution - Count: " . count($resolved));
// WICHTIG: Array umkehren, da die Liste in gewünschter Ausführungsreihenfolge definiert ist
// aber die Middleware-Chain sie in der gegebenen Reihenfolge abarbeitet
return array_reverse($resolved);
} catch (\Throwable $e) {
#error_log("MiddlewareManager: Dependency resolution failed: " . $e->getMessage());
#error_log("MiddlewareManager: Falling back to explicit order - Count: " . count($explicitOrder));
#foreach ($explicitOrder as $i => $middleware) {
# error_log("MiddlewareManager: Explicit #{$i}: " . basename($middleware));
#}
// WICHTIG: Array umkehren, da die Liste in gewünschter Ausführungsreihenfolge definiert ist
return array_reverse($explicitOrder);
}
}
/**
* Resolve middleware dependencies using constructor analysis
* @param array<int, string> $middlewares
* @return array<int, string>
*/
private function resolveDependencies(array $middlewares): array
{
$logger = $this->container->get(Logger::class);
$resolver = new MiddlewareDependencyResolver(
$this->reflectionProvider,
$this->container,
$logger
);
$resolved = $resolver->resolve($middlewares);
// Log dependency resolution info
#error_log("MiddlewareManager: Dependency resolution completed");
#error_log("MiddlewareManager: Resolved " . $resolved->count() . " middlewares");
#error_log("MiddlewareManager: Execution order: " . implode(' → ', array_map(fn ($m) => basename($m), $resolved->getMiddlewares())));
// Log dependency chains for debugging
$info = $resolved->getResolutionInfo();
#if (! empty($info['dependency_chains'])) {
# error_log("MiddlewareManager: Dependency chains: " . json_encode($info['dependency_chains']));
#}
#if (! empty($info['circular_dependencies'])) {
# error_log("MiddlewareManager: WARNING - Circular dependencies detected: " . json_encode($info['circular_dependencies']));
#}
return $resolved->getMiddlewares();
}
/**
* Get middleware dependency information for debugging
*/
/**
* @return array<string, mixed>
*/
public function getDependencyInfo(): array
{
$explicitOrder = $this->getExplicitOrder();
$classNameObjects = array_map(fn ($class) => ClassName::create($class), $explicitOrder);
$logger = $this->container->get(Logger::class);
$resolver = new MiddlewareDependencyResolver(
$this->reflectionProvider,
$this->container,
$logger
);
return $resolver->getDependencyInfo($classNameObjects);
}
/**
* Get the explicit order array (for testing and debugging)
* @return array<int, string>
*/
private function getExplicitOrder(): array
{
// Return the same explicit order array
return [
// 1. System und Error Handling
\App\Framework\Http\Middlewares\RequestIdMiddleware::class,
\App\Framework\Http\Middlewares\ExceptionHandlingMiddleware::class,
// 4. Session - MUSS vor Auth und CSRF kommen!
\App\Framework\Http\Session\SessionMiddleware::class,
// 2. Security und Rate Limiting
//RateLimitMiddleware::class,
#\App\Application\Security\Middleware\SecurityEventMiddleware::class,
// 3. Headers und CORS
\App\Framework\Http\Middlewares\SecurityHeaderMiddleware::class,
\App\Framework\Http\Middlewares\RemovePoweredByMiddleware::class,
\App\Framework\Http\Middlewares\CORSMiddleware::class,
// 5. Authentication und CSRF (brauchen Session)
\App\Framework\Http\Middlewares\AuthMiddleware::class,
\App\Framework\Http\Middlewares\CsrfMiddleware::class,
\App\Framework\Http\Middlewares\HoneypotMiddleware::class,
// 6. Routing und Request Processing
\App\Framework\Http\Middlewares\RoutingMiddleware::class,
\App\Framework\Http\Middlewares\ControllerRequestMiddleware::class,
// 7. Content und Static Files
#\App\Framework\Http\Middlewares\ServeStaticFilesMiddleware::class,
\App\Framework\Http\Middlewares\ResponseGeneratorMiddleware::class,
\App\Framework\Http\Middlewares\FormDataResponseMiddleware::class, // Temporarily disabled
// 8. Monitoring und Analytics
\App\Framework\Analytics\Middleware\AnalyticsMiddleware::class,
\App\Framework\Performance\Middleware\RequestPerformanceMiddleware::class,
\App\Framework\Performance\Middleware\RoutingPerformanceMiddleware::class,
\App\Framework\Performance\Middleware\ControllerPerformanceMiddleware::class,
\App\Framework\Tracing\TracingMiddleware::class,
// 9. Logging (am Ende)
\App\Framework\Http\Middlewares\RequestLoggingMiddleware::class,
\App\Framework\Http\Middlewares\LoggingMiddleware::class,
\App\Framework\Performance\Middleware\PerformanceDebugMiddleware::class,
\App\Framework\Security\RequestSigning\RequestSigningMiddleware::class,
// 10. FALLBACK - Absolut letztes Middleware (nur wenn keine Response vorhanden)
\App\Framework\Http\Middlewares\DefaultResponseMiddleware::class,
];
}
public function getPriorityForClass(object|string $middlewareClass): int
{
$className = is_string($middlewareClass) ? $middlewareClass : $middlewareClass::class;
$className = ltrim($className, '\\');
$cacheKey = CacheKey::fromString("middleware_priority:{$className}");
$cacheItem = $this->cache->remember($cacheKey, function () use ($className) {
return $this->resolvePriorityFromReflection($className);
}, Duration::fromSeconds(3600));
return is_int($cacheItem->value) ? $cacheItem->value : MiddlewarePriority::BUSINESS_LOGIC->value;
}
private function resolvePriorityFromReflection(string $className): int
{
try {
$classNameObj = ClassName::create($className);
$attrs = $this->reflectionProvider->getAttributes($classNameObj, MiddlewarePriorityAttribute::class);
if ($attrs->count() > 0) {
/** @var MiddlewarePriorityAttribute|null $attr */
$attr = $attrs->getFirstByType(MiddlewarePriorityAttribute::class)?->newInstance();
if ($attr instanceof MiddlewarePriorityAttribute) {
$basePriority = $attr->priority->value;
$offset = $attr->offset ?? 0;
return $basePriority + $offset;
}
}
} catch (\ReflectionException $e) {
// Class doesn't exist or can't be reflected, use default
}
// Default priority
return MiddlewarePriority::BUSINESS_LOGIC->value;
}
// Legacy static method for backward compatibility
public static function getPriority(object|string $middlewareClass): int
{
// This is a fallback for any code that still uses the static method
$className = is_string($middlewareClass) ? $middlewareClass : $middlewareClass::class;
$className = ltrim($className, '\\');
try {
$reflection = new \ReflectionClass($className);
$attrs = $reflection->getAttributes(MiddlewarePriorityAttribute::class);
if ($attrs) {
/** @var MiddlewarePriorityAttribute $attr */
$attr = $attrs[0]->newInstance();
$basePriority = $attr->priority->value;
$offset = $attr->offset ?? 0;
return $basePriority + $offset;
}
} catch (\ReflectionException $e) {
// Class doesn't exist or can't be reflected, use default
}
return MiddlewarePriority::BUSINESS_LOGIC->value;
}
/**
* Methode zum Sortieren der Middleware nach Priorität
* @param array<int, object|string> $middlewares
* @return array<int, object|string>
*/
private function sortMiddlewaresByPriority(array $middlewares): array
{
usort($middlewares, function (object|string $a, object|string $b) {
$priorityA = $this->getMiddlewarePriority($a);
$priorityB = $this->getMiddlewarePriority($b);
return $priorityB <=> $priorityA; // Höhere Priorität zuerst
});
return $middlewares;
}
// Hilfsmethode zum Abrufen der Middleware-Priorität
private function getMiddlewarePriority(object|string $middlewareClass): int
{
$className = is_string($middlewareClass) ? $middlewareClass : $middlewareClass::class;
$className = ltrim($className, '\\');
$cacheKey = CacheKey::fromString("middleware_priority_discovery:{$className}");
$cacheItem = $this->cache->remember($cacheKey, function () use ($className) {
// Use discovery registry to find priority
$priorities = $this->discoveryRegistry->attributes()->get(MiddlewarePriorityAttribute::class);
foreach ($priorities as $discoveredAttribute) {
if ($discoveredAttribute->className->getFullyQualified() === $className) {
$additionalData = $discoveredAttribute->additionalData;
$priority = $additionalData['priority'] ?? null;
if (! is_int($priority)) {
$priority = $priority->value;
}
return $priority + ($additionalData['offset'] ?? 0);
}
}
// Default priority
return MiddlewarePriority::BUSINESS_LOGIC->value;
}, Duration::fromSeconds(3600));
return is_int($cacheItem->value) ? $cacheItem->value : MiddlewarePriority::BUSINESS_LOGIC->value;
}
/**
* Clear the priority cache (useful for testing)
*/
public function clearPriorityCache(): void
{
// Clear cache entries that start with our middleware priority prefix
$this->cache->forget('middleware_priority:*');
$this->cache->forget('middleware_priority_discovery:*');
}
/**
* Get cache statistics for debugging
*
* @return array<string, string>
*/
public function getCacheStats(): array
{
return [
'cache_implementation' => get_class($this->cache),
'reflection_provider' => get_class($this->reflectionProvider),
];
}
}