Middleware
Routes store middleware metadata. Your application executes the middleware around a matched handler; the router itself does not dispatch requests.
The registration examples assume $routes = new Solo\Router\RouteCollector().
Adding middleware
$routes->get('/profile', [ProfileController::class, 'show'])
->middleware(AuthMiddleware::class);
$routes->get('/admin/users', [AdminController::class, 'users'])
->middleware(AuthMiddleware::class, AdminMiddleware::class, LogMiddleware::class);Group middleware is passed as the third argument:
use Solo\Router\RouteCollector;
$routes->group('/admin', function (RouteCollector $routes) {
$routes->get('/dashboard', [AdminController::class, 'dashboard']);
}, [AuthMiddleware::class, AdminMiddleware::class]);Order
Middleware is stored in outer-to-inner order:
- Outermost group middleware
- Nested group middleware
- Route middleware in the order appended
$routes->group('/api', function (RouteCollector $routes) {
$routes->group('/admin', function (RouteCollector $routes) {
$routes->get('/stats', [StatsController::class, 'index'])
->middleware(CacheMiddleware::class);
}, [AdminMiddleware::class]);
}, [ApiMiddleware::class]);
// ApiMiddleware → AdminMiddleware → CacheMiddleware → handlerAccessing middleware
use Solo\Router\Results\Matched;
$router = $routes->build();
$result = $router->resolve('GET', '/admin/dashboard');
if ($result instanceof Matched) {
$middlewares = $result->route->middlewares;
// [AuthMiddleware::class, AdminMiddleware::class]
}Fluent changes to definitions after a build affect later builds only.
Application pipeline example
This example assumes the application supplies $container, $request, and a $dispatch($handler, $params, $request) callable. Its middleware objects expose handle($request, $next), and middleware closures have the same arguments. These are example application conventions, not interfaces required by Solo Router.
use Solo\Router\Results\Matched;
use Solo\Router\Results\MethodNotAllowed;
$result = $router->resolve($method, $uri);
if (!$result instanceof Matched) {
if ($result instanceof MethodNotAllowed) {
http_response_code(405);
header('Allow: ' . implode(', ', $result->allowedMethods));
} else {
http_response_code(404);
}
return;
}
$pipeline = static fn ($request) => $dispatch(
$result->route->handler,
$result->params,
$request
);
foreach (array_reverse($result->route->middlewares) as $middleware) {
$next = $pipeline;
$pipeline = static function ($request) use ($middleware, $next, $container) {
if (is_string($middleware) && class_exists($middleware)) {
return $container->get($middleware)->handle($request, $next);
}
return $middleware($request, $next);
};
}
$response = $pipeline($request);Reversing the stored list while constructing the pipeline makes outer group middleware execute first and wrap the remaining middleware.
Closure middleware
$routes->get('/profile', [ProfileController::class, 'show'])
->middleware(function ($request, $next) {
// Perform an application check here.
return $next($request);
});Build the router after adding this definition to include it in the runtime snapshot.