Generate an OpenAPI 3.0.1 specification directly from an Ouzo framework application's routing table — no hand-written annotations required.
Given a list of Ouzo RouteRules, the library reflects on each controller method and produces a fully populated
OpenApi document:
- Paths & operations are derived from your routes (HTTP method + URI + controller@action).
- Path & query parameters are read from method arguments (
:idsegments becomepathparameters; remaining scalars becomequeryparameters). - Request bodies are read from object-typed method arguments.
- Responses are read from method return types — including
void, scalars, nullables, arrays of scalars/objects, and PHPDoc-typed arrays (@return Tag[]). - Component schemas are collected automatically by walking class properties recursively, with cycle protection.
- Polymorphism via Symfony's
#[DiscriminatorMap]is rendered asoneOf+ discriminator mapping, with concrete classes emitted asallOfchildren. - Backed enums (
int/string) are emitted as enum schemas with the correct backing type.
The result is a plain object graph you can serialize to JSON or YAML with Symfony Serializer.
- PHP >= 8.4
letsdrink/ouzosymfony/serializerphpdocumentor/reflection-docblock(used to read PHPDoc array element types)
composer require letsdrink/ouzo-open-apiImplement RouteRulesProvider so the library knows which routes to document.
Typically you return whatever Ouzo has already registered in its router.
use Ouzo\OpenApi\RouteRulesProvider;
use Ouzo\Routing\Route;
use Ouzo\Routing\RouteRule;
class AppRouteRulesProvider implements RouteRulesProvider
{
/** @return RouteRule[] */
public function get(): array
{
return Route::getRoutes();
}
}The generator does not populate info or servers — supply them via an
OpenApiCustomizer. Multiple customizers may be registered; each
receives the final OpenApi instance and can mutate it.
use Ouzo\OpenApi\Customizer\OpenApiCustomizer;
use Ouzo\OpenApi\Model\Info\Info;
use Ouzo\OpenApi\Model\OpenApi;
use Ouzo\OpenApi\Model\Servers\Server;
class AppInfoCustomizer implements OpenApiCustomizer
{
public function customize(OpenApi $openApi): void
{
$openApi
->setInfo(new Info()
->setTitle('My API')
->setDescription('Public API')
->setVersion('1.0.0'))
->setServers([new Server()->setUrl('https://api.example.com')]);
}
}OpenApiService is wired through Ouzo's DI container (see DefaultOpenApiModule):
$openApi = $injector->getInstance(OpenApiService::class)->create();
// Serialize with symfony/serializer to JSON or YAML
echo $serializer->serialize($openApi, 'json');The library favors convention — most types resolve from PHP reflection and PHPDoc alone — but two attributes give you fine-grained control.
#[Hidden] — exclude routes from the spec
Apply on a controller class (hides every action) or on a single method.
use Ouzo\OpenApi\Attributes\Hidden;
class InternalController
{
#[Hidden]
public function debugDump(): void {}
}use Ouzo\OpenApi\Attributes\Schema;
class CreateUser
{
#[Schema(required: true)]
public string $email;
#[Schema(nullable: true)]
public ?string $nickname;
}Element types for array are read from PHPDoc. Without it the property/return is emitted as a generic array.
/** @return Tag[] */
public function listTags(): array { /* ... */ }Use Symfony's #[DiscriminatorMap] on the parent type and the library will emit a discriminator schema plus oneOf
references where the parent type is used.
OpenApiService::create() orchestrates three collaborators:
PathsService— turns eachRouteRuleinto aPathItem/Operation(skipping#[Hidden]ones viaHiddenChecker).ComponentsService— drains the sharedSchemasRepository(singleton) intocomponents.schemas.OpenApiCustomizersRepository— runs every registeredOpenApiCustomizerover the final document.
Type resolution lives in src/OpenApi/Util/Type/ and is the layer that bridges PHP reflection + PHPDoc to OpenAPI
schemas.
Tests are golden-file based: OpenApiServiceTest builds the full graph manually, serializes the resulting OpenApi,
and compares against tests/OpenApi/expected-openapi.json. Fixtures under tests/Fixtures/ (notably SampleController
and SampleClass) are the canonical examples of every supported PHP type → OpenAPI shape.
# Inside a PHP 8.4+ environment with Composer
composer install
vendor/bin/phpunit testsMIT