Route Matching
Introduction
Version 7.0 introduces a powerful new match() method that allows you to check if a route matches without executing it. This enables non-blocking route matching for advanced use cases like conditional logic, authorisation checks, and route debugging.
The match() method returns a MatchResult value object containing information about the match, including a MatchStatus enum that indicates whether the route was found, not found, or if the method was not allowed.
RouterInterface
The new RouterInterface extends PSR-15’s RequestHandlerInterface and is implemented by both Router and Cache\Router. This interface enables type-safe dependency injection in your application.
<?php declare(strict_types=1);
use League\Route\RouterInterface;
class SomeService
{
public function __construct(private RouterInterface $router) {}
}
By type-hinting against the RouterInterface instead of a concrete class, you can easily swap between the standard Router and the cached Cache\Router without changing your application code.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container->add(League\Route\RouterInterface::class, League\Route\Router::class);
The Cache\Router requires a builder callable and a cache store as constructor arguments, so it must be bound via a factory closure. See Dependency Injection for full examples.
The RouterInterface declares two methods and inherits one from RequestHandlerInterface:
dispatch(ServerRequestInterface $request): ResponseInterface- Dispatch the request and return a responsematch(ServerRequestInterface $request): MatchResult- Match the request and return a result without executinghandle(ServerRequestInterface $request): ResponseInterface- Inherited from PSR-15RequestHandlerInterface
See Dependency Injection for examples of binding RouterInterface in a container.
Matching Routes
The match() method allows you to determine if a route exists for a given request without executing the matched controller. This is useful for scenarios where you need to make decisions based on route availability before processing the request.
<?php declare(strict_types=1);
use League\Route\Router;
use League\Route\MatchStatus;
$router = new Router;
$router->map('GET', '/users/{id}', 'UserController::show');
$router->map('POST', '/users', 'UserController::store');
$result = $router->match($request);
if ($result->isFound()) {
$route = $result->getRoute();
echo 'Matched route: ' . $route->getPath();
} elseif ($result->isMethodNotAllowed()) {
echo 'Method not allowed';
echo 'Allowed methods: ' . implode(', ', $result->getAllowedMethods());
} else {
echo 'Route not found';
}
MatchResult
The MatchResult value object contains the result of a route match operation. It provides convenient methods to determine the match status:
Properties and Methods
isFound(): bool- Returns true if a route was found and the method is allowedisMethodNotAllowed(): bool- Returns true if a route matched the path but the HTTP method is not allowedgetRoute(): Route- Returns the matchedRouteobject; throws\LogicExceptionif the result is notFoundgetAllowedMethods(): array- Returns the allowed HTTP methods; throws\LogicExceptionif the result is notMethodNotAllowedgetStatus(): MatchStatus- Returns theMatchStatusenum value
<?php declare(strict_types=1);
use League\Route\Router;
use League\Route\MatchStatus;
$router = new Router;
$router->get('/users/{id}', 'UserController::show');
$result = $router->match($request);
switch ($result->getStatus()) {
case MatchStatus::Found:
$route = $result->getRoute();
$vars = $route->getVars();
echo 'Route matched with vars: ' . json_encode($vars);
break;
case MatchStatus::NotFound:
echo 'No route found';
break;
case MatchStatus::MethodNotAllowed:
echo 'Method not allowed. Allowed: ' . implode(', ', $result->getAllowedMethods());
break;
}
Use Cases
Authorisation Checks
Check if a user has permission to access a route before dispatching:
<?php declare(strict_types=1);
use League\Route\Router;
$router = new Router;
// ... register routes
$result = $router->match($request);
if (!$result->isFound()) {
return sendNotFoundResponse();
}
if (!$this->userCanAccess($result->getRoute())) {
return sendForbiddenResponse();
}
return $router->dispatch($request);
Route Debugging
Log or debug information about matched routes during development:
<?php declare(strict_types=1);
$result = $router->match($request);
if ($result->isFound()) {
$route = $result->getRoute();
error_log('Matched: ' . $route->getMethod() . ' ' . $route->getPath());
error_log('Variables: ' . json_encode($route->getVars()));
}
Conditional Logic
Make decisions based on whether a route exists:
<?php declare(strict_types=1);
$adminResult = $router->match($adminRequest);
$publicResult = $router->match($publicRequest);
if ($adminResult->isFound() && $publicResult->isFound()) {
echo 'Both admin and public routes match';
}
Route Introspection
Build route maps or documentation by iterating routes and matching them:
<?php declare(strict_types=1);
foreach ($router->getRoutes() as $route) {
// Create a fake request for matching
$fakeRequest = $requestFactory->createServerRequest($route->getMethod(), $route->getPath());
$result = $router->match($fakeRequest);
if ($result->isFound()) {
echo sprintf(
'%s %s - %s' . PHP_EOL,
$route->getMethod(),
$route->getPath(),
$route->getName() ?? 'Unnamed'
);
}
}