From 60bf7921f29329a7750e44f2038468e3b6209a43 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Tue, 16 Jun 2026 08:01:44 -0600 Subject: [PATCH] Throw when middleware class is missing or cannot be resolved String middleware classes that do not exist or fail container resolution are now validated at request time, matching route handler behavior. Middleware that only implements one phase (before/after) continues to be skipped for the other phase. Fixes #696 --- flight/Engine.php | 30 +++++++++++++++++++++++++----- tests/EngineTest.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 5ceb763..82851f5 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -456,7 +456,22 @@ protected function processMiddleware(Route $route, string $eventName): bool $middlewareObject = method_exists($middleware, $eventName) === true ? [$middleware, $eventName] : false; // If the middleware is a string, we need to create the object and then call the event. - } elseif (is_string($middleware) === true && method_exists($middleware, $eventName) === true) { + } elseif (is_string($middleware) === true) { + if (class_exists($middleware) === false) { + if (ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { + ob_end_clean(); + } + + throw new Exception( + "Middleware class '$middleware' not found. " + . "Is it being correctly autoloaded with Flight::path()?" + ); + } + + if (method_exists($middleware, $eventName) === false) { + continue; + } + $resolvedClass = null; // if there's a container assigned, we should use it to create the object @@ -464,14 +479,19 @@ protected function processMiddleware(Route $route, string $eventName): bool $resolvedClass = $this->dispatcher->resolveContainerClass($middleware, $params); // otherwise just assume it's a plain jane class, so inject the engine // just like in Dispatcher::invokeCallable() - } elseif (class_exists($middleware) === true) { + } else { $resolvedClass = new $middleware($this); } - // If something was resolved, create an array callable that will be passed in later. - if ($resolvedClass !== null) { - $middlewareObject = [$resolvedClass, $eventName]; + if ($resolvedClass === null) { + if (ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { + ob_end_clean(); + } + + throw new Exception("Middleware class '$middleware' could not be resolved."); } + + $middlewareObject = [$resolvedClass, $eventName]; } // If nothing was resolved, go to the next thing diff --git a/tests/EngineTest.php b/tests/EngineTest.php index c374845..e2d8471 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -899,6 +899,40 @@ public function after($params) $this->expectOutputString('before456before123OKafter123456after123'); } + public function testMiddlewareMissingClassThrowsException(): void + { + $engine = new Engine(); + $engine->route('/path1', function () { + echo 'OK'; + })->addMiddleware('TotallyMissingMiddlewareClass'); + + $engine->request()->url = '/path1'; + $this->expectException(Exception::class); + $this->expectExceptionMessage( + "Middleware class 'TotallyMissingMiddlewareClass' not found. " + . "Is it being correctly autoloaded with Flight::path()?" + ); + $engine->start(); + } + + public function testMiddlewareMissingClassInGroupThrowsException(): void + { + $engine = new Engine(); + $engine->group('', function ($router) { + $router->get('/path1', function () { + echo 'OK'; + }); + }, ['TotallyMissingMiddlewareClass']); + + $engine->request()->url = '/path1'; + $this->expectException(Exception::class); + $this->expectExceptionMessage( + "Middleware class 'TotallyMissingMiddlewareClass' not found. " + . "Is it being correctly autoloaded with Flight::path()?" + ); + $engine->start(); + } + public function testContainerBadClass() { $engine = new Engine();