PHPackages                             duyler/openapi - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. duyler/openapi

ActiveLibrary[API Development](/categories/api)

duyler/openapi
==============

Duyler openapi validator

0.3.3(1mo ago)7262[1 issues](https://github.com/duyler/openapi/issues)[1 PRs](https://github.com/duyler/openapi/pulls)MITPHPPHP ^8.4CI passing

Since Feb 7Pushed 2w ago3 watchersCompare

[ Source](https://github.com/duyler/openapi)[ Packagist](https://packagist.org/packages/duyler/openapi)[ RSS](/packages/duyler-openapi/feed)WikiDiscussions main Synced today

READMEChangelog (6)Dependencies (20)Versions (8)Used By (0)

Duyler OpenAPI Validator
========================

[](#duyler-openapi-validator)

[![Quality Gate Status](https://camo.githubusercontent.com/73a1b2a1d346a6b2fd72bb14935d05ed40078e6e60aea85593837989c36f1b02/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6475796c65725f6f70656e617069266d65747269633d616c6572745f737461747573)](https://sonarcloud.io/summary/new_code?id=duyler_openapi)[![Coverage](https://camo.githubusercontent.com/eaf5466642e6f8bf90df1bf27769dfaa251d2f3f301ed921c65758d4dc657a90/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d6475796c65725f6f70656e617069266d65747269633d636f766572616765)](https://sonarcloud.io/summary/new_code?id=duyler_openapi)[![type-coverage](https://camo.githubusercontent.com/5458a2ed6de6bbab3ab83135241269d538f772eb5bc5a6c5c675739b487de30b/68747470733a2f2f73686570686572642e6465762f6769746875622f6475796c65722f6f70656e6170692f636f7665726167652e737667)](https://shepherd.dev/github/duyler/openapi)[![psalm-level](https://camo.githubusercontent.com/d368f3a892c401a604725bdb29d5afaad41901634766ee2f603cc5b9a27e4f13/68747470733a2f2f73686570686572642e6465762f6769746875622f6475796c65722f6f70656e6170692f6c6576656c2e737667)](https://shepherd.dev/github/duyler/openapi)[![PHP Version](https://camo.githubusercontent.com/24720bcb9de1f87557cc8813276ea70f1480831f84c28de506c0d1b43d338c15/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6475796c65722f6f70656e6170692f7068703f76657273696f6e3d6465762d6d61696e)](https://camo.githubusercontent.com/24720bcb9de1f87557cc8813276ea70f1480831f84c28de506c0d1b43d338c15/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6475796c65722f6f70656e6170692f7068703f76657273696f6e3d6465762d6d61696e)[![Ask DeepWiki](https://camo.githubusercontent.com/0f5ae213ac378635adeb5d7f13cef055ad2f7d9a47b36de7b1c67dbe09f609ca/68747470733a2f2f6465657077696b692e636f6d2f62616467652e737667)](https://deepwiki.com/duyler/openapi)

OpenAPI 3.2 validator for PHP 8.4+

Features
--------

[](#features)

- **Full OpenAPI 3.2 Support** - Complete implementation of OpenAPI 3.2 specification
- **JSON Schema Validation** - Full JSON Schema draft 2020-12 validation with 25+ validators
- **PSR-7 Integration** - PSR-7 HTTP message validation (requires nyholm/psr7)
- **Request Validation** - Validate path parameters, query parameters, headers, cookies, and request body
- **Response Validation** - Validate status codes, headers, and response bodies
- **Multiple Content Types** - Support for JSON, form-data, multipart, text, and XML
- **Built-in Format Validators** - 15 built-in validators (email, UUID, date-time, URI, IPv4/IPv6, etc.)
- **Custom Format Validators** - Easily register custom format validators
- **Discriminator Support** - Full support for polymorphic schemas with discriminators
- **Type Coercion** - Optional automatic type conversion
- **PSR-6 Caching** - Cache parsed OpenAPI documents for better performance
- **PSR-14 Events** - Subscribe to validation lifecycle events
- **Error Formatting** - Multiple error formatters (simple, detailed, JSON)
- **Webhooks Support** - Validate incoming webhook requests
- **Streaming Validation** - Validate NDJSON, SSE, and JSON Text Sequences responses
- **Schema Registry** - Manage multiple schema versions
- **Validator Compilation** - Generate optimized validator code

Installation
------------

[](#installation)

```
composer require duyler/openapi
```

Quick Start
-----------

[](#quick-start)

### Basic Usage

[](#basic-usage)

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

// Validate request
$operation = $validator->validateRequest($request);

// Validate response
$validator->validateResponse($response, $operation);
```

### Using the Validator Interface

[](#using-the-validator-interface)

The builder returns an `OpenApiValidatorInterface` instance. Use this interface for type-hinting in your services:

```
use Duyler\OpenApi\Builder\OpenApiValidatorInterface;

class UserService
{
    public function __construct(
        private readonly OpenApiValidatorInterface $validator,
    ) {}

    public function handleRequest(ServerRequestInterface $request): void
    {
        $operation = $this->validator->validateRequest($request);
        // ...
    }
}
```

The interface exposes the following methods:

MethodDescription`validateRequest(ServerRequestInterface $request): Operation`Validate and return matched operation`validateResponse(ResponseInterface $response, Operation $operation): void`Validate response against operation`validateSchema(mixed $data, string $schemaRef): void`Validate data against a schema reference`getFormattedErrors(ValidationException $e): string`Format validation errors as string`validateWebhook(ServerRequestInterface $request, string $name): Operation`Validate webhook request`validateCallback(ServerRequestInterface $request, string $name): Operation`Validate callback request`resolveLink(string $linkName, array $responseData): array`Resolve link parameters from response data (body only)`resolveLinkWithContext(string $linkName, LinkContext $context): array`Resolve link parameters with full Runtime Expression support ($response.body/header/query, $url, $method, $statusCode)`reset(): void`Reset validator state for reuseUsage
-----

[](#usage)

### Loading OpenAPI Specifications

[](#loading-openapi-specifications)

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

// From YAML file
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

// From JSON file
$validator = OpenApiValidatorBuilder::create()
    ->fromJsonFile('openapi.json')
    ->build();

// From YAML string
$yaml = file_get_contents('openapi.yaml');
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlString($yaml)
    ->build();

// From JSON string
$json = file_get_contents('openapi.json');
$validator = OpenApiValidatorBuilder::create()
    ->fromJsonString($json)
    ->build();
```

### PSR-7 Integration

[](#psr-7-integration)

The validator uses `nyholm/psr7` as the PSR-7 implementation:

```
use Nyholm\Psr7\Factory\Psr17Factory;
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$factory = new Psr17Factory();
$request = $factory->createServerRequest('POST', '/users')
    ->withHeader('Content-Type', 'application/json')
    ->withBody($factory->createStream('{"name": "John"}'));

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

$operation = $validator->validateRequest($request);
// $operation contains the matched path and method
```

### Caching

[](#caching)

Enable PSR-6 caching to skip YAML/JSON parsing and schema construction on every build. See the [Caching](#caching-1) section under Performance for configuration details and compiled validator caching.

### Events

[](#events)

Subscribe to validation events using PSR-14:

```
use Duyler\OpenApi\Event\ArrayDispatcher;
use Duyler\OpenApi\Event\ValidationStartedEvent;
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$dispatcher = new ArrayDispatcher([
    ValidationStartedEvent::class => [
        function (ValidationStartedEvent $event) {
            printf("Validating: %s %s\n", $event->method, $event->path);
        },
    ],
]);

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withEventDispatcher($dispatcher)
    ->build();
```

### Webhooks

[](#webhooks)

Validate webhook requests using the builder API:

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

$operation = $validator->validateWebhook($request, 'payment.webhook');
```

### Callbacks

[](#callbacks)

Validate callback requests using the builder API:

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

$operation = $validator->validateCallback($request, 'myCallback');
```

### Link Resolution

[](#link-resolution)

Resolve OpenAPI Link parameters from response data:

```
use Duyler\OpenApi\Validator\Link\LinkContext;

// Simple resolution (response body only)
$result = $validator->resolveLink('GetUserById', ['id' => 42, 'name' => 'John']);

// Full resolution with Runtime Expression support
$context = new LinkContext(
    body: ['id' => 42, 'name' => 'John'],
    headers: ['X-Request-Id' => 'abc123'],
    queryParams: ['page' => 1],
    url: 'https://api.example.com/users/42',
    method: 'GET',
    statusCode: 200,
);
$result = $validator->resolveLinkWithContext('GetUserById', $context);
```

`resolveLink()` only resolves `$response.body` expressions. Use `resolveLinkWithContext()` for full Runtime Expression support: `$response.body`, `$response.header`, `$response.query`, `$url`, `$method`, and `$statusCode`. Note that `$request.body` and `$request.query` expressions are not supported.

Advanced Usage
--------------

[](#advanced-usage)

### Custom Format Validators

[](#custom-format-validators)

Register custom format validators for domain-specific validation:

```
use Duyler\OpenApi\Validator\Format\FormatValidatorInterface;
use Duyler\OpenApi\Validator\Exception\InvalidFormatException;

// Create a custom validator
class PhoneNumberValidator implements FormatValidatorInterface
{
    public function validate(mixed $data): void
    {
        if (!is_string($data) || !preg_match('/^\+?[1-9]\d{1,14}$/', $data)) {
            throw new InvalidFormatException(
                'phone',
                $data,
                'Value must be a valid E.164 phone number'
            );
        }
    }
}

// Register with the builder
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withFormat('string', 'phone', new PhoneNumberValidator())
    ->build();
```

### Type Coercion

[](#type-coercion)

Enable automatic type conversion for query parameters and request body:

```
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->enableCoercion()  // Convert string "123" to integer 123
    ->build();
```

### Error Formatters

[](#error-formatters)

Choose from built-in error formatters or create your own:

```
use Duyler\OpenApi\Validator\Error\Formatter\DetailedFormatter;
use Duyler\OpenApi\Validator\Error\Formatter\JsonFormatter;

// Detailed formatter with suggestions
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withErrorFormatter(new DetailedFormatter())
    ->build();

// JSON formatter for API responses
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withErrorFormatter(new JsonFormatter())
    ->build();

try {
    $operation = $validator->validateRequest($request);
} catch (ValidationException $e) {
    // Get formatted errors
    $formatted = $validator->getFormattedErrors($e);
    echo $formatted;
}
```

### Discriminator Validation

[](#discriminator-validation)

Validate polymorphic schemas with discriminators:

```
$yaml =  'cat', 'name' => 'Fluffy'];
$validator->validateSchema($data, '#/components/schemas/Pet');
```

### Event-Driven Validation

[](#event-driven-validation)

Subscribe to validation lifecycle events:

```
use Duyler\OpenApi\Event\ValidationStartedEvent;
use Duyler\OpenApi\Event\ValidationFinishedEvent;
use Duyler\OpenApi\Event\ValidationErrorEvent;
use Duyler\OpenApi\Event\ValidationWarningEvent;
use Duyler\OpenApi\Event\ArrayDispatcher;

$dispatcher = new ArrayDispatcher([
    ValidationStartedEvent::class => [
        function (ValidationStartedEvent $event) {
            error_log(sprintf(
                "Validation started: %s %s",
                $event->method,
                $event->path
            ));
        },
    ],
    ValidationFinishedEvent::class => [
        function (ValidationFinishedEvent $event) {
            if ($event->success) {
                error_log(sprintf(
                    "Validation completed in %.3f seconds",
                    $event->duration
                ));
            }
        },
    ],
    ValidationErrorEvent::class => [
        function (ValidationErrorEvent $event) {
            error_log(sprintf(
                "Validation failed for %s %s: %s",
                $event->method,
                $event->path,
                $event->exception->getMessage()
            ));
        },
    ],
    ValidationWarningEvent::class => [
        function (ValidationWarningEvent $event) {
            error_log(sprintf(
                "Warning at %s (property: %s, schema: %s): %s",
                $event->propertyPath,
                $event->propertyName,
                $event->schemaRef ?? 'unknown',
                $event->message
            ));
        },
    ],
]);

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withEventDispatcher($dispatcher)
    ->build();
```

Available events:

EventDescription`ValidationStartedEvent`Dispatched before validation begins`ValidationFinishedEvent`Dispatched after validation completes`ValidationErrorEvent`Dispatched when validation fails`ValidationWarningEvent`Dispatched for non-fatal validation warnings### Schema Registry

[](#schema-registry)

Manage multiple API versions:

> **Warning:** `getDocument()` is a method of the concrete `OpenApiValidator` class, not the `OpenApiValidatorInterface`. If you type-hint the interface, this method is unavailable and will cause a runtime error. Type-hint the concrete class directly or perform an `instanceof` check before calling it.

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;
use Duyler\OpenApi\Validator\OpenApiValidator;
use Duyler\OpenApi\Registry\SchemaRegistry;
use LogicException;

// Load multiple versions
$validatorV1 = OpenApiValidatorBuilder::create()
    ->fromYamlFile('api-v1.yaml')
    ->build();
if (!($validatorV1 instanceof OpenApiValidator)) {
    throw new LogicException('Validator must be an instance of OpenApiValidator to access getDocument()');
}
$documentV1 = $validatorV1->getDocument();

$validatorV2 = OpenApiValidatorBuilder::create()
    ->fromYamlFile('api-v2.yaml')
    ->build();
if (!($validatorV2 instanceof OpenApiValidator)) {
    throw new LogicException('Validator must be an instance of OpenApiValidator to access getDocument()');
}
$documentV2 = $validatorV2->getDocument();

// Register schemas
$registry = new SchemaRegistry();
$registry = $registry
    ->register('api', '1.0.0', $documentV1)
    ->register('api', '2.0.0', $documentV2);

// Get specific version
$schema = $registry->get('api', '1.0.0');

// Get latest version (sorted by semver)
$schema = $registry->get('api');

// List all versions
$versions = $registry->getVersions('api');
// ['1.0.0', '2.0.0']

// Check if a schema exists
$registry->has('api', '1.0.0'); // true
$registry->has('api');          // true
$registry->has('unknown');      // false

// List all registered schema names
$names = $registry->getNames();
// ['api']

// Count schemas and versions
$total = $registry->count();                // 1
$apiVersions = $registry->countVersions('api'); // 2
```

The registry is immutable: `register()` returns a new instance with the added schema.

### Validator Pool

[](#validator-pool)

The validator pool uses an LRU (Least Recently Used) cache to reuse validator instances. The default capacity is 128 entries. When the pool is full, the least recently used validator is evicted.

```
use Duyler\OpenApi\Validator\ValidatorPool;

$pool = new ValidatorPool();          // default: 128 entries
$pool = new ValidatorPool(maxSize: 64); // custom capacity

// Validators are automatically reused and evicted when capacity is exceeded
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withValidatorPool($pool)
    ->build();
```

### Validator Compilation

[](#validator-compilation)

Generate optimized validator code:

```
use Duyler\OpenApi\Compiler\ValidatorCompiler;
use Duyler\OpenApi\Schema\Model\Schema;

$schema = new Schema(
    type: 'object',
    properties: [
        'name' => new Schema(type: 'string'),
        'age' => new Schema(type: 'integer'),
    ],
    required: ['name', 'age'],
);

$compiler = new ValidatorCompiler();
$code = $compiler->compile($schema, 'UserValidator');

// Save generated validator
file_put_contents('UserValidator.php', $code);

// Use generated validator
require_once 'UserValidator.php';
$validator = new UserValidator();
$validator->validate(['name' => 'John', 'age' => 30]);
```

The compiler generates a standalone PHP class with hardcoded validation rules. It does not depend on the library at runtime.

#### Compilation with $ref Resolution

[](#compilation-with-ref-resolution)

Use `compileWithRefResolution()` to inline `$ref` references from an OpenAPI document:

```
use Duyler\OpenApi\Compiler\ValidatorCompiler;
use Duyler\OpenApi\Schema\OpenApiDocument;

$compiler = new ValidatorCompiler();

// Resolve $ref pointers against the document before compiling
$code = $compiler->compileWithRefResolution($schema, 'PetValidator', $document);
```

Circular references are detected and throw a `RuntimeException`.

#### Compilation with Caching

[](#compilation-with-caching)

Use `compileWithCache()` to avoid recompiling the same schema:

```
use Duyler\OpenApi\Compiler\ValidatorCompiler;
use Duyler\OpenApi\Compiler\CompilationCache;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$cachePool = new FilesystemAdapter();
$compilationCache = new CompilationCache($cachePool);

$compiler = new ValidatorCompiler();

// First call compiles and caches, subsequent calls return cached code
$code = $compiler->compileWithCache($schema, 'UserValidator', $compilationCache);
```

`CompilationCache` uses a PSR-6 cache pool and generates a SHA-256 hash of the schema to use as the cache key. Cached entries expire after 24 hours (86400 seconds). This TTL is hardcoded and not configurable.

#### Compiler Limitations

[](#compiler-limitations)

The compiler does not support all JSON Schema keywords. If a schema uses unsupported keywords (`allOf`, `anyOf`, `oneOf`, `not`, `if`/`then`/`else`, `patternProperties`), the compiler throws `UnsupportedKeywordException`. See the Limitations section below for details.

Configuration Options
---------------------

[](#configuration-options)

### Builder Methods

[](#builder-methods)

MethodDescriptionDefault`fromYamlFile(string $path)`Load spec from YAML file-`fromJsonFile(string $path)`Load spec from JSON file-`fromYamlString(string $content)`Load spec from YAML string-`fromJsonString(string $content)`Load spec from JSON string-`withCache(SchemaCache $cache)`Enable PSR-6 caching`null``withEventDispatcher(EventDispatcherInterface $dispatcher)`Set PSR-14 event dispatcher`null``withErrorFormatter(ErrorFormatterInterface $formatter)`Set error formatter`SimpleFormatter``withFormat(string $type, string $format, FormatValidatorInterface $validator)`Register custom format-`withValidatorPool(ValidatorPool $pool)`Set custom validator pool`new ValidatorPool()``withLogger(LoggerInterface $logger)`Set PSR-3 logger`null``withEmptyArrayStrategy(EmptyArrayStrategy $strategy)`Set empty array validation strategy`AllowBoth``enableCoercion()`Enable type coercion`false``enableNullableAsType()`Enable nullable validation (default: true)`true``disableNullableAsType()`Disable nullable validation`false``enableSecurityValidation()`Enable security scheme validation for requests`false``enableStrictFormats()`Reject unknown format values instead of skipping`false``enableReportDeprecated()`Log deprecated schema elements via PSR-3 logger`true``enableServerPathResolution()`Strip server base path from request path before matching`false`Deprecated reporting is enabled by default. Without a PSR-3 logger, deprecation warnings go to `NullLogger` and produce no output. There is no `disableReportDeprecated()` method; to suppress deprecation warnings, simply omit the logger (the default behavior).

### EmptyArrayStrategy

[](#emptyarraystrategy)

When an OpenAPI schema defines a property as `type: array` and the value is an empty array `[]`, JSON does not distinguish between an empty array and an empty object. This strategy controls how the validator treats empty arrays:

StrategyBehavior`AllowBoth` (default)Empty arrays pass validation for both `array` and `object` types`PreferArray`Empty arrays are treated as arrays, not objects`PreferObject`Empty arrays are treated as objects, not arrays`Reject`Empty arrays are rejected for both `array` and `object` types```
use Duyler\OpenApi\Validator\EmptyArrayStrategy;

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withEmptyArrayStrategy(EmptyArrayStrategy::PreferArray)
    ->build();
```

### Example Configuration

[](#example-configuration)

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Duyler\OpenApi\Cache\SchemaCache;
use Duyler\OpenApi\Validator\Error\Formatter\DetailedFormatter;

$cachePool = new FilesystemAdapter();
$schemaCache = new SchemaCache($cachePool, 3600);

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withCache($schemaCache)           // Cache parsed specs
    ->withErrorFormatter(new DetailedFormatter())  // Detailed errors
    ->enableCoercion()                  // Auto type conversion
    ->build();
```

PSR-15 Middleware
-----------------

[](#psr-15-middleware)

Wrap the validator in a PSR-15 middleware to validate incoming requests before they reach your handlers. On validation failure, the middleware returns a `400 Bad Request` response with error details.

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;
use Duyler\OpenApi\Builder\OpenApiValidatorInterface;
use Duyler\OpenApi\Validator\Exception\ValidationException;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Throwable;

final class ValidationMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly OpenApiValidatorInterface $validator,
    ) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            $operation = $this->validator->validateRequest($request);
        } catch (ValidationException $e) {
            return new Response(
                status: 400,
                headers: ['Content-Type' => 'application/json'],
                body: json_encode([
                    'error' => 'Validation failed',
                    'details' => array_map(fn ($error) => [
                        'path' => $error->dataPath(),
                        'message' => $error->message(),
                    ], $e->getErrors()),
                ], JSON_PRETTY_PRINT),
            );
        } catch (Throwable $e) {
            return new Response(
                status: 400,
                headers: ['Content-Type' => 'application/json'],
                body: json_encode(['error' => $e->getMessage()], JSON_PRETTY_PRINT),
            );
        }

        return $handler->handle($request->withAttribute('operation', $operation));
    }
}
```

Register the middleware with your framework's middleware pipeline:

```
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

$middleware = new ValidationMiddleware($validator);

// Register with any PSR-15 compatible framework or dispatcher
// Example with Mezzio:
// $pipeline->pipe(new ValidationMiddleware($validator));
```

> Note: The PSR-15 interfaces require the `psr/http-server-middleware` package, typically provided by your framework.

Supported JSON Schema Keywords
------------------------------

[](#supported-json-schema-keywords)

The validator supports the following JSON Schema draft 2020-12 keywords:

### Type Validation

[](#type-validation)

- `type` - String, number, integer, boolean, array, object, null
- `enum` - Enumerated values
- `const` - Constant value
- `nullable` - Allows null values (default: enabled)

### Nullable Validation

[](#nullable-validation)

By default, the `nullable: true` schema keyword allows null values for a property:

```
properties:
  username:
    type: string
    nullable: true  # Allows null values
```

This behavior is enabled by default. To disable nullable validation and treat `nullable: true` as not allowing null values:

```
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->disableNullableAsType()  // Optional: disable nullable validation
    ->build();
```

### String Validation

[](#string-validation)

- `minLength` / `maxLength` - String length constraints
- `pattern` - Regular expression pattern
- `format` - Format validation (email, uri, uuid, date-time, etc.)

### Pattern Validation

[](#pattern-validation)

All regular expressions in schemas are validated during schema parsing. If a pattern is invalid, an `InvalidPatternException` is thrown.

#### Supported Pattern Fields

[](#supported-pattern-fields)

- `pattern` - Regular expression for string validation
- `patternProperties` - Object with patterns for property keys
- `propertyNames` - Pattern for property name validation

#### Pattern Delimiters

[](#pattern-delimiters)

The library automatically adds delimiters (`/`) to patterns without them. You can specify patterns with or without delimiters:

```
// Without delimiters (recommended)
new Schema(pattern: '^test$')

// With delimiters
new Schema(pattern: '/^test$/')
```

Both variants work identically.

#### Pattern Validation Errors

[](#pattern-validation-errors)

Invalid patterns are detected early and throw descriptive errors:

```
// This will throw InvalidPatternException:
// Invalid regex pattern "/[invalid/": preg_match(): No ending matching delimiter ']' found
new Schema(pattern: '[invalid')
```

### Numeric Validation

[](#numeric-validation)

- `minimum` / `maximum` - Range constraints
- `exclusiveMinimum` / `exclusiveMaximum` - Exclusive ranges
- `multipleOf` - Numeric division

### Array Validation

[](#array-validation)

- `items` / `prefixItems` - Array item validation
- `minItems` / `maxItems` - Array length constraints
- `uniqueItems` - Unique item requirement
- `contains` / `minContains` / `maxContains` - Item presence validation

### Object Validation

[](#object-validation)

- `properties` - Property definitions
- `required` - Required properties
- `additionalProperties` - Additional property rules
- `minProperties` / `maxProperties` - Property count constraints
- `patternProperties` - Pattern-based property validation
- `propertyNames` - Property name validation
- `dependentSchemas` - Conditional schema application

### Composition Keywords

[](#composition-keywords)

- `allOf` - Must match all schemas
- `anyOf` - Must match at least one schema
- `oneOf` - Must match exactly one schema
- `not` - Must not match schema
- `if` / `then` / `else` - Conditional validation

### Advanced Keywords

[](#advanced-keywords)

- `$ref` - Schema references
- `discriminator` - Polymorphic schemas
- `unevaluatedProperties` / `unevaluatedItems` - Dynamic evaluation

Error Handling
--------------

[](#error-handling)

### Validation Exceptions

[](#validation-exceptions)

All validation errors throw `ValidationException` which contains detailed error information:

```
use Duyler\OpenApi\Validator\Exception\ValidationException;

try {
    $operation = $validator->validateRequest($request);
} catch (ValidationException $e) {
    // Get array of validation errors
    $errors = $e->getErrors();

    foreach ($errors as $error) {
        printf(
            "Path: %s\nMessage: %s\nType: %s\n\n",
            $error->dataPath(),
            $error->message(),
            $error->getType()
        );
    }

    // Get formatted errors
    $formatted = $validator->getFormattedErrors($e);
    echo $formatted;
}
```

### Validation Error Reference

[](#validation-error-reference)

All errors implement `ValidationErrorInterface` and provide `dataPath()`, `schemaPath()`, `keyword()`, `message()`, `params()`, `suggestion()`, and `getType()` methods. The `getType()` method returns the validation keyword that triggered the error (e.g., `'type'`, `'minLength'`, `'format'`).

#### Type and Value Errors

[](#type-and-value-errors)

Error TypeKeywordDescription`TypeMismatchError``type`Data type doesn't match schema type`EnumError``enum`Value not in allowed enum`ConstError``const`Value doesn't match constant`InvalidDataTypeException``invalid`Invalid data type encountered#### Format Validation Errors

[](#format-validation-errors)

`InvalidFormatException` extends `RuntimeException` and implements `ValidationErrorInterface` directly (without extending `AbstractValidationError`). It is thrown by format validators rather than the schema validator.

Error TypeKeywordDescription`InvalidFormatException``format`Format validation failed (email, URI, etc.)#### String Validation Errors

[](#string-validation-errors)

Error TypeKeywordDescription`MinLengthError``minLength`String length below minimum`MaxLengthError``maxLength`String length exceeds maximum`PatternMismatchError``pattern`Regular expression pattern violation#### Numeric Validation Errors

[](#numeric-validation-errors)

Error TypeKeywordDescription`MinimumError``minimum` / `exclusiveMinimum`Value below minimum (inclusive/exclusive)`MaximumError``maximum` / `exclusiveMaximum`Value exceeds maximum (inclusive/exclusive)`MultipleOfKeywordError``multipleOf`Value is not a multiple of the specified number> Note: `MinimumError::keyword()` always returns `'minimum'` for both `minimum` and `exclusiveMinimum` violations. Similarly, `MaximumError::keyword()` always returns `'maximum'`. Use `schemaPath()` to distinguish between inclusive (`/minimum`, `/maximum`) and exclusive (`/exclusiveMinimum`, `/exclusiveMaximum`) constraints.

#### Array Validation Errors

[](#array-validation-errors)

Error TypeKeywordDescription`MinItemsError``minItems`Array has fewer items than required`MaxItemsError``maxItems`Array has more items than allowed`DuplicateItemsError``uniqueItems`Array contains duplicate items`ContainsMatchError``contains`Array has no matching items for `contains``MinContainsError``minContains`Too few items match `contains``MaxContainsError``maxContains`Too many items match `contains`#### Object Validation Errors

[](#object-validation-errors)

Error TypeKeywordDescription`RequiredError``required`Required property is missing`MinPropertiesError``minProperties`Object has fewer properties than required`MaxPropertiesError``maxProperties`Object has more properties than allowed`UnevaluatedPropertyError``unevaluatedProperties`Property not allowed and not evaluated by any keyword#### Composition Errors

[](#composition-errors)

Error TypeKeywordDescription`OneOfError``oneOf`Data matches multiple schemas (should match exactly one)`AnyOfError``anyOf`Data doesn't match any of the schemas#### Discriminator Errors

[](#discriminator-errors)

Error TypeKeywordDescription`DiscriminatorMismatchException``discriminator`Discriminator type doesn't match expected`InvalidDiscriminatorValueException``discriminator`Discriminator property has wrong type`UnknownDiscriminatorValueException``discriminator`Discriminator value not in mapping`MissingDiscriminatorPropertyException``discriminator`Required discriminator property is missing#### Security Errors

[](#security-errors)

Error TypeKeywordDescription`MissingSecurityCredentialsError``security`Required security credentials missing from request#### HTTP, Request, and Schema Errors

[](#http-request-and-schema-errors)

These exceptions extend `RuntimeException`, `Exception`, or `InvalidArgumentException` directly and do not implement `ValidationErrorInterface`:

ExceptionDescription`MissingParameterException`Required parameter is missing from request`MissingRequestBodyException`Request body is required but missing`EmptyBodyException`Request body is empty`UnsupportedMediaTypeException`Content-Type not supported by the operation`PathMismatchException`Request path doesn't match any operation template`InvalidParameterException`Parameter value is malformed or invalid`InvalidPatternException`Invalid regex pattern in schema definition`UndefinedResponseException`Response status code not defined in spec`RefResolutionException`Failed to resolve `$ref` reference`SchemaDepthExceededException`Maximum schema nesting depth exceeded`UnknownValidatorException`Unknown validator type requested### Error Formatters

[](#error-formatters-1)

Choose the appropriate error formatter for your use case:

```
// Simple formatter (default)
use Duyler\OpenApi\Validator\Error\Formatter\SimpleFormatter;

// Detailed formatter with suggestions
use Duyler\OpenApi\Validator\Error\Formatter\DetailedFormatter;

// JSON formatter for API responses
use Duyler\OpenApi\Validator\Error\Formatter\JsonFormatter;
```

Built-in Format Validators
--------------------------

[](#built-in-format-validators)

The following format validators are included:

### String Formats

[](#string-formats)

FormatDescriptionExample`date-time`ISO 8601 date-time`2026-01-15T10:30:00Z``date`ISO 8601 date`2026-01-15``time`ISO 8601 time`10:30:00Z``email`Email address`user@example.com``uri`URI`https://example.com``uuid`UUID`550e8400-e29b-41d4-a716-446655440000``hostname`Hostname`example.com``ipv4`IPv4 address`192.168.1.1``ipv6`IPv6 address`2001:db8::1``byte`Base64-encoded data`SGVsbG8gd29ybGQ=``duration`ISO 8601 duration`P3Y6M4DT12H30M5S``json-pointer`JSON Pointer`/path/to/value``relative-json-pointer`Relative JSON Pointer`1/property`### Numeric Formats

[](#numeric-formats)

FormatDescriptionExample`float`Floating-point number`3.14``double`Double-precision number`3.14159265359`### Overriding Built-in Validators

[](#overriding-built-in-validators)

Replace built-in validators with custom implementations:

```
$customEmailValidator = new class implements FormatValidatorInterface {
    public function validate(mixed $data): void
    {
        // Custom email validation logic
        if (!filter_var($data, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidFormatException('email', $data, 'Invalid email');
        }
    }
};

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withFormat('string', 'email', $customEmailValidator)
    ->build();
```

Migration from league/openapi-psr7-validator
--------------------------------------------

[](#migration-from-leagueopenapi-psr7-validator)

### Key Differences

[](#key-differences)

Featureleague/openapi-psr7-validatorduyler/openapiPHP VersionPHP 7.4+PHP 8.4+OpenAPI Version3.03.0, 3.1, 3.2JSON SchemaDraft 7Draft 2020-12Builder PatternFluent builderFluent builder (immutable)Type CoercionEnabled by defaultOpt-inError FormattingBasicMultiple formatters### Migration Examples

[](#migration-examples)

#### Before (league/openapi-psr7-validator)

[](#before-leagueopenapi-psr7-validator)

```
use League\OpenAPIValidation\PSR7\ValidatorBuilder;

$builder = new ValidatorBuilder();
$builder->fromYamlFile('openapi.yaml');
$requestValidator = $builder->getRequestValidator();
$responseValidator = $builder->getResponseValidator();

// Request validation
$requestValidator->validate($request);

// Response validation
$responseValidator->validate($operationAddress, $response);
```

#### After (duyler/openapi)

[](#after-duyleropenapi)

```
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->enableCoercion()
    ->build();

// Request validation - path and method are automatically detected
$operation = $validator->validateRequest($request);

// Response validation
$validator->validateResponse($response, $operation);

// Schema validation
$validator->validateSchema($data, '#/components/schemas/User');
```

Performance
-----------

[](#performance)

### Benchmark Results

[](#benchmark-results)

The following measurements come from the test suite benchmarks run on a standard development machine. Actual numbers vary depending on hardware, PHP version, and schema complexity.

ScenarioSchemaAvg per validationMemory per requestSimple (GET /ping)1 path, no body&lt; 5 ms-Medium (POST /users)4 properties, format validation, enum&lt; 10 ms-Complex (petstore.yaml)Multiple paths, `$ref`, nested schemas&lt; 10 ms-Path scanning100 routes, 50 iterations&lt; 100 ms total&lt; 1 MB growthFull request+response cycle2 properties, email format-&lt; 50 KBThese numbers represent upper bounds enforced by assertions in `tests/Benchmark/PerformanceBenchmarkTest.php`. Real-world performance is typically better.

### Caching

[](#caching-1)

Enable PSR-6 caching when the OpenAPI specification does not change between requests. This skips YAML/JSON parsing and schema construction on every build:

```
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Duyler\OpenApi\Cache\SchemaCache;
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$cachePool = new FilesystemAdapter();
$schemaCache = new SchemaCache($cachePool, 3600); // TTL: 1 hour

$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withCache($schemaCache)
    ->build();
```

For compiled validators, use `CompilationCache` to avoid regenerating PHP code:

```
use Duyler\OpenApi\Compiler\ValidatorCompiler;
use Duyler\OpenApi\Compiler\CompilationCache;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$compilationCache = new CompilationCache(new FilesystemAdapter());
$compiler = new ValidatorCompiler();

$code = $compiler->compileWithCache($schema, 'UserValidator', $compilationCache);
```

### When to Use Compilation

[](#when-to-use-compilation)

The `ValidatorCompiler` generates standalone PHP classes with hardcoded validation rules. This is faster than runtime schema traversal because the compiled code has no reflection, no `$ref` resolution, and no dynamic dispatch.

Use compilation when:

- The schema is stable and does not change at runtime
- You need maximum throughput for hot-path validation
- The schema uses only basic keywords (no `allOf`, `anyOf`, `oneOf`, `not`, `if`/`then`/`else`)

Stick with runtime validation when:

- The schema changes frequently or is user-defined
- You need composition keywords (`allOf`, `anyOf`, `oneOf`)
- You need `$ref` resolution against an OpenAPI document (use `compileWithRefResolution()` instead)

### Coercion Impact

[](#coercion-impact)

Enabling coercion with `enableCoercion()` adds a type conversion pass before validation. For request parameters (query, path, headers), this converts string values to their declared types (e.g., `"123"` to `123`). The overhead is proportional to the number of parameters and properties in the request body. For most APIs, the cost is negligible compared to the validation itself.

### Memory Profiling

[](#memory-profiling)

The validator creates a fixed set of objects during `build()`. Per-request memory usage stays under 50 KB for typical schemas. To profile memory in your application:

```
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

gc_collect_cycles();
$before = memory_get_usage();

$operation = $validator->validateRequest($request);
$validator->validateResponse($response, $operation);

gc_collect_cycles();
$after = memory_get_usage();

printf("Memory delta: %d bytes\n", $after - $before);
```

### Long-Running Processes

[](#long-running-processes)

The validator instance is safe to reuse across requests in long-running processes (RoadRunner, FrankenPHP, Swoole). The internal `ValidatorPool` uses an LRU cache to reuse validator instances without manual cleanup. The pool has a default capacity of 128 entries and automatically evicts the least recently used entries when full.

```
// Build once at worker startup
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->withCache($schemaCache)
    ->build();

// Reuse across requests
while ($request = $worker->waitRequest()) {
    $operation = $validator->validateRequest($request);
    // ...
}
```

If the OpenAPI specification changes at runtime, rebuild the validator. The old instances will be garbage-collected when no longer referenced.

Streaming Response Validation
-----------------------------

[](#streaming-response-validation)

The validator supports three streaming response formats. Each item in the stream is validated individually against the schema defined in `itemSchema` (or `schema` as fallback).

### Supported Content Types

[](#supported-content-types)

FormatContent-TypeSpecificationJSON Lines / NDJSON`application/jsonl` or `application/x-ndjson`Newline-delimited JSON objectsServer-Sent Events`text/event-stream`W3C SSE specificationJSON Text Sequences`application/json-seq`RFC 7464### OpenAPI Specification

[](#openapi-specification)

Use the `itemSchema` keyword within the media type definition to declare the schema for each individual item in the stream:

```
openapi: '3.2.0'
info:
  title: Streaming API
  version: '1.0.0'
paths:
  /logs:
    get:
      operationId: getLogs
      responses:
        '200':
          description: Log stream
          content:
            application/jsonl:
              itemSchema:
                type: object
                properties:
                  timestamp:
                    type: string
                    format: date-time
                  level:
                    type: string
                    enum: [debug, info, warn, error]
                  message:
                    type: string
                required:
                  - timestamp
                  - level
                  - message
  /events:
    get:
      operationId: getEvents
      responses:
        '200':
          description: Event stream
          content:
            text/event-stream:
              itemSchema:
                type: object
                properties:
                  event:
                    type: string
                  data:
                    type: object
                    properties:
                      message:
                        type: string
                      count:
                        type: integer
                required:
                  - event
                  - data
  /records:
    get:
      operationId: getRecords
      responses:
        '200':
          description: Record stream
          content:
            application/json-seq:
              itemSchema:
                type: object
                properties:
                  id:
                    type: string
                  value:
                    type: string
                required:
                  - id
```

### NDJSON / JSON Lines

[](#ndjson--json-lines)

Each line in the response body is a separate JSON object. Empty lines are skipped.

```
use Nyholm\Psr7\Factory\Psr17Factory;
use Duyler\OpenApi\Builder\OpenApiValidatorBuilder;

$factory = new Psr17Factory();
$validator = OpenApiValidatorBuilder::create()
    ->fromYamlFile('openapi.yaml')
    ->build();

$request = $factory->createServerRequest('GET', '/logs');
$operation = $validator->validateRequest($request);

$body = '{"timestamp":"2024-01-01T00:00:00Z","level":"info","message":"Started"}' . "\n"
    . '{"timestamp":"2024-01-01T00:00:01Z","level":"error","message":"Failed"}';

$response = $factory->createResponse(200)
    ->withHeader('Content-Type', 'application/jsonl')
    ->withBody($factory->createStream($body));

$validator->validateResponse($response, $operation);
```

### Server-Sent Events (SSE)

[](#server-sent-events-sse)

The parser handles the standard SSE format with `event`, `data`, and `id` fields. Comments (lines starting with `:`) are ignored. The `data` field is automatically decoded from JSON when possible.

```
$request = $factory->createServerRequest('GET', '/events');
$operation = $validator->validateRequest($request);

$body = "event: message\n"
    . "data: {\"message\":\"hello\",\"count\":1}\n\n"
    . "event: update\n"
    . "data: {\"message\":\"world\",\"count\":2}\n\n";

$response = $factory->createResponse(200)
    ->withHeader('Content-Type', 'text/event-stream')
    ->withBody($factory->createStream($body));

$validator->validateResponse($response, $operation);
```

### JSON Text Sequences (RFC 7464)

[](#json-text-sequences-rfc-7464)

Each record is prefixed with a record separator byte (`0x1E`). This format avoids ambiguity with newlines inside JSON strings.

```
$request = $factory->createServerRequest('GET', '/records');
$operation = $validator->validateRequest($request);

$body = "\x1E" . '{"id":"1","value":"first"}' . "\x1E" . '{"id":"2","value":"second"}';

$response = $factory->createResponse(200)
    ->withHeader('Content-Type', 'application/json-seq')
    ->withBody($factory->createStream($body));

$validator->validateResponse($response, $operation);
```

### Error Handling in Streams

[](#error-handling-in-streams)

When a stream item fails to parse (invalid JSON), the parser logs a warning and yields `null` for that item. The validator skips `null` items. When a parsed item fails schema validation, a `ValidationException` is thrown immediately.

```
use Duyler\OpenApi\Validator\Response\StreamingContentParser;
use Psr\Log\LoggerInterface;

// Custom logger to track parse failures
$parser = new StreamingContentParser($logger);

// Returns [valid, null, valid] - second item is null due to invalid JSON
$items = $parser->parseJsonLines('{"ok":true}' . "\n" . 'bad json' . "\n" . '{"ok":false}');
```

Limitations
-----------

[](#limitations)

### JSON Schema Coverage

[](#json-schema-coverage)

The validator covers approximately 95% of JSON Schema draft 2020-12 keywords. The following are not fully supported:

- `$dynamicRef` / `$dynamicAnchor` - dynamic schema resolution
- `$recursiveRef` / `$recursiveAnchor` - recursive schema resolution
- `contentEncoding` / `contentMediaType` - content validation
- Custom vocabularies and keyword extensions

### Validator Compiler

[](#validator-compiler)

The `ValidatorCompiler` is marked as `@experimental`. It supports a subset of JSON Schema keywords: `type`, `enum`, `const`, `minLength`, `maxLength`, `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`, `pattern`, `minItems`, `maxItems`, `uniqueItems`, `properties`, `required`, `additionalProperties`, `items`.

The compiler does not support composition keywords (`allOf`, `anyOf`, `oneOf`, `not`), conditional keywords (`if`/`then`/`else`), or `patternProperties`. If any of these are present in a schema, `compile()` throws `UnsupportedKeywordException`.

Generated validators throw generic `RuntimeException` on failure rather than the typed error classes used by the runtime validator.

### Security Validation

[](#security-validation)

Security scheme validation is basic. The validator checks that required credentials are present in the request (headers, query parameters, or cookies) but does not verify their correctness or format. Token validation, signature checking, and OAuth flow handling are outside the scope of this library.

The following security scheme types are supported:

- `http/bearer` - Checks for `Authorization: Bearer ...` header
- `apiKey` (query, header, cookie) - Checks for the named parameter in the specified location

The following scheme types are not supported and will produce an error when encountered:

- `http/basic` - Basic authentication
- `oauth2` - OAuth 2.0 flows
- `openIdConnect` - OpenID Connect Discovery

Requirements
------------

[](#requirements)

- **PHP 8.4 or higher** - Uses modern PHP features (readonly classes, match expressions, etc.)
- **PSR-7 HTTP message** - `psr/http-message ^2.0` (required: `nyholm/psr7 ^1.8`)
- **PSR-6 cache** - `psr/cache ^3.0` (e.g., `symfony/cache`, `cache/cache`)
- **PSR-14 events** - `psr/event-dispatcher ^1.0` (e.g., `symfony/event-dispatcher`)
- **PSR-3 logging** - `psr/log ^3.0` (included, optional to use via `withLogger()`)
- **YAML parser** - `symfony/yaml ^7.0 || ^8.0`

Testing
-------

[](#testing)

```
# Run tests
make tests

# Run with coverage
make coverage

# Run static analysis
make psalm

# Fix code style
make cs-fix
```

License
-------

[](#license)

MIT

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance91

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~19 days

Total

6

Last Release

48d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/69f18edde71f0f80540eda4e097854eddf8eb3390f38ff2ad241b9daaf622281?d=identicon)[milinsky](/maintainers/milinsky)

---

Top Contributors

[![milinsky](https://avatars.githubusercontent.com/u/17288321?v=4)](https://github.com/milinsky "milinsky (146 commits)")

---

Tags

swaggeropenapi

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/duyler-openapi/health.svg)

```
[![Health](https://phpackages.com/badges/duyler-openapi/health.svg)](https://phpackages.com/packages/duyler-openapi)
```

###  Alternatives

[symfony/symfony

The Symfony PHP framework

31.4k87.2M2.2k](/packages/symfony-symfony)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M574](/packages/shopware-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M738](/packages/sylius-sylius)[contao/core-bundle

Contao Open Source CMS

1231.6M2.8k](/packages/contao-core-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
