Routes
Request Verbs
Route has convenience methods for setting routes that will respond differently depending on the HTTP request method.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->get('/acme/route', 'Acme\Controller::getMethod');
$router->post('/acme/route', 'Acme\Controller::postMethod');
$router->put('/acme/route', 'Acme\Controller::putMethod');
$router->patch('/acme/route', 'Acme\Controller::patchMethod');
$router->delete('/acme/route', 'Acme\Controller::deleteMethod');
$router->head('/acme/route', 'Acme\Controller::headMethod');
$router->options('/acme/route', 'Acme\Controller::optionsMethod');
Each of the above routes will respond to the same URI but will invoke a different callable based on the HTTP request method.
Route Conditions
There are times when you may wish to add further conditions to route matching other than the request verb and URI. Route allows this by chaining further conditions to the route definition.
Host
You can limit a route to match only if the host is a match as well as the request verb and URI.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router
->map('GET', '/acme/route', 'Acme\Controller::getMethod')
->setHost('example.com')
;
The route above will only match if the request is for GET //example.com/acme/route.
Scheme
You can limit a route to match only if the scheme is a match as well as the request verb and URI.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router
->map('GET', '/acme/route', 'Acme\Controller::getMethod')
->setHost('example.com')
->setScheme('https')
;
The route above will only match if the request is for GET https://example.com/acme/route.
Port
You can limit a route to match only if the port is a match as well as the request verb and URI.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router
->map('GET', '/acme/route', 'Acme\Controller::getMethod')
->setHost('example.com')
->setScheme('https')
->setPort(8080)
;
The route above will only match if the request is for GET https://example.com:8080/acme/route.
As you can see above, these conditions are chainable. You can also apply the conditions to a route group so that they will be applied to all routes defined in that group, or individually on any of the routes defined within a group. For more on this, see below.
Route Groups
Route groups are a way of organising your route definitions, they allow us to provide conditions and a prefix across multiple routes. As an example, this would be useful for an admin area of a website.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->group('/admin', function (\League\Route\RouteGroup $route) {
$route->map('GET', '/acme/route1', 'AcmeController::actionOne');
$route->map('GET', '/acme/route2', 'AcmeController::actionTwo');
$route->map('GET', '/acme/route3', 'AcmeController::actionThree');
});
The above code will define routes that will respond to the following.
GET /admin/acme/route1
GET /admin/acme/route2
GET /admin/acme/route3
Named Routes
Named routes helps when you want to retrieve a Route by a human friendly label.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->group('/admin', function (\League\Route\RouteGroup $route) {
$route->map('GET', '/acme/route1', 'AcmeController::actionOne')->setName('actionOne');
$route->map('GET', '/acme/route2', 'AcmeController::actionTwo')->setName('actionTwo');
});
$route = $router->getNamedRoute('actionOne');
$route->getPath(); // "/admin/acme/route1"
Conditions
As mentioned above, route conditions can be applied to a group and will be matched across all routes contained in that group, specific routes within the group can override this functionality as displayed below.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->group('/admin', function (\League\Route\RouteGroup $route) {
$route->map('GET', '/acme/route1', 'AcmeController::actionOne');
$route->map('GET', '/acme/route2', 'AcmeController::actionTwo')->setScheme('https');
$route->map('GET', '/acme/route3', 'AcmeController::actionThree');
})
->setScheme('http')
->setHost('example.com')
;
The above code will define routes that will respond to the following.
GET http://example.com/admin/acme/route1
GET https://example.com/admin/acme/route2
GET http://example.com/admin/acme/route3
Wildcard Routes
Wildcard routes allow a route to respond to dynamic segments of a URI. If a route has dynamic URI segments, they will be passed in to the controller as an associative array of arguments.
<?php declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$router = new League\Route\Router;
$router->map('GET', '/user/{id}/{name}', function (ServerRequestInterface $request, array $args): ResponseInterface {
// $args = [
// 'id' => {id}, // the actual value of {id}
// 'name' => {name} // the actual value of {name}
// ];
// ...
});
Dynamic URI segments can also be limited to match certain requirements.
<?php declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$router = new League\Route\Router;
$router->map('GET', '/user/{id:number}/{name:word}', function (ServerRequestInterface $request, array $args): ResponseInterface {
// $args = [
// 'id' => {id}, // the actual value of {id}
// 'name' => {name} // the actual value of {name}
// ];
// ...
});
There are several built in conditions for dynamic segments of a URI.
- number
- word
- alphanum_dash
- slug
- uuid
Dynamic segments can also be set as any regular expression such as {id:[0-9]+}.
For convenience, you can also register your own aliases for a particular regular expression using the addPatternMatcher method on League\Route\Router.
For example:
<?php declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$router = new League\Route\Router;
$router->addPatternMatcher('wordStartsWithM', '(m|M)[a-zA-Z]+');
$router->map('GET', 'user/mTeam/{name:wordStartsWithM}', function (
ServerRequestInterface $request,
array $args
): ResponseInterface {
// $args = [
// 'id' => {id}, // the actual value of {id}
// 'name' => {name} // the actual value of {name}
// ];
// ...
});
The above pattern matcher will create an internal regular expression string: {$1:(m|M)[a-zA-Z]+}, where $1 will interpret to name, the variable listed before the colon.
URL Generation
Named routes can be used to generate URLs, the inverse of route matching. Both Router and Cache\Router implement UrlGeneratorInterface.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->get('/users/{id}', 'UserController::show')->setName('users.show');
$router->get('/posts/{slug:slug}', 'PostController::show')->setName('posts.show');
$url = $router->generateUrl('users.show', ['id' => '42']);
// => /users/42
$url = $router->generateUrl('posts.show', ['slug' => 'hello-world']);
// => /hello-world
Optional Segments
Routes can include optional segments using FastRoute’s bracket syntax [/{param}]. The generateUrl() method handles these correctly: if the parameter is provided, the segment is included; if not, it is omitted or the default from setVars() is used.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->get('/blog[/{page:number}]', 'BlogController::index')
->setName('blog.index')
->setVars(['page' => '1']);
$router->generateUrl('blog.index');
// => /blog/1 (uses default from setVars)
$router->generateUrl('blog.index', ['page' => '3']);
// => /blog/3 (substitution overrides default)
If no default is set and no substitution is provided, the optional segment is omitted entirely:
<?php declare(strict_types=1);
$router->get('/blog[/{page:number}]', 'BlogController::index')
->setName('blog.index');
$router->generateUrl('blog.index');
// => /blog (optional segment omitted)
Nested optional segments are also supported:
<?php declare(strict_types=1);
$router->get('/archive[/{year}[/{month}]]', 'ArchiveController::index')
->setName('archive');
$router->generateUrl('archive', ['year' => '2024']);
// => /archive/2024
$router->generateUrl('archive', ['year' => '2024', 'month' => '03']);
// => /archive/2024/03
$router->generateUrl('archive');
// => /archive
Required parameters (outside brackets) still throw InvalidArgumentException if missing, even when optional segments are present.
Missing Parameters
If required parameters are not provided, an InvalidArgumentException is thrown with a message listing the missing parameters.
<?php declare(strict_types=1);
$router->get('/users/{id}/posts/{postId}', 'PostController::show')->setName('user.posts.show');
$router->generateUrl('user.posts.show', ['id' => '42']);
// throws InvalidArgumentException: Missing required parameters {postId} for route "user.posts.show"
Extra Parameters as Query String
Any substitution keys that do not match a route parameter are appended as a query string.
<?php declare(strict_types=1);
$router->get('/users/{id}', 'UserController::show')->setName('users.show');
$url = $router->generateUrl('users.show', ['id' => '42', 'page' => '2', 'sort' => 'name']);
// => /users/42?page=2&sort=name
Type-hinting
If you need URL generation without a full router dependency, type-hint against UrlGeneratorInterface:
<?php declare(strict_types=1);
use League\Route\UrlGeneratorInterface;
class NavigationBuilder
{
public function __construct(private UrlGeneratorInterface $urlGenerator) {}
public function buildMenu(): array
{
return [
'home' => $this->urlGenerator->generateUrl('home'),
'profile' => $this->urlGenerator->generateUrl('profile', ['id' => '1']),
];
}
}
Route Introspection
You can retrieve all routes registered on the router using the getRoutes() method. This returns an array of all registered Route objects, including those defined within route groups. This is useful for route debugging, documentation generation, and advanced routing scenarios.
<?php declare(strict_types=1);
$router = new League\Route\Router;
$router->get('/users', 'UserController::index');
$router->post('/users', 'UserController::store');
$router->get('/users/{id}', 'UserController::show');
$routes = $router->getRoutes();
foreach ($routes as $route) {
echo $route->getMethod() . ' ' . $route->getPath() . PHP_EOL;
}
Each Route object provides methods to inspect its configuration:
getMethod()- the HTTP method(s)getPath()- the route path patterngetName()- the route name (if set)getHost()- the route host condition (if set)getScheme()- the route scheme condition (if set)getPort()- the route port condition (if set)getVars()- the route variables (merged defaults and path variables)
Default Route Variables
Routes can have default variables set that will be merged with any path variables captured during route matching. This is useful for providing context or permissions to your controllers.
<?php declare(strict_types=1);
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$router = new League\Route\Router;
$router->map('GET', '/users/{id}', function (ServerRequestInterface $request, array $args): ResponseInterface {
$userId = $args['id'];
$userRole = $args['role'];
// ...
})
->setVars(['role' => 'viewer']);
When this route is matched, the $args array passed to the controller will contain both the default variable and the path variable:
[
'role' => 'viewer', // from setVars()
'id' => '42' // from path matching {id}
]
This allows you to separate configuration defaults from dynamic route parameters, providing cleaner, more maintainable code.