inroutephp / inroute
从文档注释生成 HTTP 路由和调度中间件
Requires
- php: >=7.2
- aura/router: ^3
- psr/container: ^1
- psr/http-factory: ^1
- psr/http-message: ^1
- psr/http-server-middleware: ^1
- symfony/var-exporter: ^5
Requires (Dev)
Suggests
- doctrine/annotations:1: For compiling
- zircote/swagger-php:3: For building openapi apps
This package is auto-updated.
Last update: 2024-08-25 00:31:18 UTC
README
从文档注释生成 HTTP 路由和调度中间件。
Inroute 是一个代码生成器。它扫描您的源代码树以查找注释路由,并生成符合 PSR-15 的 http 路由中间件。此外,所有路由都有自己的中间件管道,这使得根据自定义注释在编译时添加行为变得容易。
- 查看example-app 以获取完整示例。
- 查看console 以获取命令行编译工具。
安装
composer require inroutephp/inroute
目录
编写路由
路由使用注释进行标注,通过 PSR-7 请求对象和 inroute 环境 调用,并期望返回一个 PSR-7 响应。
use inroutephp\inroute\Annotations\BasePath; use inroutephp\inroute\Annotations\GET; use inroutephp\inroute\Runtime\EnvironmentInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Laminas\Diactoros\Response\TextResponse; /** * @BasePath(path="/users") */ class UserController { /** * @GET( * path="/{name}", * name="getUser", * attributes={ * "key": "value", * "name": "overwritten by path value" * } * ) */ function getUser( ServerRequestInterface $request, EnvironmentInterface $environment ): ResponseInterface { return new TextResponse( // the name attribute from the request path $request->getAttribute('name') // the custom route attribute . $request->getAttribute('key') ); } }
method
和path
的值是自解释的。- 路由
name
是可选的,默认为class:method
(在示例中为UserController:getUser
)。 Attributes
是可以在运行时通过请求对象访问的自定义值。- 注意,本例中使用了 Laminas diactoros 作为 psr-7 响应实现,当然您也可以使用其他 psr-7 实现。
通过中间件传递路由
每个路由都有自己的 PSR-15 中间件管道。可以使用 @Pipe
注释向路由添加中间件。在以下示例中,将 pipedAction
路由通过 AppendingMiddleware
,并将文本 ::Middleware
添加到路由响应中。
use inroutephp\inroute\Annotations\Pipe; class PipedController { /** * @GET(path="/piped") * @Pipe(middlewares={"AppendingMiddleware"}) */ function pipedAction( ServerRequestInterface $request, EnvironmentInterface $environment ): ResponseInterface { return new TextResponse('Controller'); } } use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; class AppendingMiddleware implements MiddlewareInterface { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { $response = $handler->handle($request); return new TextResponse( $response->getBody()->getContents() . "::Middleware" ); } }
编译
构建项目的推荐方式是使用 console 构建工具。从纯 PHP 编译需要设置编译器,如下所示。
use inroutephp\inroute\Compiler\CompilerFacade; use inroutephp\inroute\Compiler\Settings\ArraySettings; $settings = new ArraySettings([ 'source-classes' => [ UserController::CLASS, PipedController::CLASS, ], 'target-namespace' => 'example', 'target-classname' => 'HttpRouter', ]); $facade = new CompilerFacade; $code = $facade->compileProject($settings); eval($code); $router = new example\HttpRouter;
可能的设置包括
container
:编译时容器的类名,如有需要则指定。bootstrap
:编译引导类的类名,默认值通常可以。source-dir
:要扫描注释路由的目录。source-prefix
:扫描目录时要使用的 psr-4 命名空间前缀。source-classes
:源类名数组,代替或与目录扫描一起使用。ignore-annotations
:要忽略的注释数组,在编译期间使用。route-factory
:路由工厂的类名,默认值通常可以。compiler
:要使用的编译器的类名,默认值通常可以。core-compiler-passes
:核心编译器遍历数组,默认值通常可以。compiler-passes
:自定义编译器遍历数组。code-generator
:要使用的代码生成器的类名,默认值通常可以。target-namespace
:生成路由器的命名空间(默认为无命名空间)。target-classname
:生成路由器的类名(默认为HttpRouter
)。
OpenApi
请注意,读取openapi注解仍然非常基础。如果您有关于应解析的更多值的建议,请提交一个问题。
除了使用内置的inroute注解外,还可以构建带有swagger-php注解的openapi项目。
将core-compiler-passes
设置设置为['inroutephp\inroute\OpenApi\OpenApiCompilerPass']
。
调度
生成的路由器是一个符合PSR-15规范的中间件。要分发,您需要提供实现请求和响应对象的PSR-7和一个响应输出功能(当然,您还应该使用完整的中间件管道以获得最大功效)。
在这个简单的示例中,我们使用
- laminas-diactoros作为PSR-15实现。
- laminas-httphandlerrunner用于输出响应。
- 内置的中间件管道用于分发。
use inroutephp\inroute\Runtime\Middleware\Pipeline; use Laminas\Diactoros\ServerRequestFactory; use Laminas\HttpHandlerRunner\Emitter\SapiEmitter; // create a simple middleware pipeline for the entire application $pipeline = new Pipeline($router); // create a psr-7 compliant response emitter $emitter = new SapiEmitter; // fakeing a GET request $request = (new ServerRequestFactory)->createServerRequest('GET', '/users/foo'); // in the real worl you would of course use // $request = ServerRequestFactory::fromGlobals(); // create the response $response = $pipeline->handle($request); // send it $emitter->emit($response);
或者发送到上面的管道示例
use inroutephp\inroute\Runtime\Middleware\Pipeline; use Laminas\Diactoros\ServerRequestFactory; use Laminas\HttpHandlerRunner\Emitter\SapiEmitter; (new SapiEmitter)->emit( (new Pipeline($router))->handle( (new ServerRequestFactory)->createServerRequest('GET', '/piped') ) );
生成路由路径
function getUser(ServerRequestInterface $request, EnvironmentInterface $environment): ResponseInterface { return new TextResponse( $environment->getUrlGenerator()->generateUrl('getUser', ['name' => 'myUserName']) ); }
创建自定义注释
Inroute使用doctrine来读取注解。创建自定义注解就像
namespace MyNamespace; /** * @Annotation */ class MyAnnotation { public $value; }
要创建自动将路由通过中间件管道的注解,可以使用以下类似的方法。
namespace MyNamespace; use inroutephp\inroute\Annotations\Pipe; /** * @Annotation */ class AdminRequired extends Pipe { public $middlewares = ['AuthMiddleware', 'RequireUserGroupMiddleware']; public $attributes = ['required_user_group' => 'admin']; }
请注意,您需要提供
AuthMiddleware
来验证用户,以及提供RequireUserGroupMiddleware
来检查用户的权限,以便此示例按预期运行。下面将介绍如何注入可以提供这些中间件的依赖注入容器。
以及注释您的控制器方法
use MyNamespace\MyAnnotation; use MyNamespace\AdminRequired; class MyController { /** * @MyAnnotation(value="foobar") * @AdminRequired */ public function aRouteThatIsOnlyOpenToAdminUsers() { } }
使用编译器遍历处理路由
自定义注解与自定义编译器传递结合使用最有用。
use inroutephp\inroute\Compiler\CompilerPassInterface; use inroutephp\inroute\Runtime\RouteInterface; use MyNamespace\MyAnnotation; class MyCompilerPass implements CompilerPassInterface { public function processRoute(RouteInterface $route): RouteInterface { if ($route->hasAnnotation(MyAnnotation::CLASS)) { return $route ->withAttribute('cool-attribute', $route->getAnnotation(MyAnnotation::CLASS)->value) ->withMiddleware(SomeCoolMiddleware::CLASS); } return $route; } }
每个路由都有自己的中间件管道。在上述示例中,所有带有MyAnnotation
注解的路由都将被SomeCoolMiddleware
包裹。这使得在编译时根据注解添加自定义行为变得很容易。
您可以在中间件中使用$request->getAttribute('cool-attribute')
来访问cool-attribute
属性。
使用依赖注入容器处理依赖关系
您可能已经注意到,在上述示例中,SomeCoolMiddleware
不是作为实例化的对象传递,而是作为类名传递。实际的对象是在运行时使用一个符合PSR-11规范的依赖注入容器创建的。控制器类也是一样。
作为您的分发逻辑的一部分创建容器,并使用setContainer()
方法将其传递给路由器。
$container = /* your custom setup */; $router = new example\HttpRouter; $router->setContainer($container); // continue dispatch...
处理路由错误
找不到路由(HTTP代码404
)和方法不允许(405
)的情况可以通过两种方式之一处理。
使用ResponseFactoryInterface
如果您容器中包含一个服务Psr\Http\Message\ResponseFactoryInterface
,则该工厂将用于创建并返回一个404
或405
HTTP响应。
捕获异常
如果没有定义工厂,则会抛出inroutephp\inroute\Runtime\Exception\RouteNotFoundException
或inroutephp\inroute\Runtime\Exception\MethodNotAllowedException
异常。