Files
michaelschiemer/src/Framework/Http/HttpMiddlewareChain.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

236 lines
8.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Http;
use App\Framework\DI\Container;
use App\Framework\Logging\Logger;
final readonly class HttpMiddlewareChain implements HttpMiddlewareChainInterface
{
private MiddlewareInvoker $invoker;
private Logger $logger;
private RequestStateManager $stateManager;
public function __construct(
private array $middlewares,
private Container $container
) {
$this->invoker = new MiddlewareInvoker($this->container);
$this->logger = $this->container->get(Logger::class);
}
public function handle(Request $request): Response
{
// Kontext mit dem Request erstellen
$context = new MiddlewareContext($request);
$this->stateManager = new MiddlewareStateManager()->forRequest($request);
// Start der Middleware-Chain loggen
error_log("🚀 MIDDLEWARE CHAIN START - URI: {$request->path}, Method: {$request->method->value}, Middleware-Count: " . count($this->middlewares));
// Middleware-Stack durchlaufen
$resultContext = $this->doProcessMiddlewareStack($context, 0);
// Ende der Middleware-Chain loggen
if ($resultContext->hasResponse()) {
error_log("✅ MIDDLEWARE CHAIN COMPLETE - Final Response Status: {$resultContext->response?->status->value}");
return $resultContext->response;
}
// Wenn keine Response vorhanden ist, ist das ein Fehler in der Middleware-Konfiguration
error_log("❌ MIDDLEWARE CHAIN FAILED - No response created after processing all middlewares");
throw new \RuntimeException(sprintf(
'Keine Response nach Middleware-Chain erstellt. Stellen Sie sicher, dass eine DefaultResponseMiddleware konfiguriert ist. URI: %s, Method: %s, Middleware-Count: %d',
$resultContext->request->path,
$resultContext->request->method->value,
count($this->middlewares)
));
}
private function doProcessMiddlewareStack(MiddlewareContext $context, int $index): MiddlewareContext
{
// Wenn das Ende der Middleware-Kette erreicht ist
if ($index >= count($this->middlewares)) {
$this->logDebug("Ende der Middleware-Kette erreicht", $context, $index);
return $context;
}
$middleware = $this->middlewares[$index];
$middlewareName = $this->getMiddlewareName($middleware);
// Start der Middleware loggen
error_log("🔄 MIDDLEWARE #{$index} START: {$middlewareName}");
// Status VOR der Middleware loggen
$this->logDebug("VOR Middleware #{$index} ({$middlewareName})", $context, $index);
// Next-Handler erstellen, der zur nächsten Middleware weiterleitet
$next = new readonly class ($this, $index, $middlewareName) implements Next {
public function __construct(
private HttpMiddlewareChain $chain,
private int $index,
private string $middlewareName
) {
}
public function __invoke(MiddlewareContext $context): MiddlewareContext
{
// Status beim Aufruf von $next() loggen
$this->chain->logDebug("NEXT aufgerufen in #{$this->index} ({$this->middlewareName})", $context, $this->index);
// Zur nächsten Middleware weitergehen
$resultContext = $this->chain->processMiddlewareStack($context, $this->index + 1);
// Status beim Rückgabewert von $next() loggen
$this->chain->logDebug("NEXT Rückgabe an #{$this->index} ({$this->middlewareName})", $resultContext, $this->index);
// Detaillierte Prüfung auf verlorene Response
if ($context->hasResponse() && ! $resultContext->hasResponse()) {
$this->chain->logError("RESPONSE VERLOREN zwischen Middleware #{$this->index} ({$this->middlewareName}) und nachfolgenden Middlewares!");
}
return $resultContext;
}
};
// Response-Status VOR der Middleware merken
$hadResponseBefore = $context->hasResponse();
$responseBeforeStatus = $hadResponseBefore ? $context->response?->status->value : null;
// Middleware mit dem Invoker ausführen
$resultContext = $this->invoker->invoke($middleware, $context, $next, $this->stateManager);
// Response-Status NACH der Middleware prüfen
$hasResponseAfter = $resultContext->hasResponse();
$responseAfterStatus = $hasResponseAfter ? $resultContext->response?->status->value : null;
// Detaillierte Response-Analyse
$this->analyzeResponseChanges(
$middlewareName,
$index,
$hadResponseBefore,
$hasResponseAfter,
$responseBeforeStatus,
$responseAfterStatus
);
// Status NACH der Middleware loggen
$this->logDebug("NACH Middleware #{$index} ({$middlewareName})", $resultContext, $index);
return $resultContext;
}
/**
* Ermittelt einen lesbaren Namen für die Middleware
*/
private function getMiddlewareName(mixed $middleware): string
{
if (is_string($middleware)) {
return $middleware;
}
if (is_object($middleware)) {
$className = get_class($middleware);
return substr($className, strrpos($className, '\\') + 1);
}
return gettype($middleware);
}
/**
* Analysiert Response-Änderungen durch eine Middleware
*/
private function analyzeResponseChanges(
string $middlewareName,
int $index,
bool $hadResponseBefore,
bool $hasResponseAfter,
?int $responseBeforeStatus,
?int $responseAfterStatus
): void {
// Response wurde erstellt
if (! $hadResponseBefore && $hasResponseAfter) {
error_log("✅ RESPONSE CREATED by Middleware #{$index} ({$middlewareName}) - Status: {$responseAfterStatus}");
return;
}
// Response wurde verloren
if ($hadResponseBefore && ! $hasResponseAfter) {
error_log("❌ RESPONSE LOST by Middleware #{$index} ({$middlewareName}) - Previous Status: {$responseBeforeStatus}");
return;
}
// Response-Status wurde geändert
if ($hadResponseBefore && $hasResponseAfter && $responseBeforeStatus !== $responseAfterStatus) {
error_log("🔄 RESPONSE MODIFIED by Middleware #{$index} ({$middlewareName}) - Status: {$responseBeforeStatus}{$responseAfterStatus}");
return;
}
// Response blieb unverändert (normal)
if ($hadResponseBefore && $hasResponseAfter && $responseBeforeStatus === $responseAfterStatus) {
error_log("➡️ RESPONSE PASSED THROUGH Middleware #{$index} ({$middlewareName}) - Status: {$responseAfterStatus}");
return;
}
// Keine Response vor und nach der Middleware (normal für frühe Middlewares)
// @phpstan-ignore booleanNot.alwaysTrue
if (! $hadResponseBefore && ! $hasResponseAfter) {
error_log("⚪ NO RESPONSE before/after Middleware #{$index} ({$middlewareName})");
}
}
/**
* Debug-Logging mit Context-Informationen
*/
public function logDebug(string $message, MiddlewareContext $context, int $index): void
{
$responseStatus = $context->hasResponse() ?
"Response✅ (Status: " . ($context->response->status->value ?? 'unknown') . ")" :
"Response❌";
}
/**
* Info-Logging für wichtige Events
*/
private function logInfo(string $message, array $context = []): void
{
$this->logger->info('Middleware Chain: {message}', array_merge([
'message' => $message,
'component' => 'MiddlewareChain',
], $context));
}
/**
* Makes processMiddlewareStack accessible to anonymous NextHandler class
*/
public function processMiddlewareStack(MiddlewareContext $context, int $index): MiddlewareContext
{
return $this->doProcessMiddlewareStack($context, $index);
}
/**
* Error-Logging für Probleme
*/
public function logError(string $message, array $context = []): void
{
$this->logger->error('Middleware Chain ERROR: {message}', array_merge([
'message' => $message,
'component' => 'MiddlewareChain',
'stack_trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5),
], $context));
}
}