菜谱
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
{
}
<?php
use OpenApi\Annotations 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()
{
}
}
<?php
use OpenApi\Annotations as OA;
class Controller
{
/**
* @OA\Schema(
* schema="Result",
* type="object",
* properties={
* @OA\Property(property="success", type="boolean"),
* },
* )
* @OA\Response(
* response=200,
* description="OK",
* @OA\JsonContent(
* oneOf={
* @OA\Schema(ref="#/components/schemas/Result"),
* @OA\Schema(type="boolean")
* },
* @OA\Examples(example="result", value={"success": true}, summary="An result object."),
* @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
{
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\OpenApi(
* @OA\ExternalDocumentation(
* description="More documentation here...",
* url="https://example.com/externaldoc1/"
* )
* )
*/
class OpenApiSpec
{
}
@OA\OpenApi,swagger-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
{
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\Schema(
* schema="StringList",
* @OA\Property(property="value", type="array", @OA\Items(anyOf={@OA\Schema(type="string")}))
* )
* @OA\Schema(
* schema="String",
* @OA\Property(property="value", type="string")
* )
* @OA\Schema(
* schema="Object",
* @OA\Property(property="value", type="object")
* )
* @OA\Schema(
* schema="mixedList",
* @OA\Property(property="fields", type="array", @OA\Items(oneOf={
* @OA\Schema(ref="#/components/schemas/StringList"),
* @OA\Schema(ref="#/components/schemas/String"),
* @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\Annotations as OA;
/**
* @OA\SecurityScheme(
* type="apiKey",
* name="api_key",
* in="header",
* securityScheme="api_key"
* )
*
* @OA\SecurityScheme(
* type="oauth2",
* securityScheme="petstore_auth",
* @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\Annotations 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\Annotations 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()
{
// ...
}
}
<?php
use OpenApi\Annotations as OA;
class Controller
{
/**
* @OA\Post(
* path="/v1/media/upload",
* summary="Upload document",
* description="",
* tags={"Media"},
* @OA\RequestBody(
* required=true,
* @OA\MediaType(
* mediaType="application/octet-stream",
* @OA\Schema(
* required={"content"},
* @OA\Property(
* description="Binary content of file",
* property="content",
* type="string",
* format="binary"
* )
* )
* )
* ),
* @OA\Response(
* response=200, description="Success",
* @OA\Schema(type="string")
* ),
* @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
{
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\Schema(
* schema="Error",
* @OA\Property(property="message"),
* @OA\Xml(name="details")
* )
* @OA\Post(
* path="/foobar",
* @OA\Response(
* response=400,
* description="Request error",
* @OA\XmlContent(ref="#/components/schemas/Error",
* @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()
{
// ...
}
}
<?php
use OpenApi\Annotations as OA;
class OpenApiSpec
{
/**
* @OA\Post(
* path="/v1/user/update",
* summary="Form post",
* @OA\RequestBody(
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* @OA\Property(property="name"),
* @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
{
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\OpenApi(
* security={{"bearerAuth": {}}}
* )
*
* @OA\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
{
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\Schema(
* schema="Profile",
* type="object",
*
* @OA\Property(
* property="Status",
* type="string",
* example="0"
* ),
*
* @OA\Property(
* property="Group",
* type="object",
*
* @OA\Property(
* property="ID",
* description="ID de grupo",
* type="number",
* example=-1
* ),
*
* @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()
{
// ...
}
}
<?php
use OpenApi\Annotations as OA;
class Controller
{
/**
* @OA\Response(
* response=200,
* @OA\JsonContent(
* oneOf={
* @OA\Schema(ref="#/components/schemas/QualificationHolder"),
* @OA\Schema(
* type="array",
* @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)
{
}
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\Response(
* response="product",
* description="All information about a product",
* @OA\JsonContent(ref="#/components/schemas/Product")
* )
*/
class ProductResponse
{
}
// ...
class ProductController
{
/**
* @OA\Get(
* tags={"Products"},
* path="/products/{product_id}",
* @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"”的警告
这种情况可能在两种场景下发生:
- 单个端点包含两个具有相同
response值的响应。 - 声明了多个全局响应,并且其中不止一个具有相同的
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\Get(
* ...
* callbacks={
* "onChange"={
* "{$request.query.callbackUrl}"={
* "post": {
* "requestBody": @OA\RequestBody(
* description="subscription payload",
* @OA\MediaType(mediaType="application/json", @OA\Schema(
* @OA\Property(property="timestamp", type="string", format="date-time", description="time of change")
* ))
* )
* },
* "responses": {
* "202": {
* "description": "Your server implementation should return this HTTP status code if the data was received successfully"
* }
* }
* }
* }
* }
* ...
* )
*/
(主要是)虚拟模型
通常,模型是通过向类添加 @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
{
}
<?php
use OpenApi\Annotations as OA;
/**
* @OA\Schema(
* @OA\Property(
* property="name",
* type="string",
* ),
* @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;
/** @OA\Property() */
public User author;
// 或者
/**
* @var User
* @OA\Property()
*/
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;
}
<?php
use OpenApi\Annotations 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()
{
// ...
}
}
<?php
use OpenApi\Annotations as OA;
class Controller
{
/**
* @OA\Get(
* path="/api/endpoint",
* description="The endpoint",
* operationId="endpoint",
* tags={"endpoints"},
* @OA\Parameter(
* name="things[]",
* in="query",
* description="A list of things.",
* required=false,
* @OA\Schema(
* type="array",
* @OA\Items(type="integer")
* )
* ),
* @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';
}
<?php
use OpenApi\Annotations 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