echo-fusion / routemanager
A lightweight and flexible routing package that helps manage HTTP routes in PHP applications. Supports PSR-7 and PSR-15, designed for MVC architecture with middleware integration.
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0
- echo-fusion/contracts: ^2.0
- psr/http-message: ^2.0
Requires (Dev)
- echo-fusion/codestandard: ^1.0
- friendsofphp/php-cs-fixer: ^3.64
- phpunit/phpunit: ^9.5
- vimeo/psalm: ^5.26
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v3.0.0
- v2.x-dev
- v2.2.0
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.0
- v1.x-dev
- v1.0.1
- v1.0.0
- v0.x-dev
- dev-feat/immutable-route-and-registrar
- dev-feat/method-not-allowed-head-and-trailing-slash
- dev-fix/route-pattern-bytes
- dev-fix/route-matching-correctness
- dev-chore/unify-tooling-and-docs
- dev-ci/fix-coverage-check
- dev-ci/matrix-and-checks
- dev-fix/use-route-loader-interface
This package is auto-updated.
Last update: 2026-10-12 13:54:31 UTC
README
A lightweight and flexible routing package that helps manage HTTP routes in PHP applications. It supports PSR-7 and PSR-15 standards and is designed for MVC architecture with middleware integration.
Part of EchoFusion, a modular PHP framework built from small packages.
Part of EchoFusion, a modular PHP framework built from small packages.
Installation
composer require echo-fusion/routemanager
Requirements
- PHP 8.1, 8.2 or 8.3
Usage
Basic Setup
-
To get started, you can use the RouteMatcher and Router classes directly.
use EchoFusion\RouteManager\RouteMatcher; use EchoFusion\RouteManager\Router; $routeMatcher = new RouteMatcher(); $router = new Router($routeMatcher);
-
Defining Routes Define routes with optional regex constraints on route parameters. The action parameter can be:
-
An array (e.g., [Controller::class, 'method'])
-
A closure
use Psr\Http\Message\ServerRequestInterface; // Define a simple route $router->post( name: 'api.store', path: '/api', action: [ApiController::class, 'store'] ); // Define a route with parameter constraints $router->get( name: 'blog-show', path: '/post/{id}/detail/{slug}', action: function (int $id, string $slug, ServerRequestInterface $request) { // Your action code here var_dump($id, $slug); }, constraints: [ 'id' => '[0-9]+', 'slug' => '[a-z0-9\-]+', ] );
-
Dispatching and Executing a Route Once routes are defined, dispatch the request and execute the action if a match is found.
try { $routeMatch = $router->dispatch($request); $action = $routeMatch->getRoute()->getAction(); $params = array_merge($routeMatch->getParams(), [$request]); // Execute the action (callable or controller method) return call_user_func_array($action, $params); } catch (EchoFusion\RouteManager\Exceptions\RouteNotFoundException $e) { // Handle route not found } catch (Throwable $exception) { // Handle other errors }
Path patterns
$router->get('post', '/post/{id}/{slug}', $action, ['id' => '[0-9]+']); // constraints array $router->get('year', '/archive/{year:\d{4}}', $action); // inline constraint
- A placeholder is
{name}or{name:regex}. Aconstraintsentry for the same name wins over the inline regex. Without either, a parameter matches[a-zA-Z0-9_-]+. - Everything outside the placeholders is literal: in
/file.jsonthe dot is a real dot. - Parameters are returned by name (
$routeMatch->getParams()), even when a constraint has capturing groups of its own. - A path that cannot be compiled (unclosed
{, duplicate or invalid parameter name, broken regex) throwsExceptions\InvalidRouteExceptionwhen the route is registered, not on the first request. - Methods are compared case-insensitively. A
HEADrequest is served by theGETroute of the same path. - One trailing slash on the request is tolerated: a route
/aboutalso matches/about/. A route defined with a trailing slash (/docs/) keeps it. - When nothing matches,
dispatch()throwsRouteNotFoundExceptionwhose message says what was requested. When the path exists but not for the request's method, it throwsMethodNotAllowedException(a subclass, so existingcatch (RouteNotFoundException)code keeps working). Use it to answer405with anAllowheader:
try { $match = $router->dispatch($request); } catch (MethodNotAllowedException $e) { return $response->withStatus(405)->withHeader('Allow', implode(', ', $e->getAllowedMethods())); } catch (RouteNotFoundException) { return $response->withStatus(404); }
Routes are values
A Route is created complete and never changes (new Route($name, $method, $path, $action, $constraints, $middlewares)); withMiddlewares() returns a changed copy. You normally do not create routes yourself: the Router does when you call get(), post() ..., or fromArray().
Registering versus dispatching
The Router implements two small contracts, so code depends only on what it uses:
EchoFusion\Contracts\RouteManager\RouteRegistrarInterface:get(),post(),put(),patch(),delete(),options()(the code that defines routes, e.g. a route file).EchoFusion\Contracts\RouteManager\RouterInterface:dispatch(),getRoute(),getRoutes()(the code that handles requests).
Using Provider
The RouteManagerProvider allows you to integrate the Router and RouteMatcher instances into a dependency injection container.
-
Register the Provider: Add RouteManagerProvider to your service container.
use EchoFusion\RouteManager\Providers\RouteManagerProvider; use YourApp\Container; $provider = new RouteManagerProvider(); $provider->register($container);
-
Define Routes Using Configuration: Configure routes by calling the boot method, passing an array of routes or a configuration file. Only
method,pathandactionare required;constraintsandmiddlewaresare optional.$routes = [ 'home' => [ 'method' => 'GET', 'path' => '/', 'action' => fn () => 'Welcome to the homepage', ], 'blog_show' => [ 'method' => 'GET', 'path' => '/post/{id}/detail/{slug}', 'action' => function (int $id, string $slug, ServerRequestInterface $request) { // Route action code here }, 'constraints' => [ 'id' => '[0-9]+', 'slug' => '[a-z0-9\-]+', ], ], ]; $provider->boot($container, $routes);
If no routes are provided, the boot method defaults to loading routes from routemanager.config.php.
-
Dispatching a Route: With the routes registered, retrieve the RouterInterface from the container and dispatch it with a request object.
use EchoFusion\Contracts\RouteManager\RouterInterface; use EchoFusion\RouteManager\Exceptions\RouteNotFoundException; try { $router = $container->get(RouterInterface::class); $routeMatch = $router->dispatch($request); } catch (RouteNotFoundException $e) { // Handle route not found } catch (Throwable $exception) { // Handle other errors }
Testing
composer test
Credits
License
The MIT License (MIT). Please see LICENSE for more information.