- 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
383 lines
16 KiB
PHP
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),
|
|
];
|
|
}
|
|
}
|