OpenAPI 3.1 Features
This document describes the OpenAPI 3.1 specific features supported by php-swagger-test.
Table of Contents
Version Detection
The library automatically detects OpenAPI 3.1 schemas based on the openapi field:
use ByJG\ApiTools\Base\Schema;
// OpenAPI 3.0 - returns OpenApiSchema
$schema30 = Schema::fromJson('{"openapi": "3.0.3", ...}');
// OpenAPI 3.1 - returns OpenApi31Schema
$schema31 = Schema::fromJson('{"openapi": "3.1.0", ...}');
// Check version
echo $schema31->getSpecificationVersion(); // "3.1"
Nullable Types
OpenAPI 3.0 Approach
OpenAPI 3.0 uses the nullable keyword:
{
"type": "string",
"nullable": true
}
OpenAPI 3.1 Approach
OpenAPI 3.1 uses JSON Schema union types:
{
"type": [
"string",
"null"
]
}
Testing Nullable Fields
$schema = Schema::fromFile('openapi31.json');
$responseBody = $schema->getResponseParameters('/users', 'get', 200);
// Both null and string values are valid
$responseBody->match(['email' => null]); // Valid
$responseBody->match(['email' => '[email protected]']); // Valid
Multiple Nullable Types
You can have multiple types including null:
{
"type": [
"string",
"number",
"null"
]
}
Nullable Objects with Required Fields
OpenAPI 3.1 supports nullable objects that have required fields and nested properties using $ref:
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"manager": {
"type": ["object", "null"],
"required": ["phone"],
"properties": {
"email": {
"$ref": "#/components/schemas/emailProperty"
},
"phone": {
"$ref": "#/components/schemas/phoneNumberProperty"
},
"firstName": {
"$ref": "#/components/schemas/firstNameProperty"
}
}
}
},
"required": ["name"]
}
When validating nullable objects with required fields:
// Valid: manager is null
$requestBody->match([
'name' => 'ACME Corp',
'manager' => null
]);
// Valid: manager is omitted (not required)
$requestBody->match([
'name' => 'ACME Corp'
]);
// Valid: manager has required phone field
$requestBody->match([
'name' => 'ACME Corp',
'manager' => [
'phone' => '+1234567890'
]
]);
// Invalid: manager is present but missing required phone
$requestBody->match([
'name' => 'ACME Corp',
'manager' => [
'email' => '[email protected]'
]
]); // Throws NotMatchedException
This feature is particularly useful when modeling optional complex objects that, when present, must satisfy specific requirements.
Webhooks
Webhooks allow you to describe incoming HTTP requests that your API will receive.
Schema Definition
{
"openapi": "3.1.0",
"webhooks": {
"newUser": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "integer"
},
"event": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
Testing Webhooks
$schema = Schema::fromFile('openapi31.json');
// Check if webhooks exist
if ($schema->hasWebhooks()) {
// Get all webhook names
$webhooks = $schema->getWebhookNames(); // ['newUser', 'orderUpdated']
// Validate webhook request
$requestBody = $schema->getWebhookRequestParameters('newUser', 'post');
$requestBody->match([
'userId' => 123,
'event' => 'user.created'
]);
// Validate webhook response
$responseBody = $schema->getWebhookResponseParameters('newUser', 'post', 200);
$responseBody->match($responseData);
}
JSON Schema 2020-12 Features
const Keyword
Validate that a value is exactly a constant:
{
"type": "object",
"properties": {
"status": {
"const": "active"
}
}
}
$requestBody->match(['status' => 'active']); // Valid
$requestBody->match(['status' => 'inactive']); // Throws NotMatchedException
Conditional Schemas (if/then/else)
Apply different validation rules based on conditions:
{
"type": "object",
"properties": {
"country": {
"type": "string"
},
"postalCode": {
"type": "string"
}
},
"if": {
"properties": {
"country": {
"const": "US"
}
}
},
"then": {
"properties": {
"postalCode": {
"pattern": "^[0-9]{5}(-[0-9]{4})?$"
}
}
},
"else": {
"properties": {
"postalCode": {
"pattern": "^[A-Z0-9 -]+$"
}
}
}
}
// US postal code
$requestBody->match([
'country' => 'US',
'postalCode' => '12345'
]); // Valid
// Non-US postal code
$requestBody->match([
'country' => 'CA',
'postalCode' => 'K1A 0B1'
]); // Valid
Tuple Validation (prefixItems)
Validate arrays with specific types at specific positions:
{
"type": "array",
"prefixItems": [
{
"type": "number",
"description": "latitude"
},
{
"type": "number",
"description": "longitude"
}
],
"minItems": 2,
"maxItems": 2
}
$requestBody->match([40.7128, -74.0060]); // Valid (lat, lng)
$requestBody->match([40.7128]); // Invalid - too few items
$requestBody->match(['40.7128', -74.0060]); // Invalid - first item is string
$ref with Sibling Keywords
In OpenAPI 3.1, you can have sibling keywords alongside $ref:
{
"$ref": "#/components/schemas/User",
"description": "The authenticated user",
"example": {
"id": 1,
"name": "John"
}
}
In OpenAPI 3.0, you would need to wrap this in allOf.
Migration from 3.0 to 3.1
1. Update openapi Version
{
"openapi": "3.1.0",
// Changed from "3.0.3"
...
}
2. Replace nullable with Type Arrays
Before (3.0):
{
"type": "string",
"nullable": true
}
After (3.1):
{
"type": [
"string",
"null"
]
}
3. Simplify $ref Usage
Before (3.0):
{
"allOf": [
{
"$ref": "#/components/schemas/User"
},
{
"description": "Additional info"
}
]
}
After (3.1):
{
"$ref": "#/components/schemas/User",
"description": "Additional info"
}
4. Optional Server Variable Defaults
In 3.1, server variable defaults are optional:
Before (3.0 - required):
{
"servers": [
{
"url": "http://{host}",
"variables": {
"host": {
"default": "localhost"
// Required
}
}
}
]
}
After (3.1 - optional):
{
"servers": [
{
"url": "http://{host}",
"variables": {
"host": {
"enum": [
"localhost",
"example.com"
]
// default is optional
}
}
}
]
}
5. Use Modern JSON Schema Keywords
Take advantage of new keywords:
- Use
constinstead of single-valueenum - Use
prefixItemsfor tuple validation - Use
if/then/elsefor conditional validation
Compatibility Notes
- Backward Compatibility: OpenAPI 3.0 schemas continue to work without changes
- Mixed Usage: You can use both 3.0 and 3.1 schemas in the same project
- Automatic Detection: The library automatically detects the version and applies appropriate validation rules
- No Breaking Changes: Existing tests and code continue to work
Examples
See the /tests/example/ directory for complete working examples:
openapi31.json- Basic 3.1 schemaopenapi31-nullable.json- Nullable type examplesopenapi31-nested-ref-required.json- Nullable objects with required fields and nested $refopenapi31-webhooks.json- Webhook definitionsopenapi31-conditional.json- Conditional schemasopenapi31-tuples.json- Tuple validation