zvonchuk / phalcon-openapi
Automatic OpenAPI 3.1 spec generation for Phalcon PHP — zero annotations, convention-based inference
Requires
- php: ^8.1
- ext-phalcon: ^5.0
Requires (Dev)
- phpstan/phpstan: ^1.10 || ^2.0
- phpunit/phpunit: ^10.0
This package is not auto-updated.
Last update: 2026-04-22 18:52:59 UTC
README
Automatic OpenAPI 3.1 spec generation for Phalcon PHP applications.
Generates a complete OpenAPI spec by reading your routes, controllers, models, and DTOs — no annotations, no YAML, no manual work. Just write your code and get Swagger UI for free.
Why This Package
| Annotation-based approach | phalcon-openapi | |
|---|---|---|
| Routes | Duplicated in annotations | Read from Phalcon Router automatically |
| Schemas | Separate schema definitions | Built from your existing DTOs and Models |
| Validation | Documentation only | Same attributes for docs AND runtime |
| Status codes | Manual per-endpoint | Convention-based (201, 204, 422, 404) |
| Setup | Custom controller + HTML | Two lines of code |
Requirements
- PHP 8.1+
- Phalcon 5.x
Installation
composer require zvonchuk/phalcon-openapi
Quick Start
use PhalconOpenApi\OpenApiModule; $module = new OpenApiModule([ 'title' => 'My API', 'version' => '1.0.0', ]); $module->registerServices($di);
Two endpoints are registered automatically:
GET /api/openapi.json— OpenAPI 3.1 JSON specGET /api/docs— Swagger UI
How It Works
The package automatically reads:
- Router — all registered routes, HTTP methods, path patterns
- Model MetaData — column types, nullable, primary keys
- Reflection — controller action parameters, return types, docblocks
- PHP 8 Attributes — optional tags, hidden endpoints, extra responses, security
Convention-Based Inference
Zero annotations needed for standard CRUD:
class UserController extends ApiController { // GET /users → 200 with array of User, operationId: listUsers public function listAction(int $page = 1, int $limit = 20) { } // POST /users → 201 Created + 422 Validation Error, operationId: createUser public function createAction(CreateUserRequest $body) { } // GET /users/{id} → 200 + 404 Not Found, operationId: getUser public function getAction(int $id) { } // DELETE /users/{id} → 204 No Content + 404, operationId: deleteUser public function deleteAction(int $id) { } }
The spec generator infers:
- Status codes:
create→ 201,delete→ 204, others → 200 - 422 Validation Error: auto-added when endpoint has a DTO body parameter
- 404 Not Found: auto-added when route has path parameters
- operationId: generated from controller + action name
- Tags: from controller name (
UserController→Users) - Schemas: from Phalcon Model metadata or DTO class properties
Attributes Reference
All attributes are optional — use only when conventions aren't enough.
Endpoint Attributes
use PhalconOpenApi\Attribute\{ApiTag, ApiIgnore, ApiResponse, ApiDescription, ApiSecurity, ApiPaginated}; #[ApiTag('Users')] // Group endpoints (class or method level) #[ApiSecurity('bearerAuth')] // Require auth (class or method level) class UserController extends ApiController { #[ApiDescription( summary: 'List all users', description: 'Returns a paginated list of users with optional filtering' )] #[ApiPaginated] // Wraps response in {data, total, page, per_page} public function listAction(int $page = 1) { } #[ApiIgnore] // Hide from spec public function internalAction() { } #[ApiResponse(409, ConflictResponse::class)] // Extra response code public function createAction(CreateUserRequest $body) { } }
Validation Attributes
Used for both runtime validation (via DtoValidator) and OpenAPI schema generation:
use PhalconOpenApi\Attribute\{Email, Min, Max, StringLength, Format, Pattern, Enum, Url, NotBlank}; class CreateUserRequest { #[NotBlank] #[StringLength(min: 1, max: 255)] public string $name; #[Email] public string $email; public ?string $phone = null; // nullable → type: ["string", "null"] #[Min(1), Max(150)] public int $age; #[Enum(['active', 'inactive'])] public string $status = 'active'; // → enum in OpenAPI schema #[Url] public ?string $website = null; // → format: uri in schema #[Format('date')] public ?string $birthDate = null; // → format: date in schema #[Pattern('/^\+\d{10,15}$/')] // PCRE delimiters stripped for OpenAPI public ?string $mobile = null; }
| Attribute | Validates | OpenAPI Schema |
|---|---|---|
#[Email] |
Valid email format | format: email |
#[StringLength(min: 1, max: 255)] |
String length bounds | minLength, maxLength |
#[Min(1)], #[Max(150)] |
Numeric range | minimum, maximum |
#[Enum(['a', 'b'])] |
Allowed values | enum: ["a", "b"] |
#[Url] |
Valid URL | format: uri |
#[NotBlank] |
Rejects whitespace-only | minLength: 1 |
#[Format('date')] |
Date/datetime/uuid/uri | format: date |
#[Pattern('/regex/')] |
PCRE regex match | pattern: regex |
Nested DTOs and Typed Arrays
Nested objects are validated recursively with dot-notation error paths:
class CreateOrderRequest { #[StringLength(min: 1)] public string $orderNumber; public AddressDto $shippingAddress; // → $ref + recursive validation /** @var OrderItemDto[] */ public array $items = []; // → array with $ref + per-item validation } class AddressDto { #[NotBlank] public string $street; #[NotBlank] public string $city; #[Pattern('/^\d{5}$/')] public string $zip; }
Validation errors for nested objects use dot-notation:
{
"code": 422,
"message": "Validation failed",
"errors": [
"shippingAddress.city is required",
"items[0].zip must match pattern /^\\d{5}$/"
]
}
Security Configuration
$module = new OpenApiModule([ 'title' => 'My API', 'version' => '1.0.0', 'security' => [ 'bearerAuth' => [ 'type' => 'http', 'scheme' => 'bearer', 'bearerFormat' => 'JWT', ], ], ]);
Then annotate controllers or methods with #[ApiSecurity('bearerAuth')].
Base Controller
Extend ApiController for automatic JSON body parsing, DTO validation,
and convenience helpers:
use PhalconOpenApi\ApiController; class UserController extends ApiController { public function getAction(int $id) { $user = User::findFirst($id); if (!$user) { return $this->notFound('User not found'); } return $this->json($user); } public function createAction(CreateUserRequest $body) { // $body is already validated and hydrated automatically $user = new User(); $user->assign((array) $body); $user->save(); return $this->json($user, 201); } }
Configuration Options
$module = new OpenApiModule([ 'title' => 'My API', // required 'version' => '1.0.0', // required 'description' => 'API description', // optional 'modelNamespace' => 'App\\Models', // enables convention-based model inference 'servers' => [ // optional ['url' => 'https://api.example.com'], ], 'security' => [ /* ... */ ], // optional, see Security section ]);
Demo Application
See phalcon-openapi-demo for a complete working example with Docker and CRUD controllers.
Running Tests
vendor/bin/phpunit
License
MIT