pswag/pswag

PHP 的首个 REST API 和 Swagger 生成工具

1.0.1 2024-08-28 07:57 UTC

This package is auto-updated.

Last update: 2024-09-29 15:22:40 UTC


README

轻松为您的 PHP 函数创建 REST API - 就像您可能从 ABP 框架的应用程序服务中了解的那样 - 不再需要与请求和响应打交道。您所需要做的就是为端点函数的方法参数和返回类型提供正确的类型定义。

虽然存在许多基于 API 的方法,它们可以从预定义的 OpenAPI 规范生成服务器端代码,或者将其作为代码的扩展从专有文档中推导出来,但 PSwag 项目旨在首先进行代码生成和自动生成(动态生成)Swagger 端点,这需要依赖适当的类型。

PSwag 是 Slim 的扩展,您可以使用它的所有功能。除此之外,PSwag 还为您提供以下好处

  • 它自动将您的自定义方法签名映射到 REST API 端点
  • 它提供了您的 REST API 端点的始终最新的 OpenAPI 3.0 规范
  • 它集成了 Swagger UI
  • 它支持 GET、PUT、DELETE、PATCH、POST
  • 它支持 PHP 内置类型、枚举、自定义类、数组(内置和自定义类型)、可空
  • 代码注释直接用于在 Swagger 中显示为描述
  • 调用 REST 端点时,请求将自动转换并调用 PHP 方法
  • PHP 方法的返回结果将自动转换为 REST 结果并返回给端点调用者
  • 支持 BasicAuth、Bearer 和 API Keys 进行身份验证

安装

建议您使用 Composer 安装 PSwag。

$ composer require pswag/pswag

这将安装 PSwag 和所有必需的依赖项。

基本示例:Petstore

让我们创建一个 Petstore 的示例。为了指定我们的 REST API 的端点,首先创建一个方法 getPetById,它接受一个 id 并返回一个类型为 Pet 的对象。

class PetApplicationService
{
    /**
     * Find pet by ID
     * @param int $petId ID of pet to return
     * @return Pet Returns a single pet
     */
    public function getPetById(int $petId): Pet {
        return new Pet(10, 'doggie', new Category(1, 'Dog'), ['photo1.jpg'], [new Tag(0, 'cute')], 'available');
    }
}

请注意,所有参数以及返回类型都需要进行适当的类型定义,以便 PSwag 能够推导出 OpenAPI 规范。方法注释可以用来提供描述或更具体的数据类型。

当使用自定义类型时,例如类 PetCategoryTag,所有这些类的属性也需要进行类型定义。让我们看看类 Pet 的例子

class Pet
{
    public ?int $id;

    public string $name;

    public ?Category $category;
    
    /** @var string[] $photoUrls */
    public array $photoUrls;
    
    /** @var ?PSwag\Example\Application\Dtos\Tag[] $tags */
    public ?array $tags;
    
    public ?string $status;
}
?>

对于 $photoUrls,类型 array 是不够的。在这种情况下,可以使用注释指定其唯一的数据类型,例如 /** @var string[] $photoUrls */。现在,PSwag 知道如何使用它来生成端点。同样适用于 $tags,但除此之外,还有一个自定义类用作数组。

请注意

  • 如果类 Tag 不与 Pet 在同一命名空间中,则必须使用完全限定的命名空间引用类 Tag,以便 PSwag 能够解析。
  • 如果您的模型包含不应公开的数据字段,或者继承的类包含不足以转换为 OpenAPI 规范的属性,则建议创建一个专门的数据传输对象(DTO)类,该类只包含有意属性,使用此 DTO 类型代替 API 签名,并在您的模型和此 DTO 类型之间进行映射。

最后,在 index.php 中创建一个 Slim 应用程序并注册方法 getPetById

<?php
require_once "vendor\\autoload.php";

use DI\Container;
use PSwag\PSwagApp;
use PSwag\Example\Application\Services\PetApplicationService;
use Slim\Factory\AppFactory;

AppFactory::setContainer(new Container()); // if you use dependency injection, PSwag does class loading for you. If you do not use DI, you must ensure to include all dtos explicitly. E.g.: require_once('application/dtos/Pet.php');
$slimApp = AppFactory::create();
$app = new PSwagApp($slimApp); // create wrapper PSwagApp
$app->addRoutingMiddleware(); // add routing middleware first, otherwise it would try to resolve route before swagger middleware can react
$app->addSwaggerUiMiddleware('/swagger', 'PSwag example', '1.0.0', 'vendor/swagger-api/swagger-ui/dist/'); // add swagger middleware: specify url pattern under which swagger UI shall be accessibile, and provide relative path to swagger ui dist.

// register endpoints by specifying class and method name
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById']);
$app->delete('/pet/{petId}', [PetApplicationService::class, 'deletePetById']);
$app->post('/pet', [PetApplicationService::class, 'createNewPet']);
$app->put('/pet', [PetApplicationService::class, 'updatePetById']);
$app->redirect('', './index.php/swagger'); // redirect root to swagger UI
$app->run();
?>

当调用 index.php/swagger 时,我们最终会得到以下内容

image

路径变量

在上面的示例中,路径变量用于GET和DELETE端点。当指定路径变量时,PSwag会尝试通过搜索相同的变量名来自动将路径变量映射到目标方法的参数。在执行端点时,将把此路径变量的值传递给具有相同名称的方法参数。如果没有这样的参数,实际上会忽略该值,并且不会将其传递给方法。

Swagger UI为路径变量提供了专门的输入字段。由于原始数据字段(查询参数或DTO属性)会与上述参数映射重复,PSwag将其从原始数据字段中删除。例如:端点/pet/{petId}使用为路径变量petId提供的值作为方法参数int $petId的值,并且将不再有额外的输入字段。PSwag会尝试从方法签名(本例中为int)自动推导值类型,并将其设置为对应路径变量的类型。

标签

Swagger的标签分组是自动从路径中提取的,斜杠后的第一个项目即为标签。例如,/pet/{petId}将被分组到标签pet下。然而,可以在将端点注册到PSwag时手动覆盖此标签,将其指定为数组的第三个元素。

// register endpoint with a custom tag as third parameter of the array
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById']); // defaults to 'pet'
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById', 'groupingTag']); // custom tag
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById', ['groupingTag']]); // can be also an array
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById', ['groupingTag', 'otherTag']]); // multiple tags
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById', []]); // no tag

身份验证

PSwag支持多种标准以保护您的API端点,这些标准都是开箱即用的。当使用时,OpenAPI规范也会自动包括相应的身份验证配置。

基本身份验证

要使用基本身份验证保护端点,创建一个继承自BasicAuthMiddleware的Middleware类。

class MyBasicAuthMiddleware extends BasicAuthMiddleware
{
    public function isUserCredentialValid(string $username, string $password): bool {
        return $username == "user" && $password == "1234"; // Do your magic here
    }
}

将此中间件添加到您想要保护的所有端点。现在您可以在Swagger UI上验证它是否按预期工作。

$basicAuthMiddleware = new MyBasicAuthMiddleware();
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById'])->add($basicAuthMiddleware);

请注意:当使用同一个中间件实例而不是为每个端点创建一个新实例来保护多个端点时,Swagger UI知道不需要为每个端点重新输入身份验证数据。

Bearer身份验证

要使用Bearer保护端点,创建一个继承自BearerAuthMiddleware的Middleware类。

class MyBearerAuthMiddleware extends BearerAuthMiddleware
{
    public function getBearerFormat(): ?string {
        return null; // There is no logic connected with it. Can be "JWT", for example.
    }
    
    public function isBearerTokenValid(string $bearerToken): bool {
        return $bearerToken == "1234"; // Do your magic here
    }
}

将此中间件添加到您想要保护的所有端点。现在您可以在Swagger UI上验证它是否按预期工作。

$bearerAuthMiddleware = new MyBearerAuthMiddleware();
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById'])->add($bearerAuthMiddleware);

请注意:当使用同一个中间件实例而不是为每个端点创建一个新实例来保护多个端点时,Swagger UI知道不需要为每个端点重新输入身份验证数据。

API密钥

要使用API密钥保护端点,创建一个继承自ApiKeysAuthMiddleware的Middleware类。

class MyApiKeyAuthMiddleware extends ApiKeyAuthMiddleware
{
    public function getName(): string {
        return "X-API-KEY"; // Specifies the name of the cookie / header / query param that will contain the API Key
    }

    public function getIn(): ApiKeyInType {
        return ApiKeyInType::Cookie; // Specifies how API Key is sent to the endpoint: Cookie, Header, Query 
    }
    
    public function isApiKeyValid(string $apiKey): bool {
        return $apiKey == "1234"; // Do your magic here
    }
}

请注意,当前不支持Query作为API密钥的传输类型。将此中间件添加到您想要保护的所有端点。现在您可以在Swagger UI上验证它是否按预期工作。

$apiKeyAuthMiddleware = new MyApiKeyAuthMiddleware();
$app->get('/pet/{petId}', [PetApplicationService::class, 'getPetById'])->add($apiKeyAuthMiddleware);

请注意:当使用同一个中间件实例而不是为每个端点创建一个新实例来保护多个端点时,Swagger UI知道不需要为每个端点重新输入身份验证数据。

OAuth 2.0

尚未支持。将在以后的版本中提供。

OpenID Connect Discovery

尚未支持。将在以后的版本中提供。