order[] = 'before-1'; $context = $next($context); $this->order[] = 'after-1'; return $context; } }; $middleware2 = new class ($executionOrder) implements HttpMiddleware { public function __construct(private array &$order) { } public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { $this->order[] = 'before-2'; $context = $next($context); $this->order[] = 'after-2'; return $context; } }; $manager = new MiddlewareManager(); $manager->addMiddleware($middleware1, 100); $manager->addMiddleware($middleware2, 50); $request = new Request(Method::GET, '/test', [], '', []); $stateManager = new RequestStateManager(); $finalHandler = function (MiddlewareContext $context) use (&$executionOrder) { $executionOrder[] = 'handler'; return $context->withResponse(new HttpResponse(Status::OK)); }; $context = $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager); expect($executionOrder)->toBe([ 'before-1', 'before-2', 'handler', 'after-2', 'after-1', ]); }); test('middleware can short-circuit pipeline', function () { $executed = []; $middleware1 = new class ($executed) implements HttpMiddleware { public function __construct(private array &$executed) { } public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { $this->executed[] = 'middleware-1'; // Short-circuit by not calling next return $context->withResponse(new HttpResponse(Status::FORBIDDEN)); } }; $middleware2 = new class ($executed) implements HttpMiddleware { public function __construct(private array &$executed) { } public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { $this->executed[] = 'middleware-2'; return $next($context); } }; $manager = new MiddlewareManager(); $manager->addMiddleware($middleware1, 100); $manager->addMiddleware($middleware2, 50); $request = new Request(Method::GET, '/test', [], '', []); $stateManager = new RequestStateManager(); $finalHandler = function (MiddlewareContext $context) use (&$executed) { $executed[] = 'handler'; return $context->withResponse(new HttpResponse(Status::OK)); }; $context = $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager); expect($executed)->toBe(['middleware-1']); expect($context->response?->status)->toBe(Status::FORBIDDEN); }); test('middleware can modify request', function () { $modifyMiddleware = new class () implements HttpMiddleware { public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { $modifiedHeaders = $context->request->headers; $modifiedHeaders['X-Custom-Header'] = 'test-value'; $modifiedRequest = new Request( $context->request->method, $context->request->path, $modifiedHeaders, $context->request->body, $context->request->server ); return $next($context->withRequest($modifiedRequest)); } }; $manager = new MiddlewareManager(); $manager->addMiddleware($modifyMiddleware); $request = new Request(Method::GET, '/test', [], '', []); $stateManager = new RequestStateManager(); $receivedRequest = null; $finalHandler = function (MiddlewareContext $context) use (&$receivedRequest) { $receivedRequest = $context->request; return $context->withResponse(new HttpResponse(Status::OK)); }; $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager); expect($receivedRequest?->headers['X-Custom-Header'] ?? null)->toBe('test-value'); }); test('middleware state management works', function () { $stateManager = new RequestStateManager(); $middleware1 = new class () implements HttpMiddleware { public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { $stateManager->set('key1', 'value1'); $stateManager->set('shared', 'from-middleware-1'); return $next($context); } }; $middleware2 = new class () implements HttpMiddleware { public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext { expect($stateManager->get('key1'))->toBe('value1'); expect($stateManager->get('shared'))->toBe('from-middleware-1'); $stateManager->set('key2', 'value2'); $stateManager->set('shared', 'from-middleware-2'); return $next($context); } }; $manager = new MiddlewareManager(); $manager->addMiddleware($middleware1); $manager->addMiddleware($middleware2); $request = new Request(Method::GET, '/test', [], '', []); $finalHandler = function (MiddlewareContext $context) use ($stateManager) { expect($stateManager->get('key1'))->toBe('value1'); expect($stateManager->get('key2'))->toBe('value2'); expect($stateManager->get('shared'))->toBe('from-middleware-2'); return $context->withResponse(new HttpResponse(Status::OK)); }; $manager->process(new MiddlewareContext($request), $finalHandler, $stateManager); });