The Router class internals — Illuminate/Routing/Router.php
Concept
Illuminate\Routing\Router is the central orchestrator of Laravel's routing system. It lives in the service container as the router binding (accessible via app('router') or the Route facade). The Router class owns a RouteCollection instance, handles route registration, manages group context, and dispatches matched routes to their actions. Understanding Router.php demystifies every routing behavior.
When you call Route::get(...), the Route facade resolves to the Router instance, and addRoute() is called. addRoute() creates an Illuminate\Routing\Route object, prepends group attributes from the group stack via mergeGroupAttributesIntoRoute(), calls route->compileRoute() to compile the URI pattern into a regex, then calls this->routes->add($route) to insert it into the RouteCollection.
RouteCollection (at Illuminate\Routing\RouteCollection) maintains three internal structures: $routes (array keyed by HTTP method, then URI), $allRoutes (flat array for fallback lookups), and $nameList (name → Route map). When a cached collection is active, CompiledRouteCollection replaces the standard collection; it holds the pre-compiled array from the cache file and reconstitutes Route objects lazily only when a route is actually matched.
Router::dispatch($request) is the method called by the HTTP kernel. It calls findRoute($request), which asks the RouteCollection to match the request. The collection tries static lookup first (by method + URI string), then falls back to iterating parameterized routes and calling Route::matches($request). Once a route is found, Router::runRoute() builds the middleware pipeline and executes the route's action through it. The action itself — whether a controller method or closure — is called via Router::callRouteAction(), which uses ControllerDispatcher for controller actions or calls the closure directly.
The groupStack property is an array of attribute frames. Every Route::group() call pushes a frame onto the stack and pops it after the closure returns. mergeGroupAttributesIntoRoute() reads the top frame and applies prefix, middleware, name prefix, domain, and namespace. This makes nesting fully composable with O(1) stack access per route registration.
Code Example
<?php
// =============================================
// How the Router class is accessed
// =============================================
use Illuminate\Support\Facades\Route;
use Illuminate\Routing\Router;
// Via facade (most common)
Route::get('/users', [UserController::class, 'index']);
// Via container binding
$router = app('router'); // Illuminate\Routing\Router instance
$router = app(Router::class); // same instance
// =============================================
// RouteCollection internals — peeking inside
// =============================================
$routes = app('router')->getRoutes(); // Returns RouteCollection
// Iterate all registered routes
foreach ($routes as $route) {
// $route is Illuminate\Routing\Route
echo $route->uri() . ' => ' . $route->getActionName() . PHP_EOL;
}
// Look up by name
$namedRoute = $routes->getByName('users.index');
// Look up by action
$actionRoute = $routes->getByAction([UserController::class, 'index']);
// =============================================
// Router::dispatch() call chain (simplified)
// =============================================
// 1. Kernel::handle($request)
// 2. Router::dispatch($request)
// 3. Router::findRoute($request)
// 4. RouteCollection::match($request)
// 5. Route::matches($request) — regex check
// 6. Router::runRoute($request, $route)
// 7. Router::runRouteWithinStack($route, $request)
// 8. Pipeline::send($request)->through($middleware)->then(...)
// 9. ControllerDispatcher::dispatch($route, $controller, $method)
// 10. Container::call([$controller, $method], $parameters)
// =============================================
// Customizing route resolution via macros
// =============================================
// In a service provider boot():
Router::macro('domain', function (string $domain) {
return $this->prefix('')->domain($domain);
});
// =============================================
// Reading route information at runtime
// =============================================
$currentRoute = request()->route(); // Illuminate\Routing\Route or null
if ($currentRoute) {
$name = $currentRoute->getName();
$action = $currentRoute->getActionName(); // e.g. "App\Http\Controllers\UserController@index"
$parameters = $currentRoute->parameters();
$middleware = $currentRoute->gatherMiddleware();
}Interview Q&A
Q: How does Router::findRoute() handle routes that have the same URI but different HTTP methods?
RouteCollection stores routes in a nested array keyed first by HTTP method, then by URI. When findRoute() is called, it retrieves all routes registered for the request's HTTP method and attempts to match the request URI against each. If a route exists for the URI but not for the current method, the collection also checks if the URI matches routes under other methods; if it does, it throws Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException with the allowed methods listed in the Allow response header, rather than a plain 404. This is the correct HTTP/1.1 behavior for 405 Method Not Allowed.
Q: What is the role of ControllerDispatcher versus calling the controller directly, and why does the container get involved?
Illuminate\Routing\ControllerDispatcher::dispatch() resolves the controller class from the container — meaning constructor dependencies are automatically injected. It then calls Container::call([$controller, $method], $routeParameters), which inspects the method's type-hinted parameters using ReflectionMethod and resolves each from the container. Route parameters (like $user after model binding) are matched by name against the reflection parameters. The container injects anything else that is a resolvable type hint. Without this layer, you would have to manually instantiate controllers and pass every dependency, defeating Laravel's DI system.
Q: What does Route::getRoutes() return and how would you use it to build a sitemap generator?
Route::getRoutes() returns the live RouteCollection instance (or CompiledRouteCollection if cached). It implements IteratorAggregate, so you can foreach over it. Each element is an Illuminate\Routing\Route with methods: uri(), getName(), methods(), getActionName(), middleware(). A sitemap generator would iterate the collection, filter to GET routes without middleware like auth, and for static routes call route($name) to generate the URL. Parameterized routes need special handling — you'd look them up in the database or skip them, since parameters require live data to generate valid URLs.