njeaner / router
一个简单的快速PHP路由器
Requires
- php: ^8.1
- psr/container: ^2.0
- psr/http-message: ^2.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- guzzlehttp/psr7: ^2.6
- njeaner/di-container: ^1.3
- phpunit/phpunit: ^11.1
- squizlabs/php_codesniffer: ^3.9
README
一个简单的PHP快速路由器
安装
composer require njeaner/router
如何使用它
路由器和路由初始化
不带 ContainerInterface 参数的初始化
$router = new \Njeaner\Router\Router();
带有 ContainerInterface 参数的初始化
对于需要容器来解析路由回调、路由中间件或路由策略参数的复杂项目,您可以通过传递 \Psr\Container\ContainerInterface 实例来初始化路由器。默认情况下,njeaner\router 包推荐使用 njeaner\container-di 包来处理包测试;但您可以使用任何其他实现 \Psr\Container\ContainerInterface 的容器包。
$container = new \NJContainer\Container\Container(); // or use any other container that implements \Psr\Container\ContainerInterface. $router = new \Njeaner\Router\Router($container);
注册路由
/** register get routes */ $router->get("/my-path", fn()=> "returned value when route callback is executed", "route_name"); /** register post routes */ $router->post("/my-path", fn()=> "returned value when route callback is executed", "route_name"); /** register put routes */ $router->put("/my-path", fn()=> "returned value when route callback is executed", "route_name"); /** register patch routes */ $router->patch("/my-path", fn()=> "returned value when route callback is executed", "route_name"); /** register delete routes */ $router->delete("/my-path", fn()=> "returned value when route callback is executed", "route_name"); /** register routes use any function that access \Njeaner\Router\RouteMethods enum as first argument */ $router->any(\Njeaner\Router\RouteMethods::get, "/my-path", fn()=> "returned value when route callback is executed", "route_name");
以下任一方法都返回 \Njeaner\Router\Routes\Route 实例
路由名称
路由名称参数可以是 null 或空,此时路由将用作路由名称
/** In the case below, route name is not defined. "my-path" will be use as route name */ $router->delete("/my-path", fn()=> "returned value when route callback is executed");
路由路径
路由路径接受参数,可以使用分号格式表示
- 。在这种情况下,可以使用 Njeaner\Router\Routes\Route::with() 函数来指示此路径参数的正则表达式格式
$router ->get("/posts/:id", fn(int $id) => Post::find($id), "post_show") ->with("id", "\d+");
- 括号格式。
$router ->get("/posts/{id:\d+}", fn(int $id) => Post::find($id), "post_show");
路由回调
路由回调接受几种格式
- 回调函数
- 一个可调用的控制器类名。
- 由 @ 或 # 连接的控制器类名和控制器动作名的字符串。
- 包含控制器类名和控制器动作名的数组
/** route with callback function */ $router->get("/posts", fn() => Post::getAll(), "post_index"); /** route invokable controller classname */ $router->get("/posts", \Namespace\MyController::class, "post_index"); /** route combining controller classname and controller action name */ $router->get("/posts", "\Namespace\MyController@index", "post_index"); // or $router->get("/posts", "\Namespace\MyController#index", "post_index"); /** route combining controller classname and controller action name */ $router->get("/posts", ["\Namespace\MyController", "index"], "post_index"); // or $router->get("/posts", ["controller" => "\Namespace\MyController", 'action' => "index"], "post_index");
路由解析
\Njeaner\Router\Router::resolve() 函数允许用户通过处理请求对象来检索之前在 \Njeaner\Router\Router 实例中注册的匹配路由。此方法接受两个参数
- 一个 \Psr\Http\Message\ServerRequestInterface 实例,默认情况下,njeaner/router 推荐使用 guzzlehttp/psr7 包
- 一个布尔参数,默认情况下,$throwException,允许在请求路径不匹配任何已注册的路由时返回 null 或抛出异常。
$router->get("/posts", fn()=>Post::getAll(), "post_index"); $router ->get('posts/:id', fn(int $id)=> Post::find($id), "post-show") ->with("id", "\d+"); $router ->post('posts/:id', fn($id)=> Post::find($id)->update($_POST), "post_show") ->with("id", "\d+"); $request = new \GuzzleHttp\Psr7\ServerRequest("get", "/posts/1"); $route = $router->resolve($request);
解析并返回匹配的路由允许用户决定何时处理路由回调函数。
处理路由回调函数(运行路由回调)
\Njeaner\Router\Router::run() 函数允许用户处理匹配的路由回调函数。此方法接受一个参数:一个 \Psr\Http\Message\ServerRequestInterface 实例,$request。
路由回调可以返回任何类型的值(字符串、数组、字符串化对象、psr7 响应对象),但返回的值将由 \Njeaner\Router\Router::run() 函数转换为 \Psr\Http\Message\ResponseInterface 实例
默认情况下,Njeaner\Router\Routes\Route 实例能够解析路由路径参数,将这些参数作为路由属性转换,如果需要,将这些属性和请求实例注入到路由回调函数中。
// route initialization $router ->post( "/publication/{type:[a-z]+}-{status:\d+}", function(ServerRequestInterface $request, string $type, int $status){ return Post::find(["type" => $type, "status" => $status]) ->update($request->getParsedBody()) }, "publication_edition" ); // build request instance $request = new \ServerRequest("post", "/publication/post-1"); // process route callback. $type value will be "post" and $status value will be 1 $router->run($request);
对于大多数复杂的路由回调参数解析,需要将容器实例注入到路由器实例中。
// defining controller class class PublicationController{ public function __construct(private RepositoryManager $repositoryManager) {} public function index(string $type) { return $this->repositoryManager->get($type)->getAll(); } } // defining repository manager class class RepositoryManager{ public function __construct(private \PDO $pdo) {} public function get(string $type): RepositoryInterface { // my code logic } } // container initialization $myContainer = new \NJContainer\Container\Container(); // or nay other container $myContainer->set(\PDO::class, $somePdoInstance); // inject pdo instance to container $router ->get( "/publication/{type:[a-z]+}", "PublicationController@index", // or "PublicationController#index" or ["PublicationController", "index"] "publication_edition" ) ->setContainer($myContainer); // Container instance will be inject during router initialization // build request instance $request = new \ServerRequest("get", "/publication/post"); //RepositoryManager and PublicationController instances will be automatically resolved and index method will be processed $router->run($request);
路由中间件
在处理路由回调函数之前调用和处理路由中间件。这允许用户在匹配当前请求路径的路由时执行某些操作。路由中间件可以是一个可调用的函数、一个可调用的类、一个包含中间件类名及其方法名的数组,或 \Psr\Http\Server\MiddlewareInterface 类的名称。
中间件通过 \Njeaner\Router\Router::middleware() 函数或 \Njeaner\Router\Router::middlewares() 设置多个路由中间件。中间件可调函数接受两个参数
- 一个 $request 参数,它是一个 \Psr\Http\Message\ServerRequestInterface 的实例;
- 一个 $next(或 $handler 对于 \Psr\Http\Server\MiddlewareInterface)参数,实际上是与匹配的路由实例。因此,Njeaner\Router\Routes\Route 实现了 Psr\Http\Server\RequestHandlerInterface 并是一个可调用的类。
注意:最后一个中间件响应将被转换为 \Psr\Http\Message\ResponseInterface。
// defining invokable middleware class InvokableMiddleware{ public function __invoke(ServerRequestInterface $request, $next){ // your middleware logic code return $next($request); } } // defining callable middleware class CallableMiddleware{ public function action(ServerRequestInterface $request, $next){ // your middleware logic code return $next($request); } } // defining \Psr\Http\Server\MiddlewareInterface class PsrMiddleware implement \Psr\Http\Server\MiddlewareInterface{ public function process(ServerRequestInterface $request, Psr\Http\Server\RequestHandlerInterface $handler){ // your middleware logic code return $handler->handle($request); } } // register middlewares $router ->middleware(InvokableMiddleware::class, function(RouterInterface $router){ $router->middlewares([ PsrMiddleware::class, [CallableMiddleware::class, 'action'], fn($request, $next) => $next($request) ], function($router){ $router->post( "/posts/{id:\d+}", fn(ServerRequestInterface $request, int $id) => Post::find($id) ->update($request->getParsedBody()), "post_show" ); }) }) $request = new \ServerRequest("post", "/post/1"); $router->run($request);
路由策略
路由策略可以是一个布尔值,一个可调用的类名,一个可调用的函数,或者一个包含策略类名和它可调用方法的数组。策略允许只有在它的值或处理后的值为 true 时,才执行 \Njeaner\Router\Router::run() 函数。
$router ->post( "/posts/{id:\d+}", fn(ServerRequestInterface $request, int $id) => Post::find($id)->update($request->getParsedBody()), "post_show" ) ->setPolicy(fn(int $id) => $id !== 1); $request = new \ServerRequest("post", "/post/1"); $router->run($request);