makise-co / http-router
PSR HTTP 路由器
Requires
- php: >=7.4
- makise-co/middleware: ^1.0
- nikic/fast-route: ^1.3
- php-di/invoker: ^2.1
- psr/container: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- guzzlehttp/psr7: ^1.7
- php-di/php-di: ^6.2
- phpstan/phpstan: ^0.12.48
- phpunit/phpunit: ^9.4
README
基于FastRoute的PSR HTTP路由器,支持中间件
需求
- PHP >= 7.4
安装
composer require makise-co/http-router
特性
- 中间件
- 依赖注入到路由处理程序(通过PSR容器和php-di/invoker)
- 支持大多数常见的请求处理程序声明(闭包、可调用、数组可调用、类@方法、类::方法)
- 遵循严格的PSR标准(PSR-7和PSR-15),每个路由处理程序必须返回PSR ResponseInterface的实例
- 高度可移植性和可定制性(任何实现部分都可以替换为您自己的实现)
常见问题解答
-
如何获取正在处理请求的路由实例?
$route = $request->getAttribute(RouteInterface::class);
-
如何获取路由参数?
// method 1 - Using DependencyInjection, many frameworks provides this way function apiEndpoint(int $id): \Psr\Http\Message\ResponseInterface { } // method 2 - Manually getting route arguments function apiEndpoint(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface { $args = $request->getAttribute(Router::ROUTE_ARGS); }
性能
对于三百万次调用(使用php-di/invoker)
Time took: 4.95980096 secs (0.00000165 secs per request)
对于三百万次调用(使用原生函数调用实现,不使用DI)
Time took: 3.31491995 secs (0.00000110 secs per request)
基准代码可以在这里找到。
- 基准测试在PHP 7.4和开启OPcache的情况下进行
- CPU: Intel Core i7-9750H 6核(基准测试时的CPU频率:4.07 GHz)
- 操作系统: Ubuntu 20.04(WSL 2)
用法
<?php declare(strict_types=1); use DI\Container; // Any PSR container implementation use MakiseCo\Http\Router\RouteCollectorFactory; use MakiseCo\Http\Router\RouteCollectorInterface; $collector = (new RouteCollectorFactory())->create( // container that is used to resolve route handlers // and inject dependencies into route handlers new Container(), ); $collector->get('/', function (): Response { return new Response(200, [], 'Welcome'); }); $collector->addGroup( 'api', [ 'namespace' => 'App\\Http\\Controllers\\', 'middleware' => [AddHeaderMiddleware1::class] ], function (RouteCollectorInterface $collector) { $collector ->get('/balance', function (): Response { return new Response(200, [], '228'); }) ->setName('api_balance') ->withMiddleware(AddHeaderMiddleware2::class) // or ->withMiddleware(new AddHeaderMiddleware2()) ->withAttribute('auth_permissions', ['customer']); $collector->post('/posts', function (ServerRequestInterface $request): Response { $body = $request->getParsedBody(); return new Response(200, [], json_encode($body)); }); $collector->get('/posts/{id:\d+}', function (int $id): Response { return new Response(200, [], "Post {$id}"); }); $collector->get('/user', 'UserController@index'); // or $collector->get('/user', [$userController, 'index']); // or $collector->get('/user', 'UserController::index'); // or $collector->get('/user', [UserController::class, 'index']); } ); $router = $collector->getRouter(); $request = new ServerRequest('GET', '/api/balance'); $response = $router->handle($request);
延迟解析
当您使用控制器方法编写路由处理程序时,您可能希望将一些依赖项注入到控制器构造函数中。默认情况下,所有依赖项将在您注册路由后自动注入到您的控制器中。
这种行为可能导致您的应用程序中出现各种错误或错误。让我们想象一下您的控制器类看起来像这样
class PostController { private ORMInterface $orm; public function __construct(ORMInterface $orm) { $this->orm = $orm; } public function index(): Response { $rows = $this ->orm ->getRepository(\App\Entity\Post::class) ->findAll(); return new Response(json_encode($rows)); } }
然后您为PostController的索引方法注册一个路由
$collector->get('/posts', 'PostController@index');
之后,您的PostController类的实例将被立即创建,并且将向其中注入ORM实例。ORM可以初始化数据库连接并查询数据库模式信息。
在多进程应用程序架构(当您的应用程序具有主进程和许多工作进程时)中,这可能导致不必要的资源消耗(耗尽数据库连接、内存消耗、较慢的应用程序启动时间)。
它甚至可能导致应用程序中的错误,特别是与Swoole后端一起使用时,因为所有非阻塞I/O操作必须在高并发上下文中执行。
此包提供了一个简单的解决方案来避免所有这些问题:延迟路由处理程序解析。只需使用RouteCollectorLazyFactory
而不是RouteCollectorFactory
。
而不是立即解析路由处理程序,这个过程将被推迟到路由编译阶段。
路由的编译阶段发生在您调用“RouteCollector”实例上的“getRouter”方法时。
这意味着路由处理程序是在调用“getRouter”方法时解析的。
错误处理
<?php declare(strict_types=1); use GuzzleHttp\Psr7\Response; use MakiseCo\Http\Router\Exception\MethodNotAllowedException; use MakiseCo\Http\Router\Exception\RouteNotFoundException; use MakiseCo\Middleware\ErrorHandlerInterface; use MakiseCo\Middleware\ErrorHandlingMiddleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; // It is just an example implementation (a more proper way is to use a response factory) class ErrorHandler implements ErrorHandlerInterface { public function handle(Throwable $e, ServerRequestInterface $request): ResponseInterface { if ($e instanceof RouteNotFoundException) { return new Response(404, [], $e->getMessage()); } if ($e instanceof MethodNotAllowedException) { // following https://tools.ietf.org/html/rfc7231#section-6.5.5 return new Response(405, ['Allow' => $e->getAllowedMethods()], $e->getMessage()); } return new Response(500, [], "Internal Server Error<br><br>{$e->getMessage()}"); } } $router = $collector->getRouter(); $app = (new \MakiseCo\Middleware\MiddlewarePipeFactory())->create([ new ErrorHandlingMiddleware(new ErrorHandler()), // placing error handling middleware first $router ]); // or $app = new \MakiseCo\Middleware\Dispatcher([ new ErrorHandlingMiddleware(new ErrorHandler()), // placing error handling middleware first $router ]); // or use any other middleware dispatcher $response = $app->handle($request);
更完整的示例可以在这里找到。