diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 859bfa8eb..8527d9c7d 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -7,6 +7,7 @@ ->path('src')->name('*.php') ->path('tools')->name('*.php') ->path('docs/examples')->name('*.php') + ->path('docs/snippets')->name('*.php') ->path('tests')->name('*.php') ->filter(function (\SplFileInfo $file) { return diff --git a/docs/guide/common-techniques.md b/docs/guide/common-techniques.md index 31286abd3..ae6e8786e 100644 --- a/docs/guide/common-techniques.md +++ b/docs/guide/common-techniques.md @@ -20,22 +20,19 @@ a structural element (`class`, `method`, `parameter` or `enum`) This means in a lot of cases it is not necessary to explicitly document all details. **Example** -```php - + + + **Results in** ```yaml @@ -52,48 +49,50 @@ components: **As if you'd written** -```php - /** - * The product name - * @var string - * - * @OA\Property( - * property="name", - * type="string", - * description="The product name" - * ) - */ - public $name; -``` + + + + ## Response media type The `@OA\MediaType` is used to describe the content: -```php -/** - * @OA\Response( - * response=200, - * description="successful operation", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema(ref="#/components/schemas/User"), - * ) - * ), - */ -``` + + + + But because most API requests and responses are JSON, the `@OA\JsonContent` allows you to simplify this by writing: -```php -/** - * @OA\Response( - * response=200, - * description="successful operation", - * @OA\JsonContent(ref="#/components/schemas/User"), - * ) - */ -``` + + + + During processing the `@OA\JsonContent` unfolds to `@OA\MediaType( mediaType="application/json", @OA\Schema(...)` and will generate the same output. @@ -103,6 +102,21 @@ and will generate the same output. It's quite common that endpoints have some overlap in either their request or response data. To keep things DRY (Don't Repeat Yourself) the specification allows reusing components using `$ref`'s + + + + + **Results in:** ```yaml @@ -128,6 +145,17 @@ components: This doesn't do anything by itself, but now you can reference this fragment by its path in the document tree `#/components/schemas/product_id` + + + + + ::: info Examples There are more uses cases on how to use refs in the [using-refs example](https://github.com/zircote/swagger-php/tree/master/examples/specs/using-refs). ::: @@ -153,24 +184,18 @@ The specification allows for [custom properties](http://swagger.io/specification as long as they start with "x-". Therefore, all swagger-php annotations have an `x` property which accepts an array (map) and will unfold into "x-" properties. -```php -/** - * @OA\Info( - * title="Example", - * version="1.0.0", - * x={ - * "some-name": "a-value", - * "another": 2, - * "complex-type": { - * "supported":{ - * {"version": "1.0", "level": "baseapi"}, - * {"version": "2.1", "level": "fullapi"}, - * } - * } - * } - * ) - */ -``` + + + + **Results in:** @@ -199,23 +224,18 @@ Enum cases can be used as value in an `enum` list just like a `string`, `integer **Basic enum:** -```php -use OpenApi\Attributes as OAT; - -enum Suit -{ - case Hearts; - case Diamonds; - case Clubs; - case Spades; -} - -class Model -{ - #[OAT\Property(enum: [Suit::Hearts, Suit::Diamonds])] - protected array $someSuits; -} -``` + + + + **Results in:** @@ -242,24 +262,18 @@ If the enum is a backed enum, the case backing value is used instead of the name The simples way of using enums is to annotate them as `Schema`. This allows you to reference them like any other schema in your spec. -```php -use OpenApi\Attributes as OAT; - -#[OAT\Schema()] -enum Colour -{ - case GREEN; - case BLUE; - case RED; -} - -#[OAT\Schema()] -class Product -{ - #[OAT\Property()] - public Colour $colour; -} -``` + + + + **Results in:** @@ -288,24 +302,18 @@ For backed enums there exist two rules that determine whether the name or backin **Using the name of a backed enum:** -```php -use OpenApi\Attributes as OAT; - -#[OAT\Schema()] -enum Colour: int -{ - case GREEN = 1; - case BLUE = 2; - case RED = 3; -} - -#[OAT\Schema()] -class Product -{ - #[OAT\Property()] - public Colour $colour; -} -``` + + + + **Results in:** @@ -328,24 +336,18 @@ components: **Using the backing value:** -```php -use OpenApi\Attributes as OAT; - -#[OAT\Schema(type: 'integer')] -enum Colour: int -{ - case GREEN = 1; - case BLUE = 2; - case RED = 3; -} - -#[OAT\Schema()] -class Product -{ - #[OAT\Property()] - public Colour $colour; -} -``` + + + + **Results in:** diff --git a/docs/guide/cookbook.md b/docs/guide/cookbook.md index cd722a95b..b8b474c7a 100644 --- a/docs/guide/cookbook.md +++ b/docs/guide/cookbook.md @@ -5,49 +5,49 @@ OpenApi has the concept of grouping endpoints using tags. On top of that, some t ([redocly](https://redoc.ly/docs/api-reference-docs/specification-extensions/x-tag-groups/), for example) support further grouping via the vendor extension `x-tagGroups`. -```php -/** - * @OA\OpenApi( - * x={ - * "tagGroups"= - * {{"name"="User Management", "tags"={"Users", "API keys", "Admin"}} - * } - * } - * ) - */ -``` + + + + ## Adding examples to `@OA\Response` -```php -/* - * @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."), - * ) - * ) - */ -``` + + + + + ## External documentation OpenApi allows a single reference to external documentation. This is a part of the top level `@OA\OpenApi`. -```php -/** - * @OA\OpenApi( - * @OA\ExternalDocumentation( - * description="More documentation here...", - * url="https://example.com/externaldoc1/" - * ) - * ) - */ -``` + + + + ::: tip If no `@OA\OpenApi` is configured, `swagger-php` will create one automatically. @@ -67,30 +67,19 @@ That means the above example would also work with just the `OA\ExternalDocumenta ## Properties with union types Sometimes properties or even lists (arrays) may contain data of different types. This can be expressed using `oneOf`. -```php -/** - * @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") - * })) - * ) - */ -``` + + + + + This will resolve into this YAML ```yaml @@ -134,145 +123,97 @@ components: ## Referencing a security scheme An API might have zero or more security schemes. These are defined at the top level and vary from simple to complex: -```php -/** - * @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" - * } - * ) - * ) - */ -``` + + + + To declare an endpoint as secure and define what security schemes are available to authenticate a client it needs to be added to the operation, for example: -```php -/** - * @OA\Get( - * path="/api/secure/", - * summary="Requires authentication" - * ), - * security={ {"api_key": {}} } - * ) - */ -``` + + + + + ::: tip Endpoints can support multiple security schemes and have custom options too: -```php -/** - * @OA\Get( - * path="/api/secure/", - * summary="Requires authentication" - * ), - * security={ - * { "api_key": {} }, - * { "petstore_auth": {"write:pets", "read:pets"} } - * } - * ) - */ -``` + + + + ::: ## File upload with headers -```php -/** - * @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" - * ) - * ) - */ -``` + + + + + + ## Set the XML root name The `OA\Xml` annotation may be used to set the XML root element for a given `@OA\XmlContent` response body -```php -/** - * @OA\Schema( - * schema="Error", - * @OA\Property(property="message"), - * @OA\Xml(name="details") - * ) - */ + + + + ## upload multipart/form-data Form posts are `@OA\Post` requests with a `multipart/form-data` `@OA\RequestBody`. The relevant bit looks something like this -```php -/** - * @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") - * ) - */ -``` + + + + + ## Default security scheme for all endpoints Unless specified each endpoint needs to declare what security schemes it supports. However, there is a way @@ -295,92 +236,51 @@ This is done on the `@OA\OpenApi` annotation: ## Nested objects Complex, nested data structures are defined by nesting `@OA\Property` annotations inside others (with `type="object"`). -```php -/** - * @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" - * ) - * ) - * ) - */ -``` + + + + ## Documenting union type response data using `oneOf` A response with either a single or a list of `QualificationHolder`'s. -```php -/** - * @OA\Response( - * response=200, - * @OA\JsonContent( - * oneOf={ - * @OA\Schema(ref="#/components/schemas/QualificationHolder"), - * @OA\Schema( - * type="array", - * @OA\Items(ref="#/components/schemas/QualificationHolder") - * ) - * } - * ) - * ) - */ -``` + + + + + ## Reusing responses Global responses are found under `/components/responses` and can be referenced/shared just like schema definitions (models) -```php -/** - * @OA\Response( - * response="product", - * description="All information about a product", - * @OA\JsonContent(ref="#/components/schemas/Product") - * ) - */ -class ProductResponse {} + + + + ::: tip `response` parameter is always required Even if referencing a shared response definition, the `response` parameter is still required. @@ -412,10 +312,50 @@ There are two scenarios where this can happen The API does include basic support for callbacks. However, this needs to be set up mostly manually. **Example** + + + + +```php +#[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' + ), + ], + ), + ), + ], + ), + ], + ], + ], + ], + // ... +)] +``` + + + + ```php /** + * @OA\Get( * ... - * * callbacks={ * "onChange"={ * "{$request.query.callbackUrl}"={ @@ -435,12 +375,14 @@ The API does include basic support for callbacks. However, this needs to be set * } * } * } - * * ... - * + * ) */ ``` + + + ## (Mostly) virtual models Typically, a model is annotated by adding a `@OA\Schema` annotation to the class and then individual `@OA\Property` annotations to the individually declared class properties. @@ -448,17 +390,19 @@ to the individually declared class properties. It is possible, however, to nest `O@\Property` annotations inside a schema even without properties. In fact, all that is needed is a code anchor - e.g. an empty class. -```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 {} -``` ## Using class name as type instead of references Typically, when referencing schemas this is done using `$ref`'s @@ -490,6 +434,9 @@ class name. With the same `User` schema as before, the `Book::author` property could be written in a few different ways + + + ```php #[OAT\Property()] public User author; @@ -508,10 +455,32 @@ With the same `User` schema as before, the `Book::author` property could be writ **or** ```php - #[OAT\Property(type: User::class)] + #[OA\Property(type: User::class)] + public author; +``` + + + + + +```php + /** @OA\Property() */ + public User author; +``` + +**or** + +```php + /** + * @var User + * @OA\Property() + */ public author; ``` + + + ## Enums As of PHP 8.1 there is native support for `enum`'s. @@ -519,21 +488,18 @@ As of PHP 8.1 there is native support for `enum`'s. **Example** -```php -#[Schema()] -enum State -{ - case OPEN; - case MERGED; - case DECLINED; -} + + + + However, in this case the schema generated for `State` will be an enum: @@ -560,27 +526,19 @@ name uses trailing `[]`. In fact, it is possible to create nested arrays too by In terms of OpenAPI, the parameters can be considered a single parameter with a list of values. -```php -/** - * @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") - * ) - */ -``` + + + + + The corresponding bit of the spec will look like this: @@ -609,6 +567,7 @@ The beauty is that in your custom `__construct()` method you can prefill as much Best of all, this works for both annotations and attributes. Example: + ```php use OpenApi\Attributes as OA; @@ -657,16 +616,20 @@ Furthermore, your custom annotations should extend from the `OpenApi\Annotations ::: ## Annotating class constants -```php -use OpenApi\Attributes as OA; -#[OA\Schema()] -class Airport -{ - #[OA\Property(property='kind')] - public const KIND = 'Airport'; -} -``` + + + + + The `const` property is supported in OpenApi 3.1.0. ```yaml components: diff --git a/docs/guide/faq.md b/docs/guide/faq.md index 87bf9d962..37fcc92dd 100644 --- a/docs/guide/faq.md +++ b/docs/guide/faq.md @@ -24,27 +24,18 @@ related code, the info annotation (and a few more) is kind of global. The simplest solution to avoid this issue is to add a 'dummy' class to the docblock and add all 'global' annotations (e.g. `Tag`, `Server`, `SecurityScheme`, etc.) **in a single docblock** to that class. -```php -/** - * @OA\Tag( - * name="user", - * description="User related operations" - * ) - * @OA\Info( - * version="1.0", - * title="Example API", - * description="Example info", - * @OA\Contact(name="Swagger API Team") - * ) - * @OA\Server( - * url="https://example.localhost", - * description="API server" - * ) - */ -class OpenApiSpec -{ -} -``` + + + + **As of version 4.8 the `doctrine/annotations` library is optional and might cause the same message.** diff --git a/docs/snippets/guide/common-techniques/backed_enum_names_as_schema_an.php b/docs/snippets/guide/common-techniques/backed_enum_names_as_schema_an.php new file mode 100644 index 000000000..899fec098 --- /dev/null +++ b/docs/snippets/guide/common-techniques/backed_enum_names_as_schema_an.php @@ -0,0 +1,24 @@ + 'a-value', + 'another' => 2, + 'complex-type' => [ + 'supported' => [ + ['version' => '1.0', 'level' => 'baseapi'], + ['version' => '2.1', 'level' => 'fullapi'], + ], + ], + ], +)] +class OpenApiSpec +{ +} diff --git a/docs/snippets/guide/common-techniques/enum_as_schema_an.php b/docs/snippets/guide/common-techniques/enum_as_schema_an.php new file mode 100644 index 000000000..e8b6e3eac --- /dev/null +++ b/docs/snippets/guide/common-techniques/enum_as_schema_an.php @@ -0,0 +1,24 @@ + true], summary: 'An result object.'), + new OA\Examples(example: 'bool', value: false, summary: 'A boolean value.'), + ], + ), + )] + public function operation() + { + } +} diff --git a/docs/snippets/guide/cookbook/reusing_response_an.php b/docs/snippets/guide/cookbook/reusing_response_an.php new file mode 100644 index 000000000..aa865272a --- /dev/null +++ b/docs/snippets/guide/cookbook/reusing_response_an.php @@ -0,0 +1,33 @@ + [], + ], + ], + )] + public function getSecurely() + { + // ... + } +} diff --git a/docs/snippets/guide/cookbook/security_schema_tips_an.php b/docs/snippets/guide/cookbook/security_schema_tips_an.php new file mode 100644 index 000000000..5dc90b410 --- /dev/null +++ b/docs/snippets/guide/cookbook/security_schema_tips_an.php @@ -0,0 +1,20 @@ + []], + ['petstore_auth' => ['write:pets', 'read:pets']], + ] + )] + public function secure() + { + } +} diff --git a/docs/snippets/guide/cookbook/security_schemas_an.php b/docs/snippets/guide/cookbook/security_schemas_an.php new file mode 100644 index 000000000..d61d14552 --- /dev/null +++ b/docs/snippets/guide/cookbook/security_schemas_an.php @@ -0,0 +1,28 @@ + 'read your pets', + 'write:pets' => 'modify pets in your account', + ], + ), + ], +)] +class OpenApiSpec +{ +} diff --git a/docs/snippets/guide/cookbook/set_xml_root_name_an.php b/docs/snippets/guide/cookbook/set_xml_root_name_an.php new file mode 100644 index 000000000..b2b126d4d --- /dev/null +++ b/docs/snippets/guide/cookbook/set_xml_root_name_an.php @@ -0,0 +1,24 @@ + new OA\Property(property: 'name', type: 'string'), + 'email' => new OA\Property(property: 'email', type: 'string'), + ] +)] +class User +{ +} diff --git a/docs/snippets/guide/cookbook/x_tag_groups_an.php b/docs/snippets/guide/cookbook/x_tag_groups_an.php new file mode 100644 index 000000000..ce5c01526 --- /dev/null +++ b/docs/snippets/guide/cookbook/x_tag_groups_an.php @@ -0,0 +1,15 @@ + [ + ['name' => 'User Management', 'tags' => ['Users', 'API keys', 'Admin']], + ], + ] +)] +class OpenApiSpec +{ +} diff --git a/docs/snippets/guide/faq/dummy_class_an.php b/docs/snippets/guide/faq/dummy_class_an.php new file mode 100644 index 000000000..fc66eef90 --- /dev/null +++ b/docs/snippets/guide/faq/dummy_class_an.php @@ -0,0 +1,23 @@ +