English | δΈζ
Automatic Swagger/OpenAPI documentation generator for the Hyperf framework, supporting Swoole/Swow engines, providing an elegant and powerful API documentation solution.
- π Auto Generation - Automatically generate OpenAPI 3.0 documentation based on PHP 8 Attributes
- π― Type Safety - Support DTO mode with automatic parameter mapping to PHP classes
- π Multiple UIs - Support Swagger UI, Knife4j, Redoc, RapiDoc, Scalar, and more
- β Data Validation - Integrate Hyperf validator with rich validation annotations
- π Security - Support API Token and multiple security schemes
- π Type Support - Support arrays, recursion, nesting, enums, and other complex types
- π¨ Flexible Config - Customizable global response format, route prefix, etc.
- π¦ Out of Box - Zero configuration ready to use with deep customization support
- PHP >= 8.1
- Hyperf >= 3.0
- Swoole >= 5.0 or Swow
- Union types are not supported for parameter mapping to PHP classes
- Controller methods should return specific types (including simple types) for better documentation generation
- Use
#[ApiResponse]annotation when return types cannot fully express the response structure
composer require tangwei/apidocsBy default, Swagger UI is used. You can optionally install Knife4j UI (recommended):
composer require tangwei/knife4j-uiphp bin/hyperf.php vendor:publish tangwei/apidocsConfiguration file will be published to config/autoload/api_docs.php
Complete Configuration Reference (Click to expand)
Full configuration example: config/autoload/api_docs.php
<?php
use Hyperf\ApiDocs\DTO\GlobalResponse;
use function Hyperf\Support\env;
return [
/*
|--------------------------------------------------------------------------
| Enable Swagger Service
|--------------------------------------------------------------------------
|
| Set to false to disable swagger service
|
*/
'enable' => env('APP_ENV') !== 'prod',
/*
|--------------------------------------------------------------------------
| Swagger File Format
|--------------------------------------------------------------------------
|
| Supports json and yaml
|
*/
'format' => 'json',
/*
|--------------------------------------------------------------------------
| Swagger File Output Path
|--------------------------------------------------------------------------
*/
'output_dir' => BASE_PATH . '/runtime/container',
/*
|--------------------------------------------------------------------------
| Proxy Class Path
|--------------------------------------------------------------------------
*/
'proxy_dir' => BASE_PATH . '/runtime/container/proxy',
/*
|--------------------------------------------------------------------------
| Route Prefix
|--------------------------------------------------------------------------
*/
'prefix_url' => env('API_DOCS_PREFIX_URL', '/swagger'),
/*
|--------------------------------------------------------------------------
| Swagger Resources CDN Path
|--------------------------------------------------------------------------
*/
'prefix_swagger_resources' => 'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.27.1',
/*
|--------------------------------------------------------------------------
| Global Response Class
|--------------------------------------------------------------------------
|
| Global response format like: [code=>200, data=>null]
| Use with ApiVariable annotation, see GlobalResponse class example
| Response format can be unified using AOP
|
*/
// 'global_return_responses_class' => GlobalResponse::class,
/*
|--------------------------------------------------------------------------
| Replace Validation Attributes
|--------------------------------------------------------------------------
|
| Use ApiModelProperty annotation values for validation error messages
|
*/
'validation_custom_attributes' => true,
/*
|--------------------------------------------------------------------------
| DTO Default Value Level
|--------------------------------------------------------------------------
|
| 0: Default (no default values)
| 1: Simple types get default values, complex types with ? get null
| - Simple type defaults: int:0 float:0 string:'' bool:false array:[] mixed:null
| 2: (Use with caution) Includes level 1 and complex types (except union) get null
|
*/
'dto_default_value_level' => 0,
/*
|--------------------------------------------------------------------------
| Global Responses
|--------------------------------------------------------------------------
*/
'responses' => [
['response' => 401, 'description' => 'Unauthorized'],
['response' => 500, 'description' => 'System error'],
],
/*
|--------------------------------------------------------------------------
| Swagger Basic Configuration
|--------------------------------------------------------------------------
|
| This maps to OpenAPI object
|
*/
'swagger' => [
'info' => [
'title' => 'API Documentation',
'version' => '1.0.0',
'description' => 'API Documentation',
],
'servers' => [
[
'url' => 'http://127.0.0.1:9501',
'description' => 'API Server',
],
],
'components' => [
'securitySchemes' => [
[
'securityScheme' => 'Authorization',
'type' => 'apiKey',
'in' => 'header',
'name' => 'Authorization',
],
],
],
'security' => [
['Authorization' => []],
],
'externalDocs' => [
'description' => 'GitHub',
'url' => 'https://github.com/tw2066/api-docs',
],
],
];<?php
// config/autoload/api_docs.php
return [
// Enable documentation service (recommended to disable in production)
'enable' => env('APP_ENV') !== 'prod',
// Documentation access path
'prefix_url' => env('API_DOCS_PREFIX_URL', '/swagger'),
// Basic information
'swagger' => [
'info' => [
'title' => 'API Documentation',
'version' => '1.0.0',
'description' => 'Project API Documentation',
],
'servers' => [
[
'url' => 'http://127.0.0.1:9501',
'description' => 'API Server',
],
],
],
];php bin/hyperf.php startAfter successful startup, visit http://your-host:9501/swagger to view the API documentation.
[INFO] Swagger docs url at http://0.0.0.0:9501/swagger
[INFO] Worker#0 started.
[INFO] HTTP Server listening at 0.0.0.0:9501
<?php
namespace App\Request;
use Hyperf\ApiDocs\Annotation\ApiModelProperty;
use Hyperf\DTO\Annotation\Validation\Required;
use Hyperf\DTO\Annotation\Validation\Integer;
use Hyperf\DTO\Annotation\Validation\Between;
class UserRequest
{
#[ApiModelProperty('Username')]
#[Required]
public string $username;
#[ApiModelProperty('Age')]
#[Required]
#[Integer]
#[Between(1, 120)]
public int $age;
#[ApiModelProperty('Email')]
public ?string $email = null;
}<?php
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\ApiDocs\Annotation\Api;
use Hyperf\ApiDocs\Annotation\ApiOperation;
use Hyperf\DTO\Annotation\Contracts\RequestBody;
use Hyperf\DTO\Annotation\Contracts\RequestQuery;
use Hyperf\DTO\Annotation\Contracts\Valid;
use App\Request\UserRequest;
#[Controller(prefix: '/user')]
#[Api(tags: 'User Management', position: 1)]
class UserController
{
#[GetMapping(path: 'list')]
#[ApiOperation(summary: 'Get user list')]
public function list(#[RequestQuery] #[Valid] UserRequest $request): array
{
return [
['id' => 1, 'username' => 'admin'],
['id' => 2, 'username' => 'user'],
];
}
#[PostMapping(path: 'create')]
#[ApiOperation(summary: 'Create user')]
public function create(#[RequestBody] #[Valid] UserRequest $request): array
{
return [
'id' => 1,
'username' => $request->username,
'age' => $request->age,
];
}
}#[Api(
tags: 'User Management', // Tag name (supports array)
description: 'User operations', // Description
position: 1, // Sort position
hidden: false // Whether to hide
)]#[ApiOperation(
summary: 'Create user', // Summary
description: 'Detailed description', // Detailed description
deprecated: false, // Whether deprecated
security: true, // Whether authentication required
hidden: false // Whether to hide
)]// Simple type response
#[ApiResponse(PhpType::INT, 200, 'Success')]
// Object response
#[ApiResponse(UserResponse::class, 200, 'User information')]
// Array response
#[ApiResponse([UserResponse::class], 200, 'User list')]
// Paginated response
#[ApiResponse(new Page([UserResponse::class]), 200, 'Paginated data')]Get JSON body parameters from POST/PUT/PATCH requests:
public function create(#[RequestBody] #[Valid] UserRequest $request)
{
// $request automatically populated with body data
}Get URL query parameters (GET parameters):
public function list(#[RequestQuery] #[Valid] QueryRequest $request)
{
// $request automatically populated with query parameters
}Get form data (multipart/form-data):
#[ApiFormData(name: 'photo', format: 'binary')]
public function upload(#[RequestFormData] UploadRequest $formData)
{
$file = $this->request->file('photo');
// Handle file upload
}Get request header information:
public function auth(#[RequestHeader] #[Valid] AuthHeader $header)
{
// $header automatically populated with header data
}Generic Type Support Example:
PHP doesn't natively support generics, but you can achieve similar functionality using #[ApiVariable]:
use Hyperf\ApiDocs\Annotation\ApiVariable;
class Page
{
public int $total;
#[ApiVariable]
public array $content;
public function __construct(array $content, int $total = 0)
{
$this->content = $content;
$this->total = $total;
}
}Controller usage:
#[ApiOperation('Paginated query')]
#[GetMapping(path: 'page')]
#[ApiResponse(new Page([UserResponse::class]))]
public function page(#[RequestQuery] PageQuery $query): Page
{
// Return paginated data
}#[ApiModelProperty(
value: 'Username', // Property description
example: 'admin', // Example value
required: true, // Whether required
hidden: false // Whether to hide
)]
public string $username;// Global header (class level)
#[ApiHeader('X-Request-Id')]
// Method level header
#[ApiHeader(
name: 'Authorization',
required: true,
type: 'string',
description: 'Bearer token'
)]Priority: Method > Class > Global
// Use default authentication
#[ApiSecurity('Authorization')]
// Method level override
#[ApiOperation(summary: 'Login', security: false)] // No authentication required
β οΈ Note: A method cannot inject bothRequestBodyandRequestFormDatasimultaneously
The component provides rich validation annotations:
use Hyperf\DTO\Annotation\Validation\*;
class UserRequest
{
#[Required] // Required
#[Max(50)] // Max length
public string $username;
#[Required]
#[Integer] // Integer
#[Between(1, 120)] // Range
public int $age;
#[Email] // Email format
public ?string $email;
#[Url] // URL format
public ?string $website;
#[Regex('/^1[3-9]\d{9}$/')] // Regex validation
public ?string $mobile;
#[In(['male', 'female'])] // Enum values
public ?string $gender;
#[Date] // Date format
public ?string $birthday;
}π‘ Tip: Simply add the
#[Valid]annotation to controller method parameters to enable validation
public function create(#[RequestBody] #[Valid] UserRequest $request)
{
// Validation is automatically executed
}// Support Laravel-style validation rules
#[Validation('required|string|min:3|max:50')]
public string $username;
// Array element validation
#[Validation('integer', customKey: 'ids.*')]
public array $ids;<?php
namespace App\Validation;
use Attribute;
use Hyperf\DTO\Annotation\Validation\BaseValidation;
#[Attribute(Attribute::TARGET_PROPERTY)]
class Mobile extends BaseValidation
{
protected $rule = 'regex:/^1[3-9]\d{9}$/';
public function __construct(string $messages = 'Invalid mobile format')
{
parent::__construct($messages);
}
}Using custom validation:
use App\Validation\Mobile;
class RegisterRequest
{
#[Required]
#[Mobile]
public string $phone;
}/**
* @var Address[]
*/
#[ApiModelProperty('Address list')]
public array $addresses;
/**
* @var int[]
*/
#[ApiModelProperty('ID list')]
public array $ids;use Hyperf\DTO\Annotation\ArrayType;
#[ApiModelProperty('Address list')]
#[ArrayType(Address::class)]
public array $addresses;
#[ApiModelProperty('Tag list')]
#[ArrayType('string')]
public array $tags;class UserRequest
{
public string $name;
// Nested object
#[ApiModelProperty('Address info')]
public Address $address;
/**
* @var Address[]
*/
#[ApiModelProperty('Multiple addresses')]
public array $addresses;
}
class Address
{
public string $province;
public string $city;
public string $street;
}use Hyperf\DTO\Type\PhpType;
enum StatusEnum: int
{
case PENDING = 0;
case ACTIVE = 1;
case INACTIVE = 2;
}
class OrderRequest
{
#[ApiModelProperty('Order status')]
public StatusEnum $status;
}Configure global response wrapper class:
// config/autoload/api_docs.php
return [
'global_return_responses_class' => \App\DTO\GlobalResponse::class,
];Define global response class:
<?php
namespace App\DTO;
use Hyperf\ApiDocs\Annotation\ApiModelProperty;
use Hyperf\ApiDocs\Annotation\ApiVariable;
class GlobalResponse
{
#[ApiModelProperty('Status code')]
public int $code = 0;
#[ApiModelProperty('Message')]
public string $message = 'success';
#[ApiVariable]
#[ApiModelProperty('Response data')]
public mixed $data = null;
}#[PostMapping(path: 'upload')]
#[ApiOperation(summary: 'File upload')]
#[ApiFormData(name: 'file', format: 'binary', required: true)]
#[ApiFormData(name: 'description', type: 'string')]
public function upload(#[RequestFormData] UploadRequest $request)
{
$file = $this->request->file('file');
// Handle file upload
return ['url' => '/uploads/file.jpg'];
}/**
* @var Address[]
*/
#[ApiModelProperty('Address list')]
public array $addresses;
/**
* @var int[]
*/
#[ApiModelProperty('ID list')]
public array $ids;use Hyperf\DTO\Annotation\ArrayType;
#[ApiModelProperty('Address list')]
#[ArrayType(Address::class)]
public array $addresses;
#[ApiModelProperty('Tag list')]
#[ArrayType('string')]
public array $tags;class UserRequest
{
public string $name;
// Nested object
#[ApiModelProperty('Address info')]
public Address $address;
/**
* @var Address[]
*/
#[ApiModelProperty('Multiple addresses')]
public array $addresses;
}
class Address
{
public string $province;
public string $city;
public string $street;
}use Hyperf\DTO\Type\PhpType;
enum StatusEnum: int
{
case PENDING = 0;
case ACTIVE = 1;
case INACTIVE = 2;
}
class OrderRequest
{
#[ApiModelProperty('Order status')]
public StatusEnum $status;
}Configure global response wrapper class:
// config/autoload/api_docs.php
return [
'global_return_responses_class' => \App\DTO\GlobalResponse::class,
];Define global response class:
<?php
namespace App\DTO;
use Hyperf\ApiDocs\Annotation\ApiModelProperty;
use Hyperf\ApiDocs\Annotation\ApiVariable;
class GlobalResponse
{
#[ApiModelProperty('Status code')]
public int $code = 0;
#[ApiModelProperty('Message')]
public string $message = 'success';
#[ApiVariable]
#[ApiModelProperty('Response data')]
public mixed $data = null;
}#[PostMapping(path: 'upload')]
#[ApiOperation(summary: 'File upload')]
#[ApiFormData(name: 'file', format: 'binary', required: true)]
#[ApiFormData(name: 'description', type: 'string')]
public function upload(#[RequestFormData] UploadRequest $request)
{
$file = $this->request->file('file');
// Handle file upload
return ['url' => '/uploads/file.jpg'];
}Access different UI interfaces:
- Swagger UI:
http://your-host:9501/swagger - Knife4j:
http://your-host:9501/swagger/knife4j - Redoc:
http://your-host:9501/swagger/redoc - RapiDoc:
http://your-host:9501/swagger/rapidoc - Scalar:
http://your-host:9501/swagger/scalar
api-docs depends on the DTO component. For more details, see DTO Documentation
Mark as DTO class:
use Hyperf\DTO\Annotation\Dto;
#[Dto]
class DemoQuery
{
}- Can set return format
#[Dto(Convert::SNAKE)]to batch convert keys to snake_case Dtoannotation doesn't generate documentation, useJSONFieldannotation to generate docs
Used to set property aliases:
use Hyperf\DTO\Annotation\Dto;
use Hyperf\DTO\Annotation\JSONField;
#[Dto]
class DemoQuery
{
#[ApiModelProperty('This is an alias')]
#[JSONField('alias_name')]
#[Required]
public string $name;
}- Setting
JSONFieldgenerates proxy class withalias_nameproperty - Both request and response use
alias_nameas the field name
Configure in aspects.php:
return [
\Hyperf\DTO\Aspect\ObjectNormalizerAspect::class
]After importing symfony/serializer (^5.0) and symfony/property-access (^5.0), configure mapping in dependencies.php:
use Hyperf\Serializer\SerializerFactory;
use Hyperf\Serializer\Serializer;
return [
Hyperf\Contract\NormalizerInterface::class => new SerializerFactory(Serializer::class),
];- Use meaningful class names like
CreateUserRequest,UserResponse - Add
ApiModelPropertyannotation for each property - Separate Request and Response definitions
- Use validation annotations appropriately
- Use
Apiannotation to group controllers - Add
ApiOperationdescription for each method - Return specific types instead of
arraywhen possible - Use
ApiResponseto define response formats properly
- Disable documentation service in production
- Use
ApiSecurityto control API authentication - Use
hidden: trueto hide sensitive endpoints
- Use documentation in development, disable in production
- Use caching appropriately
- Avoid deeply nested structures
A: Check the following:
- Is
enableset totruein config file - Is
#[Api]annotation added to controller - Is route annotation added to method (e.g.,
#[GetMapping]) - Check logs for errors
A: Use PHPDoc comments or ArrayType annotation:
/**
* @var User[]
*/
public array $users;
// Or
#[ArrayType(User::class)]
public array $users;A: Use hidden parameter:
#[Api(hidden: true)] // Hide entire controller
#[ApiOperation(summary: 'Test', hidden: true)] // Hide single endpointA: Use ApiResponse annotation or configure global response class:
#[ApiResponse(UserResponse::class, 200, 'Success')]
public function getUser(): UserResponse
{
return new UserResponse();
}A: All Hyperf Validation rules are supported. See Hyperf Validation Documentation.
A: Yes, but it only collects POST methods. It's recommended to use standard route annotations for better documentation generation.
For complete examples, see the example directory
See CHANGELOG for detailed version updates.
Issues and Pull Requests are welcome!
- Fork this repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Hyperf - Excellent coroutine PHP framework
- Swagger PHP - PHP Swagger generator
- Knife4j - Excellent API documentation tool
If this project helps you, please give it a β Star!