474 lines
9 KiB
Markdown
474 lines
9 KiB
Markdown
# API Documentation
|
|
|
|
Automatically generate OpenAPI 3.0 documentation with Swagger UI, Scalar, and ReDoc viewers.
|
|
|
|
## Overview
|
|
|
|
The API package automatically generates OpenAPI documentation from your routes, controllers, and doc blocks.
|
|
|
|
**Features:**
|
|
- Automatic route discovery
|
|
- OpenAPI 3.0 spec generation
|
|
- Multiple documentation viewers
|
|
- Security scheme documentation
|
|
- Request/response examples
|
|
- Interactive API explorer
|
|
|
|
## Accessing Documentation
|
|
|
|
### Available Endpoints
|
|
|
|
```
|
|
/api/docs - Swagger UI (default)
|
|
/api/docs/scalar - Scalar viewer
|
|
/api/docs/redoc - ReDoc viewer
|
|
/api/docs/openapi - Raw OpenAPI JSON
|
|
```
|
|
|
|
### Protection
|
|
|
|
Documentation is protected in production:
|
|
|
|
```php
|
|
// config/api.php
|
|
return [
|
|
'documentation' => [
|
|
'enabled' => env('API_DOCS_ENABLED', !app()->isProduction()),
|
|
'middleware' => ['auth', 'can:view-api-docs'],
|
|
],
|
|
];
|
|
```
|
|
|
|
## Attributes
|
|
|
|
### Hiding Endpoints
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Attributes\ApiHidden;
|
|
|
|
#[ApiHidden]
|
|
class InternalController
|
|
{
|
|
// Entire controller hidden from docs
|
|
}
|
|
|
|
class PostController
|
|
{
|
|
#[ApiHidden]
|
|
public function internalMethod()
|
|
{
|
|
// Single method hidden
|
|
}
|
|
}
|
|
```
|
|
|
|
### Tagging Endpoints
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Attributes\ApiTag;
|
|
|
|
#[ApiTag('Blog Posts')]
|
|
class PostController
|
|
{
|
|
// All methods tagged with "Blog Posts"
|
|
}
|
|
```
|
|
|
|
### Documenting Parameters
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Attributes\ApiParameter;
|
|
|
|
class PostController
|
|
{
|
|
#[ApiParameter(
|
|
name: 'status',
|
|
in: 'query',
|
|
description: 'Filter by post status',
|
|
required: false,
|
|
schema: ['type' => 'string', 'enum' => ['draft', 'published', 'archived']]
|
|
)]
|
|
#[ApiParameter(
|
|
name: 'category',
|
|
in: 'query',
|
|
description: 'Filter by category ID',
|
|
schema: ['type' => 'integer']
|
|
)]
|
|
public function index(Request $request)
|
|
{
|
|
// GET /posts?status=published&category=5
|
|
}
|
|
}
|
|
```
|
|
|
|
### Documenting Responses
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Attributes\ApiResponse;
|
|
|
|
class PostController
|
|
{
|
|
#[ApiResponse(
|
|
status: 200,
|
|
description: 'Post created successfully',
|
|
content: [
|
|
'application/json' => [
|
|
'schema' => [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'id' => ['type' => 'integer'],
|
|
'title' => ['type' => 'string'],
|
|
'status' => ['type' => 'string'],
|
|
],
|
|
],
|
|
],
|
|
]
|
|
)]
|
|
#[ApiResponse(
|
|
status: 422,
|
|
description: 'Validation error'
|
|
)]
|
|
public function store(Request $request)
|
|
{
|
|
// ...
|
|
}
|
|
}
|
|
```
|
|
|
|
### Security Requirements
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Attributes\ApiSecurity;
|
|
|
|
#[ApiSecurity(['apiKey' => []])]
|
|
class PostController
|
|
{
|
|
// Requires API key authentication
|
|
}
|
|
|
|
#[ApiSecurity(['bearerAuth' => ['posts:write']])]
|
|
public function store(Request $request)
|
|
{
|
|
// Requires Bearer token with posts:write scope
|
|
}
|
|
```
|
|
|
|
## Configuration
|
|
|
|
```php
|
|
// config/api.php
|
|
return [
|
|
'documentation' => [
|
|
'enabled' => true,
|
|
|
|
'info' => [
|
|
'title' => 'Core PHP Framework API',
|
|
'description' => 'REST API for Core PHP Framework',
|
|
'version' => '1.0.0',
|
|
'contact' => [
|
|
'name' => 'API Support',
|
|
'email' => 'api@example.com',
|
|
'url' => 'https://example.com/support',
|
|
],
|
|
],
|
|
|
|
'servers' => [
|
|
[
|
|
'url' => 'https://api.example.com',
|
|
'description' => 'Production',
|
|
],
|
|
[
|
|
'url' => 'https://staging.example.com',
|
|
'description' => 'Staging',
|
|
],
|
|
],
|
|
|
|
'security_schemes' => [
|
|
'apiKey' => [
|
|
'type' => 'http',
|
|
'scheme' => 'bearer',
|
|
'bearerFormat' => 'API Key',
|
|
'description' => 'API key authentication. Format: `Bearer sk_live_...`',
|
|
],
|
|
],
|
|
|
|
'viewers' => [
|
|
'swagger' => true,
|
|
'scalar' => true,
|
|
'redoc' => true,
|
|
],
|
|
],
|
|
];
|
|
```
|
|
|
|
## Extensions
|
|
|
|
### Custom Extensions
|
|
|
|
```php
|
|
<?php
|
|
|
|
namespace Mod\Blog\Api\Documentation;
|
|
|
|
use Mod\Api\Documentation\Extension;
|
|
|
|
class BlogExtension extends Extension
|
|
{
|
|
public function apply(array $spec): array
|
|
{
|
|
// Add custom tags
|
|
$spec['tags'][] = [
|
|
'name' => 'Blog Posts',
|
|
'description' => 'Operations for managing blog posts',
|
|
];
|
|
|
|
// Add custom security requirements
|
|
$spec['paths']['/posts']['post']['security'][] = [
|
|
'apiKey' => [],
|
|
];
|
|
|
|
return $spec;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Register Extension:**
|
|
|
|
```php
|
|
use Core\Events\ApiRoutesRegistering;
|
|
|
|
public function onApiRoutes(ApiRoutesRegistering $event): void
|
|
{
|
|
$event->documentationExtension(new BlogExtension());
|
|
}
|
|
```
|
|
|
|
### Built-in Extensions
|
|
|
|
**Rate Limit Extension:**
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Extensions\RateLimitExtension;
|
|
|
|
// Automatically documents rate limits in responses
|
|
// Adds X-RateLimit-* headers to all endpoints
|
|
```
|
|
|
|
**Workspace Header Extension:**
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Extensions\WorkspaceHeaderExtension;
|
|
|
|
// Documents X-Workspace-ID header requirement
|
|
// Adds to all workspace-scoped endpoints
|
|
```
|
|
|
|
## Common Examples
|
|
|
|
### Pagination
|
|
|
|
```php
|
|
use Mod\Api\Documentation\Examples\CommonExamples;
|
|
|
|
#[ApiResponse(
|
|
status: 200,
|
|
description: 'Paginated list of posts',
|
|
content: CommonExamples::paginatedResponse('posts', [
|
|
'id' => 1,
|
|
'title' => 'Example Post',
|
|
'status' => 'published',
|
|
])
|
|
)]
|
|
public function index(Request $request)
|
|
{
|
|
return PostResource::collection(
|
|
Post::paginate(20)
|
|
);
|
|
}
|
|
```
|
|
|
|
**Generates:**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": 1,
|
|
"title": "Example Post",
|
|
"status": "published"
|
|
}
|
|
],
|
|
"links": {
|
|
"first": "...",
|
|
"last": "...",
|
|
"prev": null,
|
|
"next": "..."
|
|
},
|
|
"meta": {
|
|
"current_page": 1,
|
|
"total": 100
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Responses
|
|
|
|
```php
|
|
#[ApiResponse(
|
|
status: 404,
|
|
description: 'Post not found',
|
|
content: CommonExamples::errorResponse('Post not found', 'resource_not_found')
|
|
)]
|
|
public function show(Post $post)
|
|
{
|
|
return new PostResource($post);
|
|
}
|
|
```
|
|
|
|
## Module Discovery
|
|
|
|
The documentation system automatically discovers API routes from all modules:
|
|
|
|
```php
|
|
// Mod\Blog\Boot
|
|
public function onApiRoutes(ApiRoutesRegistering $event): void
|
|
{
|
|
$event->routes(function () {
|
|
Route::get('/posts', [PostController::class, 'index']);
|
|
// Automatically included in docs
|
|
});
|
|
}
|
|
```
|
|
|
|
**Discovery Process:**
|
|
1. Scan all registered API routes
|
|
2. Extract controller methods
|
|
3. Parse doc blocks and attributes
|
|
4. Generate OpenAPI spec
|
|
5. Cache for performance
|
|
|
|
## Viewers
|
|
|
|
### Swagger UI
|
|
|
|
Interactive API explorer with "Try it out" functionality.
|
|
|
|
**Access:** `/api/docs`
|
|
|
|
**Features:**
|
|
- Test endpoints directly
|
|
- View request/response examples
|
|
- OAuth/API key authentication
|
|
- Model schemas
|
|
|
|
### Scalar
|
|
|
|
Modern, clean documentation viewer.
|
|
|
|
**Access:** `/api/docs/scalar`
|
|
|
|
**Features:**
|
|
- Beautiful UI
|
|
- Dark mode
|
|
- Code examples in multiple languages
|
|
- Interactive examples
|
|
|
|
### ReDoc
|
|
|
|
Professional documentation with three-panel layout.
|
|
|
|
**Access:** `/api/docs/redoc`
|
|
|
|
**Features:**
|
|
- Search functionality
|
|
- Menu navigation
|
|
- Responsive design
|
|
- Printable
|
|
|
|
## Best Practices
|
|
|
|
### 1. Document All Public Endpoints
|
|
|
|
```php
|
|
// ✅ Good - documented
|
|
#[ApiTag('Posts')]
|
|
#[ApiResponse(200, 'Success')]
|
|
#[ApiResponse(422, 'Validation error')]
|
|
public function store(Request $request)
|
|
|
|
// ❌ Bad - undocumented
|
|
public function store(Request $request)
|
|
```
|
|
|
|
### 2. Provide Examples
|
|
|
|
```php
|
|
// ✅ Good - request example
|
|
#[ApiParameter(
|
|
name: 'status',
|
|
example: 'published'
|
|
)]
|
|
|
|
// ❌ Bad - no example
|
|
#[ApiParameter(name: 'status')]
|
|
```
|
|
|
|
### 3. Hide Internal Endpoints
|
|
|
|
```php
|
|
// ✅ Good - hidden
|
|
#[ApiHidden]
|
|
public function internal()
|
|
|
|
// ❌ Bad - exposed in docs
|
|
public function internal()
|
|
```
|
|
|
|
### 4. Group Related Endpoints
|
|
|
|
```php
|
|
// ✅ Good - tagged
|
|
#[ApiTag('Blog Posts')]
|
|
class PostController
|
|
|
|
// ❌ Bad - ungrouped
|
|
class PostController
|
|
```
|
|
|
|
## Testing
|
|
|
|
```php
|
|
use Tests\TestCase;
|
|
|
|
class DocumentationTest extends TestCase
|
|
{
|
|
public function test_generates_openapi_spec(): void
|
|
{
|
|
$response = $this->getJson('/api/docs/openapi');
|
|
|
|
$response->assertStatus(200);
|
|
$response->assertJsonStructure([
|
|
'openapi',
|
|
'info',
|
|
'paths',
|
|
'components',
|
|
]);
|
|
}
|
|
|
|
public function test_includes_blog_endpoints(): void
|
|
{
|
|
$response = $this->getJson('/api/docs/openapi');
|
|
|
|
$spec = $response->json();
|
|
|
|
$this->assertArrayHasKey('/posts', $spec['paths']);
|
|
$this->assertArrayHasKey('/posts/{id}', $spec['paths']);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Learn More
|
|
|
|
- [Authentication →](/packages/api/authentication)
|
|
- [Scopes →](/packages/api/scopes)
|
|
- [API Reference →](/api/endpoints)
|