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
This package requires PHP 8.2+ and uses the following runtime dependencies:
| Package | Purpose | Framework Required? |
|---|---|---|
psr/container |
PSR-11 Container Interface | ❌ No |
symfony/console |
CLI command handling | ❌ No (standalone utility) |
symfony/yaml |
YAML file parsing/writing | ❌ No (standalone utility) |
Note: The Symfony packages used are standalone utility libraries, not framework components. They work independently without the Symfony framework and are used by many non-Symfony projects (Composer, PHPStan, PHPUnit, etc.).
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.
This package is part of the MethorZ HTTP middleware ecosystem:
| Package | Description |
|---|---|
| methorz/http-dto | Automatic HTTP ↔ DTO conversion with validation |
| methorz/http-problem-details | RFC 7807 error handling middleware |
| methorz/http-cache-middleware | HTTP caching with ETag support |
| methorz/http-request-logger | Structured logging with request tracking |
| methorz/openapi-generator | Automatic OpenAPI spec generation (this package) |
These packages work together seamlessly in PSR-15 applications.
Built with:
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ by Thorsten Merz