Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- 📝 **Multi-Version Support** - Support for JSON Schema Draft-06, Draft-07, Draft 2019-09, and Draft 2020-12
- ✅ **Validation** - Validate data against schemas with detailed error messages
- 🤝 **Conditional Schemas** - Support for if/then/else, allOf, anyOf, and not conditions
- 🔄 **Reflection** - Generate schemas from PHP Classes, Enums and Closures
- 🔄 **Reflection** - Generate schemas from PHP classes, enums, and closures — including docblock array generics and constructor property promotion
- 💪 **Type Safety** - Built with PHP 8.3+ features and strict typing
- 🔍 **Version-Aware Features** - Automatic validation of version-specific features with helpful error messages

Expand Down
123 changes: 105 additions & 18 deletions docs/json-schema/code-generation/from-classes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: 'Generate JSON schemas automatically from PHP class definitions'
icon: 'code'
---

Generate JSON schemas automatically from PHP classes using reflection and docblock analysis. This feature extracts property types, validation rules, and documentation from your existing PHP classes.
Generate JSON schemas automatically from PHP classes using reflection and docblock analysis. This feature extracts property types, defaults, documentation, and array element types from your existing PHP classes.

## Basic Class Schema Generation

Expand Down Expand Up @@ -113,7 +113,7 @@ class Product
public bool $in_stock = true;

/**
* @var array Product tags
* @var string[] Product tags
*/
public array $tags = [];
}
Expand Down Expand Up @@ -155,6 +155,9 @@ $schema = Schema::fromClass(Product::class);
"tags": {
"type": "array",
"description": "Product tags",
"items": {
"type": "string"
},
"default": []
}
},
Expand Down Expand Up @@ -188,12 +191,65 @@ class ModernUser
// Generate with Draft 2019-09 for deprecated support
$schema = Schema::fromClass(
ModernUser::class,
version: SchemaVersion::Draft_2019_09
schemaVersion: SchemaVersion::Draft_2019_09
);

// The generated schema will include "deprecated": true for old_email
```

## Constructor Property Promotion

Promoted constructor properties are supported. Descriptions and array element types are read from the constructor's `@param` tags when the property itself has no docblock:

```php
/**
* User data transfer object
*
* @param string $name The user's full name
* @param int $age The user's age in years
*/
class UserDto
{
public function __construct(
public string $name,
public int $age = 18,
) {}
}

$schema = Schema::fromClass(UserDto::class);
// name: required string with @param description
// age: optional integer with default 18
```

An explicit `@var` tag on a promoted property takes precedence over the matching `@param` description.

## Array Item Types

When a property is typed as `array`, the converter reads element types from docblock generics and adds an `items` schema. Supported syntax includes:

- `string[]` and `(int|string)[]`
- `array<string>`, `array<int|string>`, and `array<string, int>` (value type is used for key-value maps)
- `list<bool>`, `non-empty-array<string>`, and similar list/array generics

```php
class Article
{
/** @var string[] Article tags */
public array $tags;

/** @var array<int|string> Flexible identifiers */
public array $ids;
}

$schema = Schema::fromClass(Article::class);
// tags: { "type": "array", "items": { "type": "string" } }
// ids: { "type": "array", "items": { "type": ["integer", "string"] } }
```

<Note>
Array item types are limited to JSON Schema scalar types (`string`, `integer`, `number`, `boolean`, `null`, `object`, `array`) and unions of those scalars. Class names (e.g. `DateTime[]`), `mixed`, and nested generics (e.g. `array<array<int>>`) are skipped silently, leaving a plain `array` without `items`.
</Note>

## Complex Property Types

Handle complex property types and collections:
Expand All @@ -210,7 +266,7 @@ class Order
public string $id;

/**
* @var array List of order items
* @var array<string> List of order item SKUs
*/
public array $items;

Expand All @@ -225,26 +281,51 @@ class Order
public ?object $billing_address = null;

/**
* @var array Additional notes
* @var string[] Additional notes
*/
public array $notes = [];

/**
* @var array Metadata key-value pairs
* @var array<string, string> Metadata key-value pairs
*/
public array $metadata = [];
}

$schema = Schema::fromClass(Order::class);
// Custom class types (like Address) are treated as generic 'object' type
// Generic array syntax (e.g., array<OrderItem>) in docblocks is not parsed
// Arrays are treated as generic arrays without item type information
// items, notes, and metadata include string item schemas
// Custom class types (like Address) require ignoreUnknownTypes or separate schema generation
```

<Note>
Custom class types (non-built-in PHP types) are converted to generic `object` schemas. The converter does not recursively analyze nested classes or parse generic array syntax from docblocks. To create schemas for nested objects, generate them separately and combine them using the fluent API.
Custom class types (non-built-in PHP types) are not mapped to JSON Schema types by default and will throw an `UnknownTypeException`. Use `ignoreUnknownTypes: true` to skip unmappable properties, or generate nested object schemas separately and combine them using the fluent API.
</Note>

## Handling Unknown Types

By default, properties typed as custom classes (e.g. `DateTime`, `Address`) cause conversion to fail. Pass `ignoreUnknownTypes: true` to omit those properties from the generated schema:

```php
class Event
{
public string $title;

public DateTime $starts_at;

public ?DateTime $ends_at = null;
}

// Throws UnknownTypeException for DateTime properties
$schema = Schema::fromClass(Event::class);

// Skips DateTime properties, includes title only
$schema = Schema::fromClass(
Event::class,
ignoreUnknownTypes: true,
);
```

This option is also available on `Schema::from()` when passing a class or object instance.

## Enum Integration

Automatically handle backed enums:
Expand Down Expand Up @@ -363,9 +444,14 @@ class CompleteUser extends BaseEntity
}

$schema = Schema::fromClass(CompleteUser::class);
// Schema will include properties from base class and traits
// Schema includes properties from the base class and traits
// Static properties are excluded
```

<Note>
Only **public** properties are included by default. Pass `publicOnly: false` to include protected and private properties. Static properties are always excluded.
</Note>

## Validation and Usage

Use the generated schema to validate data:
Expand Down Expand Up @@ -418,13 +504,14 @@ if ($userSchema->isValid($userData)) {

The schema generator automatically extracts the following from your docblocks:

- **Description text** - From the docblock summary and description
- **Property types** - From `@var` annotations (combined with native PHP types)
- **Parameter types** - From `@param` annotations
- **Deprecation status** - Using the `@deprecated` tag
- **Description text** - From the class docblock summary and `@var` property descriptions
- **Promoted property descriptions** - From matching constructor `@param` tags when no `@var` is present
- **Property types** - From native PHP type hints (combined with `@var` where present)
- **Array item types** - From generic array syntax in `@var` or `@param` tags (scalar element types only)
- **Deprecation status** - Using the `@deprecated` tag on classes and properties

<Note>
Validation rules (like minLength, pattern, format) are **not** extracted from docblocks. You need to apply them programmatically using the fluent API after generation, or define them in your actual PHP code using native types and enums.
Validation rules (like minLength, pattern, format) are **not** extracted from docblocks. Apply them programmatically using the fluent API after generation, or rely on native PHP types and backed enums for type validation.
</Note>

## Best Practices
Expand Down Expand Up @@ -485,15 +572,15 @@ public string $email;
*/
public string $username;

// Good: Document complex array types
// Good: Document array element types with generics
/**
* @var array<string> List of user roles
*/
public array $roles;
```

<Note>
Validation rules like minLength, pattern, format are not extracted from docblocks. Generic array syntax (e.g., `array<string>`, `array<OrderItem>`) in docblocks is not parsed - arrays are treated as generic arrays without item type information. Apply validation rules and array item schemas programmatically using the fluent API after schema generation, or use native PHP types and enums for type validation.
Validation rules like minLength, pattern, and format are not extracted from docblocks. Array item types are inferred from generic syntax for scalar elements only — nested object or array element types (e.g. `array<OrderItem>`) are not resolved recursively. Apply additional validation programmatically using the fluent API after schema generation.
</Note>

## Common Use Cases
Expand Down
48 changes: 43 additions & 5 deletions docs/json-schema/code-generation/from-closures.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Handle optional parameters with default values:
* @param float $price Product price in USD
* @param string $description Product description
* @param bool $active Whether the product is active
* @param array $tags Product tags
* @param string[] $tags Product tags
*/
$createProductClosure = function (
string $name,
Expand Down Expand Up @@ -111,6 +111,9 @@ $schema = Schema::fromClosure($createProductClosure);
"tags": {
"type": "array",
"description": "Product tags",
"items": {
"type": "string"
},
"default": []
}
},
Expand Down Expand Up @@ -184,6 +187,41 @@ The generated schema will include nullable types:
```
</Accordion>

## Array Item Types

When a parameter is typed as `array`, element types from generic docblock syntax are added as an `items` schema:

```php
/**
* Tag a resource with labels
*
* @param string[] $tags Resource tags
* @param array<int|string> $ids Associated identifiers
*/
$tagResourceClosure = function (array $tags, array $ids): void {};

$schema = Schema::fromClosure($tagResourceClosure);
// tags: { "type": "array", "items": { "type": "string" } }
// ids: { "type": "array", "items": { "type": ["integer", "string"] } }
```

Supported syntax matches class property docblocks: `T[]`, `array<T>`, `list<T>`, `array<K,V>`, and unions of scalar element types. Class names, `mixed`, and nested generics are skipped silently.

## Handling Unknown Types

Parameters typed as custom classes throw an `UnknownTypeException` by default. Pass `ignoreUnknownTypes: true` to skip unmappable parameters:

```php
/**
* @param string $title Event title
* @param DateTime $starts_at Start time
*/
$createEvent = function (string $title, DateTime $starts_at): void {};

$schema = Schema::fromClosure($createEvent, ignoreUnknownTypes: true);
// Only includes title
```

## Advanced Parameter Documentation

Use detailed docblock annotations for validation rules:
Expand Down Expand Up @@ -261,7 +299,7 @@ Real-world example for API endpoint validation:
* @param ?string $email Email filter
* @param ?int $age_min Minimum age filter
* @param ?int $age_max Maximum age filter
* @param array $roles Role filter
* @param array<string> $roles Role filter
* @param int $page Page number for pagination
* @param int $per_page Items per page
* @param string $sort_by Sort field
Expand Down Expand Up @@ -319,7 +357,7 @@ $modernApiClosure = function (
// Generate with Draft 2019-09 for deprecated support
$modernSchema = Schema::fromClosure(
$modernApiClosure,
version: SchemaVersion::Draft_2019_09
schemaVersion: SchemaVersion::Draft_2019_09
);

// Apply validation rules programmatically after generation
Expand Down Expand Up @@ -429,7 +467,7 @@ function processData($name, $age, $tags, $date) {}
```

<Note>
Validation rules like format, minLength, pattern are not extracted from docblocks. Generic array syntax (e.g., `array<string>`, `array<OrderItem>`) in docblocks is not parsed - arrays are treated as generic arrays without item type information. Only parameter types and descriptions are extracted. Apply validation rules and array item schemas programmatically using the fluent API after schema generation.
Validation rules like format, minLength, and pattern are not extracted from docblocks. Array item types are inferred from generic syntax for scalar elements only. Apply additional validation programmatically using the fluent API after schema generation.
</Note>

### 4. Design Functions for Schema Generation
Expand All @@ -451,7 +489,7 @@ function handleUserStuff($data, $action, $options = []) {}
| `int` | `integer` | Integer numbers |
| `float` | `number` | Floating-point numbers |
| `bool` | `boolean` | Boolean true/false |
| `array` | `array` | Generic arrays |
| `array` | `array` | Generic arrays; `items` added when docblock specifies element types |
| `?string` | `["string", "null"]` | Nullable string |
| `mixed` | No type constraint | Accepts any type |
| `object` | `object` | Generic object |
Expand Down
4 changes: 2 additions & 2 deletions docs/json-schema/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ All schema types support common properties like title, description, examples, de

### Code Generation
Generate schemas from existing PHP code:
- **PHP Classes** - Extract schemas from class properties and docblocks
- **Closures** - Generate from function signatures and parameter types
- **PHP Classes** - Extract schemas from class properties, constructor promotion, and docblocks (including array item types)
- **Closures** - Generate from function signatures, parameter types, and docblock generics
- **Backed Enums** - Create enum validation schemas
- **JSON Import** - Convert existing JSON Schema definitions

Expand Down
5 changes: 0 additions & 5 deletions phpunit.dist.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage>
<report>
<html outputDirectory="coverage"/>
</report>
</coverage>
<source>
<include>
<directory suffix=".php">./src</directory>
Expand Down
Loading
Loading