| name | developing-openapi-specs |
| description | Guide for contributing to the OpenAPI layer using processor pattern, complexity management, and functional programming. Use when adding endpoint factories, processors, parameter descriptions, reducing cyclomatic complexity, or fixing OpenAPI validation errors. Covers architecture patterns from user-service repository. |
Developing OpenAPI Specs
Guide for contributing to the OpenAPI layer at src/Shared/Application/OpenApi/.
This skill covers architecture patterns, complexity management techniques, and best practices for maintaining OpenAPI specifications while keeping code quality high.
Related Skills:
When to Use This Skill
- Adding new OpenAPI endpoint factories
- Creating processors for spec transformation
- Adding parameter descriptions or validation
- Modifying OpenAPI generation logic
- Fixing OpenAPI validation errors
- Reducing cyclomatic complexity in OpenAPI code
Architecture Overview
The OpenAPI layer follows a Processor Pattern with clear separation of concerns:
src/Shared/Application/OpenApi/
├── Builder/ # Schema and parameter builders
├── Factory/
│ ├── Endpoint/ # Custom endpoint factories
│ ├── Request/ # Request body schemas
│ ├── Response/ # Response schemas
│ └── UriParameter/ # Path parameter factories
├── Processor/ # Spec transformation processors
└── OpenApiFactory.php # Main coordinator for tagged factories/processors
Key Principles
- Single Responsibility: Each processor/factory handles ONE concern
- Immutability: Use
with*() methods, never mutate directly
- Functional Programming: Prefer
array_map, array_filter over loops
- Flat Control Flow: Prefer guard clauses, extracted helpers, and explicit branches
- Early Returns: Guard clauses reduce nesting and complexity
Quick Pattern Reference
Pattern 1: OPERATIONS Constant
private const OPERATIONS = ['Get', 'Post', 'Put', 'Patch', 'Delete'];
private function processPathItem(PathItem $pathItem): PathItem
{
foreach (self::OPERATIONS as $operation) {
$pathItem = $pathItem->{'with' . $operation}(
$this->processOperation($pathItem->{'get' . $operation}())
);
}
return $pathItem;
}
Pattern 2: Guard Clauses and Explicit Branches
private function processOperation(?Operation $operation): ?Operation
{
if ($operation === null) {
return null;
}
if ($operation->getParameters() === []) {
return $operation;
}
return $operation->withParameters(...);
}
Pattern 3: Functional Array Operations
private function collectRequired(array $params): array
{
return array_values(
array_map(
static fn (Parameter $p) => $p->name,
array_filter($params, static fn (Parameter $p) => $p->isRequired())
)
);
}
private function collectRequired(array $params): array
{
$required = [];
foreach ($params as $param) {
if ($param->isRequired()) {
$required[] = $param->name;
}
}
return $required;
}
Pattern 4: Extract Methods (Keep Under 20 Lines)
private function processContent(Operation $operation): Operation
{
$content = $operation->getRequestBody()->getContent();
$modified = false;
foreach ($content as $mediaType => $mediaTypeObject) {
$fixedProperties = $this->fixProperties($mediaTypeObject);
if ($fixedProperties !== null) {
$content[$mediaType]['schema']['properties'] = $fixedProperties;
$modified = true;
}
}
return $modified ? $operation->withRequestBody(...) : $operation;
}
private function fixProperties(array $mediaTypeObject): ?array
{
if (!isset($mediaTypeObject['schema']['properties'])) {
return null;
}
$properties = $mediaTypeObject['schema']['properties'];
return array_map(
static fn ($prop) => self::fixProperty($prop),
$properties
);
}
Complexity Target Metrics
- Min Complexity: 93% (source code threshold)
- Max Cyclomatic Complexity/Method: 10
- Max Method Length: 20 lines
- Max Class Complexity: ≤ 8
Quick Start Guide
Adding a Processor
- Create class in
src/Shared/Application/OpenApi/Processor/
- Implement
OpenApiProcessorInterface
- Use OPERATIONS constant, guard clauses, and functional style
- Tag the service with
app.openapi_processor and an explicit priority
- Do not add a new dedicated constructor argument to
OpenApiFactory
See REFERENCE.md - Adding Processors for complete examples.
Adding an Endpoint Factory
- Implement
EndpointFactoryInterface
- Tag with
app.openapi_endpoint_factory in services.yaml
- Auto-discovered by
OpenApiFactory
See REFERENCE.md - Adding Endpoint Factories for step-by-step guide.
Adding Parameter Descriptions
Extend ParameterDescriptionProcessor with a focused provider and wire it into getParameterDescriptions():
private function getYourFilterDescriptions(): array
{
return [
'yourParam' => 'Description of parameter',
'yourParam[]' => 'Array variant description',
];
}
private function getParameterDescriptions(): array
{
return array_merge(
$this->getOrderDescriptions(),
$this->getYourFilterDescriptions(),
);
}
Anti-Patterns to Avoid
- ❌ Don't Mutate: Use
withX() methods, not direct assignment
- ❌ Don't Use empty(): Explicitly check
$array === [] or $string === ''
- ❌ Don't Create God Classes: Split into focused processors
- ❌ Don't Repeat HTTP Methods: Use OPERATIONS constant
- ❌ Don't Write Nested Branch Mazes: flatten control flow with guard clauses and helper extraction
Testing Your Changes
make generate-openapi-spec
make validate-openapi-spec
make phpinsights
make unit-tests
Expected Results:
- OpenAPI validation: "No results with severity 'hint' or higher"
- PHPInsights: Code 100%, Complexity ≥93% (src), Architecture 100%, Style 100%
- Unit tests: 100% coverage
Architecture Layer (DDD)
OpenAPI code belongs in the Application layer:
- ✅ Application Layer:
src/Shared/Application/OpenApi/
- ❌ Never in Domain: Domain must have zero framework dependencies
- See implementing-ddd-architecture for layer rules
OpenAPI components can depend on:
- Domain entities (for type information)
- Symfony/API Platform components
- DTOs and transformers
Detailed Documentation
For comprehensive patterns, step-by-step guides, and examples:
- REFERENCE.md - Full directory structure, all patterns with examples, troubleshooting guide
- Codebase Examples:
ParameterDescriptionProcessor.php - Parameter-description provider pattern
IriReferenceTypeProcessor.php - Schema transformation pattern
PathParametersProcessor.php - Delegation pattern for path cleanup
Quick Checklist
Before committing OpenAPI changes:
Remember: Low complexity and high quality go hand-in-hand. Use functional programming, guard clauses, and method extraction to keep code maintainable.