Runtime HTTP router for Componenta applications. The package provides immutable route records, fluent route building, route groups, route collections, path pattern compilation, optimized compiled routes, URL generation, PSR-15 route matching and dispatch middleware, handler resolution, and router exception handlers.
Attribute discovery, production route-cache generation, and HTTP-interceptor route handler resolution live in componenta/router-app.
composer require componenta/routercomponenta/router owns runtime routing behavior. It can work with explicitly registered routes and does not require class scanning.
| Package | Why it matters here |
|---|---|
componenta/http-responder |
Provides HTTP response helpers; router only selects handlers and does not format controller output. |
componenta/middleware-factory |
Turns strings, classes, groups, and middleware instances into MiddlewareInterface. |
componenta/di |
Creates route handlers and middleware from the container when needed. |
componenta/router-app |
Discovers #[Route] attributes, builds route cache files, and provides application HTTP-interceptor route handler resolution. |
Use componenta/router for:
- route records and route collections;
- route matching and URL generation;
- grouped routes and middleware groups;
- PSR-15 matching/dispatch middleware;
- compiled route collections loaded from cache;
- route exception handling.
Use componenta/router-app for:
- discovering
#[Route]attributes; - generating route cache files;
- integrating route compilation with the application cache pipeline;
- resolving route handlers through HTTP interceptors.
RouteRecord is an immutable route definition.
use Componenta\Http\Router\RouteRecord;
$route = RouteRecord::get(
name: 'posts.show',
path: '/posts/{id}',
handler: ShowPostController::class,
middlewares: ['auth'],
tokens: ['id' => '[0-9a-f-]{36}'],
);Public fields:
| Field | Type | Meaning |
|---|---|---|
name |
string |
Unique route name used for matching result and URL generation. |
path |
string |
Normalized route path. |
handler |
RouteHandler |
Handler definition wrapped as route middleware. |
methods |
list<string> |
Uppercase HTTP methods. |
middlewares |
?MiddlewareGroup |
Route-level middleware group. |
tokens |
array<string, string> |
Regex constraints for path parameters. |
defaults |
array<string, mixed> |
Scalar defaults for optional parameters. |
group |
?string |
Group name to apply through Routes. |
Static constructors exist for get, post, put, patch, delete, head, options, and any.
RouteRecord validates early:
- method list must not be empty;
- every method must be a non-empty string;
- token patterns must be valid regular expressions;
- default values must be scalar or null.
RouteBuilder is useful when a route is assembled conditionally.
use Componenta\Http\Router\RouteBuilder;
$route = RouteBuilder::get('posts.show', '/posts/[id]')
->handler(ShowPostController::class)
->token('id', '[0-9a-f-]{36}')
->default('preview', false)
->group('public')
->build();build() throws LogicException when no handler was set.
The default compiler uses CompositeSyntax, so several parameter syntaxes are accepted:
| Syntax | Meaning |
|---|---|
/posts/{id} |
Required parameter. |
/posts/[id] |
Required parameter. |
/posts/:id |
Required parameter in colon syntax. |
/posts/[?page] |
Optional parameter. |
/posts/[id:\d+] |
Inline regex constraint. |
/posts/[?page:\d+=1] |
Optional parameter with regex and default. |
Explicit tokens override inline tokens. Explicit defaults override inline defaults.
RouteGroup applies name prefix, path prefix, middleware, tokens, and defaults to child routes.
$routes = new Routes();
$api = $routes->group('api', '/api', middleware: ['api']);
$admin = $api->group('admin', '/admin', middleware: ['auth', 'admin']);
$admin->get('dashboard', '/dashboard', AdminDashboardController::class);The route above is registered as:
- name:
api.admin.dashboard - path:
/api/admin/dashboard - middleware:
api,auth,admin - group:
api.admin
Nested groups are registered in the parent collection, so later routes can reference the full group name explicitly.
Routes implements:
RouteCollectorInterfaceMatcherInterfaceGeneratorInterfaceCountableIteratorAggregate
$routes = new Routes();
$routes->addRoute(RouteRecord::get('home', '/', HomeController::class));
$match = $routes->match($routes, '/', 'GET');
$url = $routes->generate($routes, 'home');Routes compiles static and dynamic lookup tables lazily after registration. Static routes use hash lookup. Dynamic routes use compiled regex patterns.
Routes accepts an optional SyntaxParserInterface as the named syntax argument for URL generation. When omitted, it uses the built-in Compiler syntax, or CompositeSyntax for another CompilerInterface implementation. A custom compiler does not need to expose a syntax property.
Router combines a route collector, matcher, and generator.
$router = Router::fromDnf($routes);
$match = $router->match('/posts/42', 'GET');
$url = $router->generate('posts.show', ['id' => 42]);match() throws:
RouteNotFoundExceptionwhen no route matches URI and method;MethodNotAllowedExceptionwhen the URI matches but the method is not allowed.
generate() throws:
RouteNotRegisteredExceptionwhen the route name is missing;InvalidArgumentExceptionwhen required parameters are missing or invalid.
MatchResult contains data needed for dispatch:
| Field | Type |
|---|---|
name |
string |
handler |
RouteHandler |
middlewares |
?MiddlewareGroup |
parameters |
array<string, mixed> |
route |
RouteRecord lazy property |
Matched parameters are cast from numeric strings to integers or floats when possible.
MatchRouteMiddleware matches the incoming request and stores the result in request attributes:
MatchRouteMiddleware::ATTRIBUTE_MATCH_RESULT- every matched route parameter under its parameter name
DispatchRouteMiddleware reads the match result and resolves route middleware through MiddlewareFactory.
flowchart LR
A["ServerRequestInterface"] --> B["MatchRouteMiddleware"]
B --> C["request attributes"]
C --> D["DispatchRouteMiddleware"]
D --> E["route middleware group"]
E --> F["route handler"]
DispatchRouteMiddleware resolves route middleware on every request through the configured factory. The container and middleware resolver determine object lifetimes; compiled route collections preserve the same resolution and execution behavior.
| Handler | Behavior |
|---|---|
ThrowingRouterExceptionHandler |
Re-throws router exceptions for a global error handler. |
RouterExceptionHandler |
Returns 404 or 405 PSR-7 responses. |
JsonRouterExceptionHandler |
Returns JSON 404/405 responses and Allow for 405. |
CallableRouterExceptionHandler |
Delegates handling to a closure. |
CompiledRoutes loads an explicitly compiled route collection from a generated cache file.
$routes = CompiledRoutes::fromCache($cacheFile);
$router = Router::fromDnf($routes);Performance model:
- static routes use O(1) hash lookup;
- dynamic routes can use one regex per HTTP method;
- large dynamic sets can be split into chunks;
- prefix index reduces the number of chunks checked;
- route handler definitions and middleware groups are initialized lazily.
The cache file is generated by RouteCacheGenerator, normally through componenta/router-app and app:build. Production service factories load the map when optimization is enabled. Rebuild it whenever route definitions, configuration or external inputs used by the route source change. Combined expressions are checked against PCRE limits and oversized groups are split further. If a combined expression exhausts a matching limit, matching continues through the original individual route patterns. CachedRouteLocator falls back to its supplied source when generated data is invalid, including when the application converts warnings into exceptions.
Replacing a compiled collection's public compiler before its first match uses the source matching path. URL generation uses the current compiler while retaining any explicitly configured generation syntax. Cache format 8 records whether that syntax was explicit; tryFromCache() rejects older formats so its caller can use the source.
#[Route] is the runtime metadata class consumed by application discovery.
use Componenta\Http\Router\Attribute\Route;
#[Route('posts.show', '/posts/[id:\d+]', 'GET', middlewares: ['web'])]
final readonly class ShowPostController
{
public function __invoke(int $id): ResponseInterface
{
// ...
}
}Constructor:
public function __construct(
string $name,
string $path,
array|string $methods = ['GET'],
array|string $middlewares = [],
array $tokens = [],
array $defaults = [],
?string $group = null,
int $priority = 0,
)methods accepts a string ('GET'), a |-separated string ('GET|POST'), or an array (['GET', 'POST']). priority is used by componenta/router-app: when multiple attribute routes can match the same URI, the route with the higher priority is added first.
The base router package does not scan this attribute. Discovery belongs to componenta/router-app.
ConfigProvider registers:
RouteLocatorInterfaceRouterMatchRouteMiddlewareDispatchRouteMiddlewareRouteHandlerResolverCompilerThrowingRouterExceptionHandler- aliases for
CompilerInterface,RouterExceptionHandlerInterface,MatcherInterface, andGeneratorInterface
Config keys:
| Key | Meaning |
|---|---|
ConfigKey::ROUTES_FILE |
Path to route definition file. |
ConfigKey::ROUTES_CACHE_FILE |
Optional explicit route cache file path. |
ConfigKey::COMPILED_PIPELINE |
Enables the compiled route map in production; defaults to true. |
| Exception | When |
|---|---|
RouteAlreadyExistsException |
Duplicate route name registration. |
RouteNotRegisteredException |
URL generation references unknown route name. |
RouteNotFoundException |
URI/method cannot be matched. |
MethodNotAllowedException |
URI matches a route, but method is not allowed. |
GroupNotFoundException |
Requested group is absent. |