njeaner/router

一个简单的快速PHP路由器

V1.0.0 2024-05-07 19:06 UTC

This package is auto-updated.

Last update: 2024-09-09 07:09:02 UTC


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);