leven-framework / router
There is no license information available for the latest version (1.1.0) of this package.
1.1.0
2022-07-07 12:49 UTC
Requires
- php: >8.0
- rdlowrey/auryn: ^1.4
This package is auto-updated.
Last update: 2024-10-12 23:25:52 UTC
README
Features
- ๐ route parameters
- ๐ชน nestable route groups
- ๐ง configure routes with PHP8 attributes
- ๐ dependency injection using Auryn
- ๐ reverse routing (controller -> path)
- ๐ฅ middleware support (before and after)
Basic usage example
require 'vendor/autoload.php'; ($injector = new \Auryn\Injector)->share( $request = \Leven\Router\Messages\Request::fromSuperglobals() ); $rc = new \Leven\Router\RouterConfigurator( $router = new \Leven\Router\Router ); $rc->post('/demo', function(\Leven\Router\Messages\Request $request) { return "Hello {$request->body->name}"; }); $rc->get('/test/{id}', function(\Leven\Router\RouteParams $params) { return "ID: {$params->id}"; }); (new \Leven\Router\RouteHandler($injector))->handle($router->match($request))->dispatch();
Attribute configuration + reverse routing example
require 'vendor/autoload.php'; ($injector = new \Auryn\Injector)->share( $request = \Leven\Router\Messages\Request::fromSuperglobals() ); $router = new \Leven\Router\Router; class MyController { #[\Leven\Router\Route('GET', '/foo')] public function fooController(){ return new \Leven\Router\Messages\JsonResponse(['foo' => 'hello']); } #[\Leven\Router\Route('GET', '/bar')] public function barController(\Leven\Router\RouteUrlResolver $url){ $href = $url([static::class, 'fooController']); return new \Leven\Router\Messages\HtmlResponse("<a href='$href'>foo</a>"); } } (new \Leven\Router\ControllerScanner($router)) ->scanControllerClasses(MyController::class, ); // RouteUrlResolver allows you to get route path of a given controller $injector->share( new \Leven\Router\RouteUrlResolver($router) ); (new \Leven\Router\RouteHandler($injector))->handle($router->match($request))->dispatch();
Middleware example
require 'vendor/autoload.php'; ($injector = new \Auryn\Injector)->share( $request = \Leven\Router\Messages\Request::fromSuperglobals() ); $rc = new \Leven\Router\RouterConfigurator( $router = new \Leven\Router\Router ); class MyMiddleware { function __invoke(\Leven\Router\MiddlewareCallback $next) { $response = $next(); return "before $response->body after"; } } $rc->post('/cool', fn() => "something")->middleware(MyMiddleware::class); (new \Leven\Router\RouteHandler($injector))->handle($router->match($request))->dispatch();
Installation
PHP 8.1+ is required.
composer install leven-framework/router
Gotchas
- You're locked into using Auryn for dependency injection
- No support for other Request or Response classes like PSR-7
- There's no production optimization for scanned routes yet implemented
- This readme is only documentation for now
- No tests written yet
Documentation
<?php require 'vendor/autoload.php'; // first we need to create a dependency injector; check https://github.com/rdlowrey/auryn for more info $injector = new \Auryn\Injector(); // initialize a Request object automatically from the superglobals ($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER) $request = \Leven\Router\Messages\Request::fromSuperglobals(); // here we share the Request object with the injector, so Auryn can inject it into our controllers $injector->share($request); // this object will hold the route registry and perform route matching $router = new \Leven\Router\Router; // you can optionally specify middleware to be prepended to each route's middleware stack $router->addGlobalMiddleware( MyMiddleware::class, function( ){ }, [MyMiddleware::class, 'someMethod'], ); // you can use the RouterConfigurator to easily add routes to the router $config = new \Leven\Router\RouterConfigurator($router); $config->get('/demo/{id}', function( ){ }); $config->post('/demo/{id}', Controller::class) ->middlewareAppend(MyMiddleware::class); $config->map('PUT', '/test', [Controller::class, 'controllerMethod']) ->middlewarePrepend([MyMiddleware::class, 'middlewareMethod']); $config->map(['GET', 'POST'], '/test/{uuid}', function( ){ }) ->middleware(function( ){ }); // alias for middlewareAppend $config->group('/group', function(\Leven\Router\RouterConfigurator $group){ $group->addMiddleware(MyMiddleware::class); $group->get('/inner', MyController::class); // path: /group/inner $group->group('/deep', function(\Leven\Router\RouterConfigurator $group2){ $group2->put('/foo', function( ){ }); // path: /group/deep/foo }); }); // another way to define routes is by giving Route attributes to controller methods class MyController { #[\Leven\Router\Route('GET', '/foo/{id}')] public function __invoke( ) { /* ... */ } #[\Leven\Router\Route(['GET', 'POST'], '/bar/baz', middleware: [Middleware::class])] public function controllerMethod( ) { /* ... */ } } // ControllerScanner will scan classes for methods with Route attributes and configure the Router accordingly $scanner = new \Leven\Router\ControllerScanner($router); $scanner->scanControllerClasses(MyController::class, ); // RouteUrlResolver helps generate URLs by reverse matching a controller to a path $url = new \Leven\Router\RouteUrlResolver($router); $url = new \Leven\Router\RouteUrlResolver($router, prefix: '/api/v1/'); $url = new \Leven\Router\RouteUrlResolver($router, prefix: 'https://example.com/'); // share the RouteUrlResolver, so it can be used in controllers $injector->share($url); // in the controller, use RouteUrlResolver like this: $href = $url([MyController::class, 'controllerMethod']); // and your route has parameters, pass them as the second argument: $href = $url(MyController::class, ['id' => '123']); // when writing a controller, keep in mind all parameters // will be injected automatically by Auryn class AnotherController { // if your controller class has many controller methods, // you can provide shared dependencies in the class constructor public function __construct( protected \Leven\Router\RouteUrlResolver $url, protected \Leven\Router\Messages\Request $request, ){} public function __invoke( ) { // access the dependencies from constructor: $href = ($this->url)(MyController::class); } } // route parameters can be accessed in the controller through // the automatically injected RouteParams object function fooController(\Leven\Router\RouteParams $params) { return 'Hello ' . $params->name; } // middlewares control the flow of the request: // they can call the next middleware in the stack, or return a response function myMiddleware( \Leven\Router\MiddlewareCallback $next ) { // if you want to prevent the request from continuing, don't call $next() // you can also modify the response returned by $next() before returning it return $next(); // this middleware doesn't do anything } // each controller should return a Response object // if a string is returned, it will be converted // into a Response object with the string as the body function myController() { return new \Leven\Router\Messages\Response('Hello World!'); return 'Hello World!'; // same as above return new \Leven\Router\Messages\Response('wrong request', 400); return new \Leven\Router\Messages\JsonResponse(['message' => 'foo']); return new \Leven\Router\Messages\HtmlResponse('<h1>no access!</h1>', '403'); return new \Leven\Router\Messages\RedirectResponse('/foo'); } // the Request object has the following properties: $request->method // HTTP request method as a string $request->path // path of the request as a string $request->query // query string as an array $request->body // request body if any (decoded) $request->rawBody // raw request body if any $request->headers // request headers as an array $request->cookies // request cookies as an array $request->files // request files as an array // requests also contain custom attributes that may be useful // for passing data between middleware and controllers $request->setAttribute($name, $value); $foo = $request->getAttribute($name); // after configuring the Router, we match the Request to resolve a Route // if no route is found, the Router will throw a RouteNotFoundException try { $matchedRoute = $router->match($request); } catch (\Leven\Router\Exception\RouteNotFoundException $e) { die('route not found'); } // RouteHandler handles execution of a Route and its middleware stack $handler = new \Leven\Router\RouteHandler($injector); // it returns a Response object that we can manipulate if needed $response = $handler->handle($matchedRoute); // finally, we can send the response to the client $response->dispatch();