Skip to main content
For a ready-to-run script and CI wiring, see Generate OpenAPI and docblocks.

How it Works

The generator leverages the existing framework infrastructure to discover and process your API metadata:
  1. Route Discovery: It uses the Router to find all registered routes and their corresponding controller classes.
  2. Metadata Extraction: For each route, it resolves the associated request and response classes via AbstractApivalkController::getRequestClass() and ::getResponseClasses(), then calls getDocumentation() on those classes to obtain the declared ApivalkRequestDocumentation / ApivalkResponseDocumentation. Route-level metadata (filters, sortings, pagination, rate limit, authorization) is merged in from the Route object itself.
  3. Object Mapping: It maps the Apivalk Property system to OpenAPI objects (Schemas, Parameters, RequestBodies, Responses).
  4. Serialization: It assembles these objects into a final OpenAPI object and serializes it to JSON.

Core Logic

The OpenAPI Object

The OpenAPI class represents the root of the specification. It contains sub-objects for:
  • InfoObject: API title, version, description.
  • ServerObject: Base URLs for your API.
  • PathsObject: Map of endpoints and their methods.
  • ComponentsObject: Reusable schemas and security schemes.

The Generation Process

The OpenAPIGenerator coordinates several specialized generators:
  • PathsGenerator: Processes the list of routes.
  • PathItemGenerator: Handles a single URL (which may have multiple HTTP methods).
  • OperationGenerator: Handles a single HTTP method (GET, POST, etc.) on a path.
  • ParameterGenerator: Converts query and path properties to OpenAPI parameters.
  • RequestBodyGenerator: Converts body properties to a JSON request body schema.
  • ResponseGenerator: Converts response documentation to OpenAPI response schemas.

Usage Example

For a copy-paste bin/generate-openapi script and a complementary docblock-generation script, see the how-to: generate OpenAPI + docblocks.

Security Schemes and Components

The ComponentsObject is where you define reusable elements for your OpenAPI specification, such as security schemes, common schemas, or parameters.

How Security Schemes Connect to Routes

The key concept: the name you give a SecuritySchemeObject is the same name you pass to RouteAuthorization on your routes. This is how Apivalk knows which security scheme protects which route, and how Swagger UI knows when to show the “Authorize” button.
When the OpenAPI spec is generated, Apivalk matches these names to produce the correct security entries per operation. Swagger UI then renders the “Authorize” button and applies the right credentials to the right endpoints.

Factory Methods

SecuritySchemeObject ships four static factories — one per scheme type. Each factory only exposes the parameters that are valid for that type, so you can’t accidentally pass bearerFormat to an apiKey scheme or in to an http scheme. The raw constructor still works but requires you to pass every field positionally, including nulls for fields that don’t apply. Use SecuritySchemeObject::TYPE_* constants wherever you need to reference a type string in your own code.

SecuritySchemeObject::http($name, $scheme, $description, $bearerFormat)

For HTTP authentication schemes (Basic, Bearer, Digest, …). The scheme value must be a registered IANA HTTP Authentication Scheme and is placed in the Authorization header by the client. bearerFormat is optional — it is a documentation hint only and has no effect on validation.
Emits:
name and in are not emitted — they are invalid for http per the OpenAPI spec. The name is used only as the key in components.securitySchemes and to match RouteAuthorization.

SecuritySchemeObject::apiKey($name, $in, $description)

For raw API keys passed as a header, query parameter, or cookie. in must be one of "header", "query", or "cookie". The name is emitted in the spec as the actual header/parameter name the client must send (e.g. X-Api-Key).
Emits:
Note that for apiKey, name is emitted — it is the required field that tells the client which header or parameter to set.

SecuritySchemeObject::oauth2($name, $flows, $description)

For OAuth2. Requires an OAuthFlowsObject describing which grant types are supported. Each flow has its own URLs and scopes; pass null for flows you don’t support.
Emits:

SecuritySchemeObject::openIdConnect($name, $openIdConnectUrl, $description)

For OpenID Connect. Requires the well-known discovery URL of the provider. Swagger UI uses this URL to fetch scopes automatically.
Emits:

End-to-End Example: JWT Bearer

Step 1: Define the security scheme in your ComponentsObject.
Step 2: Reference the same name in your route’s RouteAuthorization.
Step 3: Pass the components to the generator.
The generated OpenAPI JSON will contain the security scheme under components.securitySchemes.api, and the /rest/v1/contract POST operation will have security: [{"api": ["contract"]}]. Swagger UI renders this as an “Authorize” button with a JWT bearer input.

End-to-End Example: Raw API Key Header

Not every API uses JWT. If your API authenticates via a raw API key sent in a custom header (e.g., X-API-Key), use the apiKey type: Step 1: Define the security scheme.
Step 2: Reference it in your route.
Swagger UI will show an “Authorize” button that prompts for an API key value, and will send it as the X-API-Key header on matching requests.

End-to-End Example: OAuth2

For OAuth2, you can define full flows (authorization URL, token URL, scopes) so that Swagger UI can perform the OAuth2 flow directly: Step 1: Define the security scheme with OAuth2 flows.
Step 2: Reference it in your route.

Multiple Security Schemes

You can define multiple security schemes in a single ComponentsObject and use different ones on different routes:

Automatic Security Scheme Generation (Fallback)

If you use security requirements in your routes but haven’t defined them in the ComponentsObject, the OpenAPIGenerator will attempt to automatically generate a base authorization part for you:
  • If the scheme name contains “bearer”, it defaults to an http type with a bearer scheme.
  • If the scheme name contains “oauth2”, it defaults to an oauth2 type with a basic password flow.
  • If the scheme name contains “fido”, it defaults to an apiKey type in the header.
  • Otherwise, it defaults to an apiKey type in the header.
This fallback exists as a convenience, but it is recommended to define your schemes explicitly so you have full control over descriptions, flows, and how Swagger UI renders the authorization interface.
Keep in mind: Future Auto-Discovery. Apivalk is moving towards full auto-discovery of security schemes. In the future, it will be able to automatically detect and document your security configuration (including names and versions) directly from your middleware and authenticators.

Automated Headers

Locale Headers (Accept-Language / Content-Language)

By default, the generator documents localization headers on every operation:
  • Request: An optional Accept-Language header parameter (BCP 47 language tag).
  • Response: A Content-Language header on every response, indicating the resolved locale.
This matches the runtime behavior of the MiddlewareStack, which always resolves the locale from the Accept-Language header and returns Content-Language. You can disable locale header documentation by passing false for the $documentLocaleHeaders parameter:

Filter Documentation Style

By default, all filters for a route are grouped under a single filter parameter using OpenAPI’s style: deepObject convention. Swagger UI renders each filter as a separate input labelled filter[field], and clients send filters as ?filter[status]=active.
If you prefer each filter to appear as its own flat query parameter (?status=active), pass true for the $flatFilters parameter:
With flatFilters: false (default):
  • Swagger UI shows inputs labelled filter[status], filter[name], etc.
  • Clients send ?filter[status]=active&filter[name]=Acme
With flatFilters: true:
  • Swagger UI shows inputs labelled status, name, etc.
  • Clients send ?status=active&name=Acme
Both formats are accepted by the server at runtime regardless of this setting. This option only controls how the filters are presented in the OpenAPI spec.

Rate Limit Headers

When a route has a rate limit defined (via Route::rateLimit()), the generator automatically documents the following response headers on every response for that operation: The header descriptions include the configured window duration from the RateLimitInterface. Routes without a rate limit will not have these headers in their documentation.

Automated Pagination Envelope

When a route has a pagination strategy attached via Route::pagination(...), the generator automatically wraps the matching list response’s schema in the standard pagination envelope (data, plus a pagination object whose shape matches the chosen strategy — page/page_size/total_pages for Pagination::page(), limit/offset/total for Pagination::offset(), limit/current_cursor/next_cursor for Pagination::cursor()). At runtime, attach the matching PaginationResponseInterface to your response via setPaginationResponse(...) — see pagination for the end-to-end flow.