Automatic OpenAPI 3.0 specification generator from routes and DTOs
Automatically generates OpenAPI specifications by analyzing your application's routes and Data Transfer Objects (DTOs). Perfect for Mezzio, Laminas, and any PSR-15 application.
- π Automatic Generation: Scans routes and DTOs to generate complete OpenAPI specs
- π DTO Analysis: Extracts request/response schemas from PHP DTOs with property promotion
- β Validation Integration: Reads Symfony Validator attributes for schema constraints
- π― Handler Detection: Automatically finds request and response DTOs in handlers
- π¦ Multiple Formats: Generates both YAML and JSON outputs
- π§ Zero Configuration: Works out-of-the-box with sensible defaults
- π¨ Customizable: Configure via application config
- π Nested DTOs: Automatically generates schemas for nested DTO references
- π Collections: Supports typed arrays with
@param array<Type>PHPDoc - π² Enums: Full support for backed and unit enums (PHP 8.1+)
- π Union Types: Generates
oneOfschemas for union types (PHP 8.0+) - β‘ Performance: Schema caching for efficient generation
composer require methorz/openapi-generatorAdd to your application's command configuration:
// config/autoload/dependencies.global.php
use Methorz\OpenApi\Command\GenerateOpenApiCommand;
return [
'dependencies' => [
'factories' => [
GenerateOpenApiCommand::class => function ($container) {
return new GenerateOpenApiCommand($container);
},
],
],
];php bin/console openapi:generateThis will create:
public/openapi.yaml- YAML formatpublic/openapi.json- JSON format
// config/autoload/openapi.global.php
return [
'openapi' => [
'title' => 'My API',
'version' => '1.0.0',
],
];The generator automatically analyzes your handlers:
namespace App\Handler;
use App\Request\CreateItemRequest;
use App\Response\ItemResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class CreateItemHandler implements RequestHandlerInterface
{
public function handle(
ServerRequestInterface $request,
CreateItemRequest $dto // β Request DTO detected
): ItemResponse { // β Response DTO detected
// Handler logic...
}
}namespace App\Request;
use Symfony\Component\Validator\Constraints as Assert;
final readonly class CreateItemRequest
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 100)]
public string $name,
#[Assert\NotBlank]
#[Assert\Length(min: 10, max: 500)]
public string $description,
#[Assert\Email]
public string $email,
) {}
}Generated Schema:
components:
schemas:
CreateItemRequest:
type: object
required:
- name
- description
- email
properties:
name:
type: string
minLength: 3
maxLength: 100
description:
type: string
minLength: 10
maxLength: 500
email:
type: string
format: emailThe generator extracts constraints from Symfony Validator attributes:
| Attribute | OpenAPI Property |
|---|---|
@Assert\NotBlank |
required: true |
@Assert\Length(min, max) |
minLength, maxLength |
@Assert\Range(min, max) |
minimum, maximum |
@Assert\Email |
format: email |
@Assert\Url |
format: uri |
@Assert\Uuid |
format: uuid |
Generates enum schemas from PHP 8.1+ backed enums:
enum StatusEnum: string
{
case DRAFT = 'draft';
case ACTIVE = 'active';
case ARCHIVED = 'archived';
}
final readonly class CreateItemRequest
{
public function __construct(
public StatusEnum $status,
) {}
}Generated Schema:
CreateItemRequest:
type: object
properties:
status:
type: string
enum: ['draft', 'active', 'archived']Automatically generates schemas for nested DTO objects:
final readonly class AddressDto
{
public function __construct(
#[Assert\NotBlank]
public string $street,
#[Assert\NotBlank]
public string $city,
public ?string $country = null,
) {}
}
final readonly class CreateUserRequest
{
public function __construct(
public string $name,
public AddressDto $address, // β Nested DTO
public ?AddressDto $billingAddress = null, // β Nullable nested DTO
) {}
}Generated Schema:
CreateUserRequest:
type: object
required: ['name', 'address']
properties:
name:
type: string
address:
$ref: '#/components/schemas/AddressDto'
billingAddress:
$ref: '#/components/schemas/AddressDto'
nullable: true
AddressDto:
type: object
required: ['street', 'city']
properties:
street:
type: string
city:
type: string
country:
type: string
nullable: trueSupports typed arrays using PHPDoc annotations:
/**
* @param array<int, AddressDto> $addresses
* @param array<string> $tags
*/
final readonly class CreateOrderRequest
{
public function __construct(
public string $orderId,
public array $addresses,
public array $tags,
) {}
}Generated Schema:
CreateOrderRequest:
type: object
properties:
orderId:
type: string
addresses:
type: array
items:
$ref: '#/components/schemas/AddressDto'
tags:
type: array
items:
type: stringGenerates oneOf schemas for union types:
final readonly class FlexibleRequest
{
public function __construct(
public string|int $identifier, // β Union type
) {}
}Generated Schema:
FlexibleRequest:
type: object
properties:
identifier:
oneOf:
- type: string
- type: integerScans your application's route configuration:
// config/autoload/routes.global.php
return [
'routes' => [
[
'path' => '/api/v1/items',
'middleware' => [CreateItemHandler::class],
'allowed_methods' => ['POST'],
],
],
];Creates OpenAPI operations with:
- HTTP method (GET, POST, PUT, DELETE, etc.)
- Path parameters (extracted from
{id}patterns) - Request body (for POST/PUT/PATCH)
- Response schemas
- Summary and operationId
- Tags (from module namespace)
Automatically detects and types path parameters:
'/api/v1/items/{id}' β parameter: id (format: uuid)
'/api/v1/users/{userId}' β parameter: userId (type: integer)openapi: 3.0.0
info:
title: My API
version: 1.0.0
description: Automatically generated from routes and DTOs
servers:
- url: http://localhost:8080
description: Local development
paths:
/api/v1/items:
post:
operationId: createItem
summary: create item
tags:
- Items
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateItemRequest'
responses:
201:
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/ItemResponse'
400:
description: Bad Request
404:
description: Not Found
components:
schemas:
CreateItemRequest:
# ... schema definition
ItemResponse:
# ... schema definition// config/autoload/openapi.global.php
return [
'openapi' => [
'title' => 'My API',
'version' => '1.0.0',
'description' => 'API for managing items',
'servers' => [
[
'url' => 'https://api.example.com',
'description' => 'Production',
],
[
'url' => 'http://localhost:8080',
'description' => 'Development',
],
],
],
];View your generated OpenAPI specification:
# Install Swagger UI
composer require swagger-api/swagger-ui
# Access at:
http://localhost:8080/swagger-uiOr use online tools:
# Run all tests
composer test
# Run with coverage
composer test:coverage
# Code style check
composer cs-check
# Fix code style
composer cs-fix
# Static analysis
composer analyze
# All quality checks
composer qualityContributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for new features
- Ensure all quality checks pass (
composer quality) - Submit a pull request
MIT License. See LICENSE for details.
- methorz/http-dto - Automatic DTO mapping and validation
- mezzio/mezzio - PSR-15 middleware microframework
Built with:
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with β€οΈ by Thorsten Merz