Spiral Framework provides seamless integration for generating OpenAPI (Swagger) documentation from your PHP code using attributes. The documentation is automatically generated from your controllers, filters, and response classes, staying in sync with your actual implementation.
Install the package via Composer:
composer require spiral-packages/swagger-php
Register the bootloader:
public function defineBootloaders(): array
{
return [
// ...
\Spiral\OpenApi\Bootloader\SwaggerBootloader::class,
// ...
];
}
See also Read more about bootloaders in the Framework — Bootloaders section.
Create configuration file app/config/swagger.php:
<?php
declare(strict_types=1);
use Spiral\OpenApi\Generator\Parser\ConfigurationParser;
use Spiral\OpenApi\Generator\Parser\OpenApiParser;
use Spiral\OpenApi\Renderer\HtmlRenderer;
use Spiral\OpenApi\Renderer\JsonRenderer;
use Spiral\OpenApi\Renderer\YamlRenderer;
return [
'documentation' => [
'info' => [
'title' => 'My API',
'description' => 'API documentation',
'version' => '1.0.0',
],
'servers' => [
['url' => 'https://api.example.com', 'description' => 'Production'],
['url' => 'http://localhost:8080', 'description' => 'Development'],
],
],
'parsers' => [
ConfigurationParser::class,
OpenApiParser::class,
],
'renderers' => [
JsonRenderer::FORMAT => JsonRenderer::class,
YamlRenderer::FORMAT => YamlRenderer::class,
HtmlRenderer::FORMAT => HtmlRenderer::class,
],
'paths' => [
directory('app') . '/src',
],
'exclude' => null,
'pattern' => '*.php',
'version' => null,
'cache_key' => 'swagger_docs',
'use_cache' => env('DEBUG', false) === false,
'generator_config' => [
'operationId' => [
'hash' => true,
],
],
];
| Option | Type | Description |
|---|---|---|
documentation |
array |
OpenAPI specification base configuration (info, servers, security schemes) |
parsers |
array |
List of parser classes to extract OpenAPI annotations |
renderers |
array |
Output format renderers (JSON, YAML, HTML) |
paths |
array |
Directories to scan for OpenAPI attributes |
exclude |
string|array|null |
Paths or patterns to exclude from scanning |
pattern |
string|null |
File pattern to match during scanning |
version |
string|null |
OpenAPI specification version |
cache_key |
string |
Cache key for storing generated documentation |
use_cache |
bool |
Enable caching (recommended for production) |
generator_config |
array |
Configuration for OpenAPI generator behavior |
You can add additional scan paths programmatically:
<?php
declare(strict_types=1);
namespace App\Application\Bootloader;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\OpenApi\Bootloader\SwaggerBootloader;
final class OpenApiBootloader extends Bootloader
{
public function boot(SwaggerBootloader $swagger): void
{
$swagger->addPath(directory('app') . '/src/Endpoint');
}
}
Configure routes to expose documentation endpoints:
<?php
declare(strict_types=1);
namespace App\Application\Bootloader;
use Spiral\Bootloader\Http\RoutesBootloader as BaseRoutesBootloader;
use Spiral\OpenApi\Controller\DocumentationController;
use Spiral\Router\Loader\Configurator\RoutingConfigurator;
final class RoutesBootloader extends BaseRoutesBootloader
{
protected function defineRoutes(RoutingConfigurator $routes): void
{
$routes
->add('swagger-ui', '/api/docs')
->action(DocumentationController::class, 'html');
$routes
->add('swagger-json', '/api/docs.json')
->action(DocumentationController::class, 'json');
$routes
->add('swagger-yaml', '/api/docs.yaml')
->action(DocumentationController::class, 'yaml');
}
}
Disable documentation in production:
use Spiral\Boot\EnvironmentInterface;
final class RoutesBootloader extends BaseRoutesBootloader
{
public function __construct(
private readonly EnvironmentInterface $env
) {}
protected function defineRoutes(RoutingConfigurator $routes): void
{
if ($this->env->get('APP_ENV') !== 'production') {
$routes
->add('swagger-ui', '/api/docs')
->action(DocumentationController::class, 'html');
}
}
}
OpenAPI documentation is generated from PHP attributes placed on your controllers and filters. The package uses the swagger-php library attributes for defining the API specification.
Note
Refer to the swagger-php documentation for detailed information about available attributes and their properties.
Add OpenAPI attributes to your controller methods:
<?php
declare(strict_types=1);
namespace App\Endpoint\Web;
use OpenApi\Attributes as OA;
use Spiral\Router\Annotation\Route;
#[OA\Tag(name: 'Users', description: 'User management')]
class UserController
{
#[Route(route: '/api/users', name: 'users.list', methods: 'GET')]
#[OA\Get(
path: '/api/users',
operationId: 'users.list',
summary: 'List users',
tags: ['Users'],
parameters: [
new OA\QueryParameter(
name: 'page',
description: 'Page number',
schema: new OA\Schema(type: 'integer', default: 1)
),
],
responses: [
new OA\Response(
response: 200,
description: 'Success',
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'data',
type: 'array',
items: new OA\Items(ref: '#/components/schemas/User')
),
]
)
),
]
)]
public function list(): array
{
// Implementation
}
#[Route(route: '/api/users', name: 'users.create', methods: 'POST')]
#[OA\Post(
path: '/api/users',
operationId: 'users.create',
summary: 'Create user',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(ref: CreateUserFilter::class)
),
tags: ['Users'],
responses: [
new OA\Response(
response: 201,
description: 'Created',
content: new OA\JsonContent(ref: '#/components/schemas/User')
),
]
)]
public function create(CreateUserFilter $filter): array
{
// Implementation
}
}
See also Learn more about routing in the HTTP — Routing section.
Filters can serve as both request validators and OpenAPI schema definitions. Add OpenAPI attributes to your filter classes:
<?php
declare(strict_types=1);
namespace App\Endpoint\Web\Filter;
use OpenApi\Attributes as OA;
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
use Spiral\Validator\FilterDefinition;
#[OA\Schema(
schema: 'CreateUserRequest',
required: ['email', 'password', 'name'],
properties: [
new OA\Property(
property: 'email',
type: 'string',
format: 'email',
example: 'user@example.com'
),
new OA\Property(
property: 'password',
type: 'string',
format: 'password',
minLength: 8
),
new OA\Property(
property: 'name',
type: 'string',
example: 'John Doe'
),
]
)]
final class CreateUserFilter extends Filter implements HasFilterDefinition
{
#[Post]
public string $email;
#[Post]
public string $password;
#[Post]
public string $name;
public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'email' => ['required', 'email'],
'password' => ['required', 'string::length', 8],
'name' => ['required', 'string'],
]);
}
}
See also Learn more about filters in the Filters — Filter Object section.
Resource classes can be documented to define response schemas:
<?php
declare(strict_types=1);
namespace App\Endpoint\Web\Resource;
use App\Application\HTTP\Response\JsonResource;
use OpenApi\Attributes as OA;
#[OA\Schema(
schema: 'User',
required: ['id', 'email', 'name', 'created_at'],
properties: [
new OA\Property(
property: 'id',
type: 'integer',
example: 1
),
new OA\Property(
property: 'email',
type: 'string',
format: 'email',
example: 'user@example.com'
),
new OA\Property(
property: 'name',
type: 'string',
example: 'John Doe'
),
new OA\Property(
property: 'created_at',
type: 'string',
format: 'date-time',
example: '2024-01-15T10:30:00Z'
),
]
)]
final class UserResource extends JsonResource
{
protected function mapData(): array
{
return [
'id' => $this->data->id,
'email' => $this->data->email,
'name' => $this->data->name,
'created_at' => $this->data->createdAt->format('c'),
];
}
}
For better organization, you can create dedicated schema classes:
<?php
declare(strict_types=1);
namespace App\OpenApi\Schema;
use OpenApi\Attributes as OA;
#[OA\Schema(
schema: 'Error',
required: ['message'],
properties: [
new OA\Property(
property: 'message',
type: 'string',
example: 'An error occurred'
),
new OA\Property(
property: 'code',
type: 'integer',
example: 400
),
]
)]
final class ErrorSchema
{
}
Use schema references in your controllers:
#[OA\Response(
response: 400,
description: 'Bad Request',
content: new OA\JsonContent(ref: '#/components/schemas/Error')
)]
Create an enum for type-safe schema references:
<?php
declare(strict_types=1);
namespace App\OpenApi;
enum SchemaRef: string
{
case User = '#/components/schemas/User';
case Error = '#/components/schemas/Error';
case ValidationError = '#/components/schemas/ValidationError';
}
Use in controllers:
#[OA\Response(
response: 200,
description: 'Success',
content: new OA\JsonContent(ref: SchemaRef::User->value)
)]
Define authentication schemes in configuration:
return [
'documentation' => [
'info' => [
'title' => 'My API',
'version' => '1.0.0',
],
'components' => [
'securitySchemes' => [
'bearerAuth' => [
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
],
],
],
'security' => [
['bearerAuth' => []],
],
],
];
Use in endpoints:
#[OA\Get(
path: '/api/users',
security: [['bearerAuth' => []]],
// ...
)]
Enable caching in production for better performance:
return [
'use_cache' => env('APP_ENV') === 'production',
'cache_key' => 'swagger_docs',
];
Clear cache when documentation changes:
php app.php cache:clear swagger_docs
Extend the generator with custom parsers for additional data sources:
<?php
declare(strict_types=1);
namespace App\OpenApi;
use OpenApi\Analysis;
use OpenApi\Annotations\OpenApi;
use Spiral\OpenApi\Generator\Parser\ParserInterface;
final class CustomParser implements ParserInterface
{
public function parse(OpenApi $openApi, Analysis $analysis): void
{
// Custom parsing logic
}
}
Register the parser:
return [
'parsers' => [
ConfigurationParser::class,
OpenApiParser::class,
CustomParser::class,
],
];
See also
- HTTP — Routing - Configure routes for your API
- Filters — Filter Object - Create request validation filters
- HTTP — Middleware - Add authentication and other middleware
- swagger-php documentation - Complete attribute reference