OpenAPI documentation
You can use swagger-php attributes to generate an OpenAPI spec directly from your services. The key pattern is to define endpoint handlers as named class methods instead of inline closures, giving the attributes a target to attach to.
Install swagger-php
composer require --dev zircote/swagger-phpExtend the Api class
API-level metadata like the title and version belongs on an Api subclass. Use the #[OA\Info] attribute to define it:
<?php namespace ProcessWire;
use PwJsonApi\Api;
use OpenApi\Attributes as OA;
#[
OA\Info(
title: 'Documented API',
version: '1.0.0',
description: 'Example API with OpenAPI documentation',
),
]
class DocumentedApi extends Api {}Add attributes to a service
Define each handler as a named method and annotate it with the matching OpenAPI attribute. In init(), pass the handler using first-class callable syntax ($this->greet(...)):
INFO
The path values in the attributes must include the full route as seen by the client (base path + endpoint path).
<?php namespace ProcessWire;
use PwJsonApi\{Service, Response, EndpointHandlerArgs};
use OpenApi\Attributes as OA;
class DocumentedService extends Service
{
protected function init()
{
$this->addEndpoint('/greet')
->get($this->greet(...))
->post($this->createGreeting(...));
}
#[
OA\Get(
path: '/documented-api/greet',
operationId: 'greet',
parameters: [
new OA\Parameter(
name: 'name',
in: 'query',
required: false,
schema: new OA\Schema(type: 'string'),
description: 'Greeting message',
),
],
responses: [
new OA\Response(response: 200, description: 'Greeting response'),
],
),
]
protected function greet(EndpointHandlerArgs $args): Response
{
return new Response([
'greeting' => $args->request->queryParam('name') ?? null,
]);
}
#[
OA\Post(
path: '/documented-api/greet',
operationId: 'createGreeting',
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'name',
type: 'string',
description: 'Greeting message',
),
],
),
),
responses: [
new OA\Response(response: 201, description: 'Greeting created'),
],
),
]
protected function createGreeting(EndpointHandlerArgs $args): Response
{
return (new Response([
'greeting' => $args->request->body['name'] ?? null,
]))->code(201);
}
}Register the API
Wire everything up in ready.php the same way you would with a regular API:
(new DocumentedApi())
->setBasePath('documented-api')
->addService(new DocumentedService())
->run();Generate the spec
Use the openapi CLI tool to scan your service files and output the spec. Assuming the API classes are placed in site/lib:
./vendor/bin/openapi site/lib -o openapi.yamlTo generate JSON instead:
./vendor/bin/openapi site/lib --format json -o openapi.jsonView and explore the spec
The generated spec file can be opened in an API client or documentation viewer:
- Bruno — open-source API client that can import OpenAPI specs for testing and exploration
- Swagger UI — interactive documentation viewer that renders the spec as a browsable reference with a built-in request console