inroutephp/inroute

从文档注释生成 HTTP 路由和调度中间件

1.1.1 2019-12-23 22:43 UTC

README

Packagist Version Build Status Quality Score

从文档注释生成 HTTP 路由和调度中间件。

Inroute 是一个代码生成器。它扫描您的源代码树以查找注释路由,并生成符合 PSR-15 的 http 路由中间件。此外,所有路由都有自己的中间件管道,这使得根据自定义注释在编译时添加行为变得容易。

  • 查看example-app 以获取完整示例。
  • 查看console 以获取命令行编译工具。

安装

composer require inroutephp/inroute

目录

  1. 编写路由
  2. 通过中间件传递路由
  3. 编译
  4. 调度
  5. 生成路由路径
  6. 创建自定义注释
  7. 使用编译器遍历处理路由
  8. 使用依赖注入容器处理依赖关系
  9. 处理路由错误

编写路由

路由使用注释进行标注,通过 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')
        );
    }
}
  • methodpath 的值是自解释的。
  • 路由 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和一个响应输出功能(当然,您还应该使用完整的中间件管道以获得最大功效)。

在这个简单的示例中,我们使用

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,则该工厂将用于创建并返回一个404405 HTTP响应。

捕获异常

如果没有定义工厂,则会抛出inroutephp\inroute\Runtime\Exception\RouteNotFoundExceptioninroutephp\inroute\Runtime\Exception\MethodNotAllowedException异常。