matthiasmullie / router
PSR-15 based router interface on top of FastRoute
Fund package maintenance!
matthiasmullie
Requires
- php: ^8.1
- ext-json: *
- nikic/fast-route: ^1.3.0
- psr/http-message: ^1.0
- psr/http-message-implementation: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: >=3.0
- guzzlehttp/psr7: ^2.0
- monolog/monolog: ^3.0
- phpunit/phpunit: >=9.0
Suggests
- psr/log-implementation: Log router events
README
Installation
Simply add a dependency on matthiasmullie/router
to your composer.json file if you use Composer to manage the dependencies of your project:
composer require matthiasmullie/router
Usage
Routes
This router provides a simple way to map routes (request method + path) to request handlers.
Request handlers are classes that implement PSR-15's the Psr\Http\Server\RequestHandlerInterface
interface,
which accept a Psr\Http\Message\ServerRequestInterface
and return a Psr\Http\Message\ResponseInterface
object.
This is essentially just a simple PSR-15 layer on top of FastRoute.
It's about as simple as that:
use MatthiasMullie\Router\RequestMethods; use MatthiasMullie\Router\Router; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\ServerRequest; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; // create 2 example request handlers class RouteOne implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { return new Response( 200, ['Content-Type' => 'application/json; charset=utf-8'], json_encode(['message' => 'Hello world']), ); } } class RouteTwo implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { return new Response( 200, ['Content-Type' => 'text/html; charset=utf-8'], '<html><body><p>Hello world</p></body></html>', ); } } // create router instance and add both routes $this->router = new Router(); $this->router->addRoute( RequestMethods::GET, '/one', new RouteOne(), ); $this->router->addRoute( RequestMethods::GET, '/two', new RouteTwo(), ); // route a request, receive response from the matching request handler $response = $router->handle( new ServerRequest('GET', '/one'), ); echo $response->getBody(); // outputs: {"message":"Hello world"}
Groups
For convenient, routes with the same prefix can be bundled together in a group.
Like so:
// create router instance and add an individual route $this->router = new Router(); $this->router->addRoute( RequestMethods::GET, '/one', // maps to /one new RouteOne(), ); // then 2 more routes with a shared prefix $group = $this->router->addGroup('/prefix'); $group->addRoute( RequestMethods::GET, '/two', // maps to /prefix/two new RouteTwo(), ); $group->addRoute( RequestMethods::GET, '/three', // maps to /prefix/three new RouteThree(), );
Middleware
In addition to PSR-15's request handlers, this router also supports PSR-15's Psr\Http\Server\MiddlewareInterface
,
which wrap around a request handler, executing either or both before and after handling the request.
This simplifies logic that is shared between multiple request handlers, like authentication, logging, etc.
Middleware can be added to individual routes, all routes within a group, or simply all routes.
Like this:
class Middleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // execute something before handling the request, // or manipulate the request object $request = $request->withAttribute('middleware', 'Hello world'); // invoke the handler (or the next middleware, if any) // and retrieve the response $response = $handler->handle($request); // execute something after handling the request, // or manipulate the response object return $response->withAddedHeader('Content-Type', 'application/json; charset=utf-8); } } // create router instance and add a route with middleware $this->router = new Router(); $this->router ->addRoute( RequestMethods::GET, '/one', new RouteOne(), ) ->addMiddleware( new Middleware(), ); // or add a group with middleware that applies to all routes within that group $this->router ->addGroup('/prefix') ->addMiddleware( new Middleware(), ) ->addRoute( RequestMethods::GET, '/two', new RouteTwo(), ); // or add middleware that applies to all routes $this->router->addMiddleware( new Middleware(), );
Exceptions
By default, any exception that is encountered, either within the request handler/middleware, or as part of routing (e.g. invalid route), will simply be thrown. It is, however, possible to add a custom exception handler, which will catch any exception and return a response.
This can be done by supplying an MatthiasMullie\Router\ExceptionResponseInterface
instance to the router, e.g. the provided MatthiasMullie\Router\ExceptionResponse
.
This will catch any exception and return a response with the appropriate status code.
This package also comes with a custom MatthiasMullie\Router\Exception
class that allows including HTTP status codes and headers in the exception.
Example:
use MatthiasMullie\Router\Exception; use MatthiasMullie\Router\ExceptionResponse; class RouteException implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { throw new Exception('Not Implemented', 501); } } $this->router = new Router(new ExceptionResponse(new Response())); $this->router->addRoute( RequestMethods::GET, '/exception', new RouteException(), ); $response = $router->handle( new ServerRequest('GET', '/exception'), ); // $response now includes the 501 Not Implemented status code & reason phrase
License
router is MIT licensed.