laminas/laminas-router
Laminas Router provides flexible HTTP routing for PHP apps. Build routes from config or code, match incoming requests to controllers/handlers, and assemble URLs from route names and parameters. Supports segment, literal, regex, and more route types.
Begin by installing via Composer: composer require laminas/laminas-router. The core entry points are Laminas\Router\Http\TreeRouteStack (for composed route hierarchies) and Laminas\Router\Http\RouteInterface implementations like Laminas\Router\Http\Segment. Start with defining a basic route in config or a route provider class:
use Laminas\Router\Http\Segment;
use Laminas\Router\RouteStack;
$router = new RouteStack();
$router->addRoute(
'home',
new Segment('/[:controller][/:action]', [
'controller' => 'IndexController',
'action' => 'index',
])
);
Parse an incoming request using $router->match($request) and generate URLs with $router->assemble($params, $name = 'home'). The RouteStack is PSR-7 compatible — it works with ServerRequestInterface and integrates seamlessly into custom middleware pipelines.
Modular Route Aggregation: Use TreeRouteStack::addRoute() per module/service provider to build a composite router. Routes are evaluated in priority order; lower priority numbers match first.
Child Routes for RESTful Nesting: Define parent-child relationships to avoid repetition:
$router->addRoute('users', new Segment('/users', [
'controller' => 'UserController',
]), [
'child_routes' => [
'user-detail' => new Segment('/[:id]', ['action' => 'view']),
'user-edit' => new Segment('/[:id]/edit', ['action' => 'edit']),
]
]);
PSR-15 Middleware Interoperability: Combine with middleware-based stacks (e.g., Mezzio or custom handlers). The matched route’s match() result contains params, controller, action, etc., usable in your next middleware.
Dynamic Route Discovery: Implement RouteInterface or extend SimpleRouteStack for database-backed or configuration-driven routes (e.g., CMS-based CMS slugs). Use RouteStack::addRoute() with an object to inject dependencies (e.g., doctrine repo).
URL Generation Best Practice: Always use named routes and assemble() rather than hardcoding paths — changes to route definitions auto-update generated links.
Route Priority Matters: Routes are matched in LIFO order by default unless priority is set. Misordered routes cause unexpected matches (e.g., Segment('/[:slug]') matching early). Always set explicit priorities (priority => 1) for critical routes like admin panels.
Default Parameters Are Not Substituted: When assemble() omits a param used in the path but has a default in the route config, it won’t be reinserted unless explicitly passed. Ensure defaults are re-provided if needed.
Regex Routes Are Performance-Heavy: Avoid regex-based routes in hot paths — they’re slower than Segment. Prefer constraints in segment options ('constraints' => ['id' => '\d+']) instead.
Child Routes Don’t Inherit Defaults Unless Specified: Child routes get their own defaults unless explicitly merged. Use ‘merge_defaults’ => true in child definition if needed.
Test with Real Requests: Use ServerRequestFactory::fromGlobals() to build test requests rather than mocking — regex matching and parameter extraction depend on real request attributes (like PATH_INFO).
Extensibility via Plugin Manager: Extend Laminas\Router\Http\RoutePluginManager to register custom route types (MyRoute::class) and resolve via $router->getRoutePluginManager()->get('my_route').
Debugging Match Failures: Enable logging inside a custom router listener or patch TreeRouteStack::match() temporarily to dump which routes are attempted — the stack is evaluated depth-first on failure.
How can I help you explore Laravel packages today?