Lzh on GitHub

x-tagGroups

OpenApi 有一个使用标签对端点进行分组的概念。此外,某些工具(例如 Redocly)支持通过供应商扩展 x-tagGroups 进行进一步分组。

<?php

use OpenApi\Attributes as OA;

#[OA\OpenApi(
    x: [
        'tagGroups' => [
            ['name' => 'User Management', 'tags' => ['Users', 'API keys', 'Admin']],
        ],
    ]
)]
class OpenApiSpec
{
}

@OA\Response 中添加示例

<?php

use OpenApi\Attributes as OA;

class Controller
{
    #[OA\Schema(
        schema: 'Result',
        type: 'object',
        properties: [
            new OA\Property(property: 'success', type: 'boolean'),
        ],
    )]
    #[OA\Response(
        response: 200,
        description: 'OK',
        content: new OA\JsonContent(
            oneOf: [
                new OA\Schema(ref: '#/components/schemas/Result'),
                new OA\Schema(type: 'boolean'),
            ],
            examples: [
                new OA\Examples(example: 'result', value: ['success' => true], summary: 'An result object.'),
                new OA\Examples(example: 'bool', value: false, summary: 'A boolean value.'),
            ],
        ),
    )]
    public function operation()
    {
    }
}

外部文档 (External documentation)

OpenApi 允许对外部文档进行单个引用。这是顶层 @OA\OpenApi 的一部分。

<?php

use OpenApi\Attributes as OA;

#[OA\OpenApi(
    externalDocs: new OA\ExternalDocumentation(
        description: 'More documentation here...',
        url: 'https://example.com/externaldoc1/',
    ),
)]
class OpenApiSpec
{
}
如果没有配置 @OA\OpenApiswagger-php 会自动创建一个。
这意味着上面的示例即使只有 OA\ExternalDocumentation 注解也能工作。
/**
 * @OA\ExternalDocumentation(
 *   description="More documentation here...",
 *   url="https://example.com/externaldoc1/"
 * )
 */

具有联合类型的属性 (Properties with union types)

有时属性甚至列表(数组)可能包含不同类型的数据。这可以使用 oneOf 来表示。

<?php

use OpenApi\Attributes as OA;

#[OA\Schema(
    schema: 'StringList',
    properties: [
        new OA\Property(
            property: 'value',
            type: 'array',
            items: new OA\Items(
                anyOf: [new OA\Schema(type: 'string')]
            ),
        ),
    ],
)]
#[OA\Schema(
    schema: 'String',
    properties: [
        new OA\Property(
            property: 'value',
            type: 'string',
        ),
],
)]
#[OA\Schema(
    schema: 'Object',
    properties: [
        new OA\Property(
            property: 'value',
            type: 'object',
        ),
],
)]
#[OA\Schema(
    schema: 'mixedList',
    properties: [
        new OA\Property(
            property: 'fields',
            type: 'array',
            items: new OA\Items(
                oneOf: [
                    new OA\Schema(ref: '#/components/schemas/StringList'),
                    new OA\Schema(ref: '#/components/schemas/String'),
                    new OA\Schema(ref: '#/components/schemas/Object'),
                ],
            ),
        ),
    ],
)]
class OpenApiSpec
{
}

这将解析成以下 YAML

openapi: 3.0.0
components:
  schemas:
    StringList:
      properties:
        value:
          type: array
          items:
            anyOf:
              -
                type: string
      type: object
    String:
      properties:
        value:
          type: string
      type: object
    Object:
      properties:
        value:
          type: object
      type: object
    mixedList:
      properties:
        fields:
          type: array
          items:
            oneOf:
              -
                $ref: '#/components/schemas/StringList'
              -
                $ref: '#/components/schemas/String'
              -
                $ref: '#/components/schemas/Object'
      type: object

引用安全方案 (Referencing a security scheme)

一个 API 可能有零个或多个安全方案。这些是在顶层定义的,从简单到复杂各不相同:

<?php

use OpenApi\Attributes as OA;

#[OA\SecurityScheme(
    type: 'apiKey',
    name: 'api_key',
    in: 'header',
    securityScheme: 'api_key',
)]
#[OA\SecurityScheme(
    type: 'oauth2',
    securityScheme: 'petstore_auth',
    flows: [
        new OA\Flow(
            authorizationUrl: 'http://petstore.swagger.io/oauth/dialog',
            flow: 'implicit',
            scopes: [
                'read:pets' => 'read your pets',
                'write:pets' => 'modify pets in your account',
            ],
        ),
    ],
)]
class OpenApiSpec
{
}

要将一个端点声明为安全的,并定义可用于客户端认证的安全方案,需要将其添加到操作中,例如:

<?php

use OpenApi\Attributes as OA;

class Controller
{
    #[OA\Get(
        path: '/api/secure/',
        summary: 'Requires authentication',
        security: [
            [
                'api_key' => [],
            ],
        ],
    )]
    public function getSecurely()
    {
        // ...
    }
}
端点可以支持多种安全方案,并具有自定义选项:
<?php

use OpenApi\Attributes as OA;

class Controller
{
    #[OA\Get(
        path: '/api/secure/',
        summary: 'Requires authentication',
        security: [
            ['api_key' => []],
            ['petstore_auth' => ['write:pets', 'read:pets']],
        ]
    )]
    public function secure()
    {
    }
}

带有头部的文件上传

<?php

use OpenApi\Attributes as OA;

class Controller
{
    #[OA\Post(
        path: '/v1/media/upload',
        summary: 'Upload document',
        description: '',
        tags: ['Media'],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\MediaType(
                mediaType: 'application/octet-stream',
                schema: new OA\Schema(
                    required: ['content'],
                    properties: [
                        new OA\Property(
                            description: 'Binary content of file',
                            property: 'content',
                            type: 'string',
                            format: 'binary',
                        ),
                    ],
                ),
            ),
        ),
    )]
    #[OA\Response(
        response: 200,
        description: 'Success',
    )]
    #[OA\Response(
        response: 400,
        description: 'Bad Request',
    )]
    public function upload()
    {
        // ...
    }
}

设置 XML 根名称

OA\Xml 注解可用于为给定的 @OA\XmlContent 响应体设置 XML 根元素。

<?php

use OpenApi\Attributes as OA;

#[OA\Schema(
    schema: 'Error',
    properties: [new OA\Property(property: 'message')],
    xml: new OA\Xml(name: 'details'),
)]
#[OA\Post(
    path: '/foobar',
    responses: [
        new OA\Response(
            response: 400,
            description: 'Request error',
            content: new OA\XmlContent(
                ref: '#/components/schemas/Error',
                xml: new OA\Xml(name: 'error'),
            ),
        ),
    ],
)]
class OpenApiSpec
{
}

上传 multipart/form-data

表单提交是带有 multipart/form-data @OA\RequestBody@OA\Post 请求。相关部分看起来像这样:

<?php

use OpenApi\Attributes as OA;

class OpenApiSpec
{
    #[OA\Post(
        path: '/v1/user/update',
        summary: 'Form post',
        requestBody: new OA\RequestBody(
            content: new OA\MediaType(
                mediaType: 'multipart/form-data',
                schema: new OA\Schema(
                    properties: [
                        new OA\Property(
                            property: 'name',
                        ),
                        new OA\Property(
                            description: 'file to upload',
                            property: 'avatar',
                            type: 'string',
                            format: 'binary',
                        ),
                    ],
                ),
            ),
        ),
    )]
    #[OA\Response(
        response: 200,
        description: 'Success'
    )]
    public function update()
    {
        // ...
    }
}

所有端点的默认安全方案

除非另有指定,每个端点都需要声明它支持的安全方案。然而,有一种方法可以为整个 API 全局配置安全方案。

这在 @OA\OpenApi 注解上完成:

<?php

use OpenApi\Attributes as OAT;

#[OAT\OpenApi(
    security: [['bearerAuth' => []]]
)]
#[OAT\Components(
    securitySchemes: [
        new OAT\SecurityScheme(
            securityScheme: 'bearerAuth',
            type: 'http',
            scheme: 'bearer'
        ),
    ]
)]
class OpenApiSpec
{
}

嵌套对象 (Nested objects)

复杂的嵌套数据结构是通过将 @OA\Property 注解嵌套在其他注解(带有 type="object")中来定义的。

<?php

use OpenApi\Attributes as OA;

#[OA\Schema(
    schema: 'Profile',
    type: 'object',
    properties: [
        new OA\Property(
            property: 'Status',
            type: 'string',
            example: '0',
        ),
        new OA\Property(
            property: 'Group',
            type: 'object',
            properties: [
                new OA\Property(
                    property: 'ID',
                    description: 'ID de grupo',
                    type: 'number',
                    example: -1,
                ),
                new OA\Property(
                    property: 'Name',
                    description: 'Nombre de grupo',
                    type: 'string',
                    example: 'Superadmin'
                ),
            ],
        ),
    ],
)]
class OpenApiSpec
{
}

使用 oneOf 文档化联合类型响应数据

一个响应可能包含一个或一个 QualificationHolder 列表。

<?php

use OpenApi\Attributes as OA;

class Controller
{
    #[OA\Response(
        response: 200,
        content: new OA\JsonContent(
            oneOf: [
                new OA\Schema(ref: '#/components/schemas/QualificationHolder'),
                new OA\Schema(
                    type: 'array',
                    items: new OA\Items(ref: '#/components/schemas/QualificationHolder')
                ),
            ],
        ),
    )]
    public function index()
    {
        // ...
    }
}

重用响应 (Reusing responses)

全局响应位于 /components/responses 下,可以像模式定义(模型)一样被引用/共享。

<?php

use OpenApi\Attributes as OA;

#[OA\Response(
    response: 'product',
    description: 'All information about a product',
    content: new OA\JsonContent(ref: '#/components/schemas/Product'),
)]
class ProductResponse
{
}

// ...

class ProductController
{
    #[OA\Get(
        tags: ['Products'],
        path: '/products/{product_id}',
        responses: [
            new OA\Response(
                response: 'default',
                ref: '#/components/responses/product'
            ),
        ],
    )]
    public function getProduct($id)
    {
    }
}
response 参数始终是必需的
即使引用了共享的响应定义,response 参数仍然是必需的。

mediaType="/"

使用 */* 作为 mediaType 在使用注解时是不可能的。

示例:

/**
 * @OA\MediaType(
 *     mediaType="*/*",
 *     @OA\Schema(type="string",format="binary")
 * )
 */

用于解析注解的 doctrine annotations 库无法处理这一点,并会将 */ 部分解释为注释的结束。

使用 *application/octet-stream 可能是可行的变通方案。

关于“多个响应具有相同的 response="200"”的警告

这种情况可能在两种场景下发生:

  1. 单个端点包含两个具有相同 response 值的响应。
  2. 声明了多个全局响应,并且其中不止一个具有相同的 response 值。

回调 (Callbacks)

API 包含了对回调的基本支持。然而,这大部分需要手动设置。

示例

#[OA\Get(
    // ...
    callbacks: [
        'onChange' => [
            '{$request.query.callbackUrl}' => [
                'post' => [
                    'requestBody' => new OA\RequestBody(
                        description: 'subscription payload',
                        content: [
                            new OA\MediaType(
                                mediaType: 'application/json',
                                schema: new OA\Schema(
                                    properties: [
                                        new OA\Property(
                                            property: 'timestamp',
                                            type: 'string',
                                            format: 'date-time',
                                            description: 'time of change'
                                        ),
                                    ],
                                ),
                            ),
                        ],
                    ),
                ],
            ],
        ],
    ],
    // ...
)]

(主要是)虚拟模型

通常,模型是通过向类添加 @OA\Schema 注解,然后向单独声明的类属性添加 @OA\Property 注解来注解的。

然而,即使没有属性,也可以将 O@\Property 注解嵌套在模式中。实际上,所需要的只是一个代码锚点——例如一个空类。

<?php

use OpenApi\Attributes as OA;

#[OA\Schema(
    properties: [
        'name' => new OA\Property(property: 'name', type: 'string'),
        'email' => new OA\Property(property: 'email', type: 'string'),
    ]
)]
class User
{
}

使用类名作为类型而不是引用

通常,引用模式是通过 $ref 完成的。

#[OAT\Schema(schema: 'user')]
class User
{
}

#[OAT\Schema()]
class Book
{
    /**
     * @var User
     */
    #[OAT\Property(ref: '#/components/schemas/user')]
    public $author;
}

这种方式可行,但不是很方便。

首先,当使用自定义模式名称(schema: 'user')时,需要在所有地方都考虑到这一点。其次,必须编写 ref: '#/components/schemas/user' 既繁琐又容易出错。

使用属性时,这一切都改变了,因为我们可以利用 PHP 本身,通过其(完全限定的)类名来引用模式。

沿用之前的 User 模式,Book::author 属性可以用几种不同的方式编写:

#[OAT\Property()]
public User author;

// 或者

/**
 * @var User
 */
#[OAT\Property()]
public author;

// 或者

#[OA\Property(type: User::class)]
public author;

枚举 (Enums)

从 PHP 8.1 开始,原生支持 枚举

swagger-php 支持枚举的方式与使用类名引用模式的方式非常相似。

示例

<?php

use OpenApi\Attributes as OA;

#[OA\Schema()]
enum State
{
    case OPEN;
    case MERGED;
    case DECLINED;
}

#[OA\Schema()]
class PullRequest
{
    #[OA\Property()]
    public State $state;
}

然而,在这种情况下,为 State 生成的模式将是一个枚举:

components:
  schemas:
    PullRequest:
      properties:
        state:
          $ref: '#/components/schemas/State'
      type: object
    State:
      type: string
      enum:
        - OPEN
        - MERGED
        - DECLINED

多值查询参数:&q[]=1&q[]=1

PHP 允许在 URL 中多次使用查询参数,如果参数名带有末尾的 [],它会将值组合成一个数组。事实上,通过使用多对 [],甚至可以创建嵌套数组。

在 OpenAPI 方面,这些参数可以被视为具有值列表的单个参数。

<?php

use OpenApi\Attributes as OA;

class Controller
{
    #[OA\Get(
        path: '/api/endpoint',
        description: 'The endpoint',
        operationId: 'endpoint',
        tags: ['endpoints'],
        parameters: [
            new OA\Parameter(
                name: 'things[]',
                in: 'query',
                description: 'A list of things.',
                required: false,
                schema: new OA\Schema(
                    type: 'array',
                    items: new OA\Items(type: 'integer')
                )
            ),
        ],
        responses: [
            new OA\Response(response: '200', description: 'All good'),
        ]
    )]
    public function endpoint()
    {
        // ...
    }
}

规范中相应的代码段将如下所示:

      parameters:
        -
          name: 'things[]'
          in: query
          description: 'A list of things.'
          required: false
          schema:
            type: array
            items:
              type: integer

swagger-ui 将显示一个表单,允许向列表中添加/删除项目(本例中为整数值),并以类似 ?things[]=1&things[]=2&things[]=0 的形式提交这些值。

自定义响应类 (Custom response classes)

即使使用引用,共享响应也有一点开销。一个解决办法是编写自己的响应类。这样做的好处是,你可以在自定义的 __construct() 方法中预先填充你需要的所有内容。

最棒的是,这对于注解和属性都有效。

示例:

use OpenApi\Attributes as OA;

/**
 * @Annotation
 */
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class BadRequest extends OA\Response
{
    public function __construct()
    {
        parent::__construct(response: 400, description: 'Bad request');
    }
}

class Controller
{
    #[OA\Get(path: '/foo', responses: [new BadRequest()])]
    public function get()
    {
    }

    #[OA\Post(path: '/foo')]
    #[BadRequest]
    public function post()
    {
    }

    /**
     * @OA\Delete(
     * path="/foo",
     * @BadRequest()
     * )
     */
    public function delete()
    {
    }
}
仅限注解?
如果你只对注解感兴趣,可以省略 BadRequest 的属性设置行(#[\Attribute...)
此外,你的自定义注解应该继承自 OpenApi\Annotations 命名空间。

注解类常量 (Annotating class constants)

<?php

use OpenApi\Attributes as OA;

#[OA\Schema()]
class Airport
{
    #[OA\Property(property: 'kind')]
    public const KIND = 'Airport';
}

const 属性在 OpenApi 3.1.0 中得到支持。

components:
  schemas:
    Airport:
        properties:
          kind:
            type: string
            const: Airport

对于 3.0.0 版本,这被序列化为一个单值枚举。

components:
  schemas:
    Airport:
        properties:
          kind:
            type: string
            enum:
              - Airport