- 🏗️ Fluent Builder API - Build JSON Schemas using an intuitive fluent interface
- 📝 Multi-Version Support - Support for JSON Schema 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
- đź’Ş Type Safety - Built with PHP 8.3+ features and strict typing
- 🔍 Version-Aware Features - Automatic validation of version-specific features with helpful error messages
This package supports multiple JSON Schema specification versions with automatic feature validation:
- Draft-07 (2018) - Default version for maximum compatibility
- Draft 2019-09 - Adds advanced features like
$defs
,unevaluatedProperties
,deprecated
- Draft 2020-12 - Latest version with
prefixItems
, dynamic references, and format vocabularies
- PHP 8.3+
composer require cortexphp/json-schema
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaFormat;
use Cortex\JsonSchema\Enums\SchemaVersion;
// Create a basic user schema using the SchemaFactory
$schema = SchemaFactory::object('user')
->description('User schema')
->properties(
SchemaFactory::string('name')
->minLength(2)
->maxLength(100)
->required(),
SchemaFactory::string('email')
->format(SchemaFormat::Email)
->required(),
SchemaFactory::integer('age')
->minimum(18)
->maximum(150),
SchemaFactory::boolean('active')
->default(true),
SchemaFactory::object('settings')
->additionalProperties(false)
->properties(
SchemaFactory::string('theme')
->enum(['light', 'dark']),
),
);
You can also use the objects directly instead of the factory methods. (Example shows PHP 8.4 syntax)
$schema = new ObjectSchema('user')
->description('User schema')
->properties(
new StringSchema('name')
->minLength(2)
->maxLength(100)
->required(),
new StringSchema('email')
->format(SchemaFormat::Email)
->required(),
new IntegerSchema('age')
->minimum(18)
->maximum(150),
new BooleanSchema('active')
->default(true),
new ObjectSchema('settings')
->additionalProperties(false)
->properties(
new StringSchema('theme')->enum(['light', 'dark']),
),
);
// Convert to array
$schema->toArray();
// Convert to JSON string
$schema->toJson();
$schema->toJson(JSON_PRETTY_PRINT);
$data = [
'name' => 'John Doe',
'email' => 'foo', // invalid email
'age' => 16,
'active' => true,
'settings' => [
'theme' => 'dark',
],
];
// Validate data against the schema
try {
$schema->validate($data);
} catch (SchemaException $e) {
echo $e->getMessage(); // "The data must match the 'email' format"
}
// Or just get a boolean
$schema->isValid($data); // false
You can specify the JSON Schema version when creating schemas:
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaVersion;
// Create schema with specific version
$schema = SchemaFactory::string('name', SchemaVersion::Draft_2020_12);
// Set global default version for all new schemas
SchemaFactory::setDefaultVersion(SchemaVersion::Draft_2019_09);
// Change version on existing schema
$schema->version(SchemaVersion::Draft_07);
The package automatically validates that features are only used with compatible versions:
// âś… This works - deprecated is supported in Draft 2019-09+
$schema = SchemaFactory::string('oldField', SchemaVersion::Draft_2019_09)
->deprecated();
// ❌ This throws an exception - deprecated requires Draft 2019-09+
$schema = SchemaFactory::string('oldField', SchemaVersion::Draft_07)
->deprecated(); // SchemaException: Feature not supported in Draft 7
// âś… Array contains count features work in Draft 2019-09+
$arraySchema = SchemaFactory::array('items', SchemaVersion::Draft_2019_09)
->minContains(2)
->maxContains(5);
// âś… Format validation for newer formats
$schema = SchemaFactory::string('duration', SchemaVersion::Draft_2019_09)
->format(SchemaFormat::Duration); // ISO 8601 duration format
// âś… Unevaluated properties/items for advanced validation
$objectSchema = SchemaFactory::object('user', SchemaVersion::Draft_2019_09)
->properties(
SchemaFactory::string('name')->required()
)
->unevaluatedProperties(false); // Strict validation
$arraySchema = SchemaFactory::array('items', SchemaVersion::Draft_2019_09)
->items(SchemaFactory::string())
->unevaluatedItems(false); // Strict array validation
Schemas automatically use the correct keywords for their version:
// Draft-07 uses 'definitions'
$draft07Schema = SchemaFactory::object('user', SchemaVersion::Draft_07)
->addDefinition('address', $addressSchema);
// Output: { "definitions": { "address": {...} } }
// Draft 2019-09+ uses '$defs'
$modernSchema = SchemaFactory::object('user', SchemaVersion::Draft_2019_09)
->addDefinition('address', $addressSchema);
// Output: { "$defs": { "address": {...} } }
Feature | Draft-07 | Draft 2019-09 | Draft 2020-12 |
---|---|---|---|
Basic validation (minLength , pattern , etc.) |
âś… | âś… | âś… |
Conditionals (if /then /else ) |
âś… | âś… | âś… |
Basic formats (email , date-time , etc.) |
âś… | âś… | âś… |
deprecated annotation |
❌ | ✅ | ✅ |
$defs (replaces definitions ) |
❌ | ✅ | ✅ |
minContains /maxContains |
❌ | ✅ | ✅ |
duration /uuid formats |
❌ | ✅ | ✅ |
unevaluatedProperties /unevaluatedItems |
❌ | ✅ | ✅ |
dependentSchemas |
❌ | ✅ | ✅ |
prefixItems (tuple validation) |
❌ | ❌ | ✅ |
Dynamic references ($dynamicRef ) |
❌ | ❌ | ✅ |
You can check if a specific feature is supported in a schema version:
use Cortex\JsonSchema\Enums\SchemaFeature;
use Cortex\JsonSchema\Enums\SchemaVersion;
// Check if a feature is supported
if (SchemaVersion::Draft_07->supports(SchemaFeature::Deprecated)) {
// This will be false - deprecated is not supported in Draft 07
}
if (SchemaVersion::Draft_2019_09->supports(SchemaFeature::Deprecated)) {
// This will be true - deprecated is supported in Draft 2019-09
}
// Get feature information
$feature = SchemaFeature::MinContains;
echo $feature->getDescription(); // "Minimum number of contains matches"
echo $feature->getMinimumVersion()->name; // "Draft201909"
// Create a schema using modern JSON Schema features
$modernSchema = SchemaFactory::object('user', SchemaVersion::Draft_2019_09)
->description('Modern user schema with advanced features')
->properties(
SchemaFactory::string('username')
->minLength(3)
->required(),
SchemaFactory::string('legacyEmail')
->format(SchemaFormat::Email)
->deprecated() // Only available in Draft 2019-09+
->comment('Use contactInfo.email instead'),
SchemaFactory::array('tags')
->items(SchemaFactory::string())
->minContains(1) // Only available in Draft 2019-09+
->maxContains(10),
SchemaFactory::string('uuid')
->format(SchemaFormat::Uuid) // Only available in Draft 2019-09+
);
// This outputs a schema with "$defs" instead of "definitions"
// and includes "$schema": "https://json-schema.org/draft/2019-09/schema"
$modernSchema->toJson();
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::string('name')
->minLength(2)
->maxLength(100)
->pattern('^[A-Za-z]+$')
->readOnly();
$schema->isValid('John Doe'); // true
$schema->isValid('John Doe123'); // false (contains numbers)
$schema->isValid('J'); // false (too short)
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "string",
"title": "name",
"minLength": 2,
"maxLength": 100,
"pattern": "^[A-Za-z]+$",
"readOnly": true
}
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::number('price')
->minimum(0)
->maximum(1000)
->multipleOf(0.01);
$schema->isValid(100); // true
$schema->isValid(1000.01); // false (too high)
$schema->isValid(0.01); // true
$schema->isValid(1.011); // false (not a multiple of 0.01)
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "number",
"title": "price",
"minimum": 0,
"maximum": 1000,
"multipleOf": 0.01
}
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::integer('age')
->exclusiveMinimum(0)
->exclusiveMaximum(150)
->multipleOf(1);
$schema->isValid(18); // true
$schema->isValid(150); // false (too high)
$schema->isValid(0); // false (too low)
$schema->isValid(150.01); // false (not an integer)
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "integer",
"title": "age",
"exclusiveMinimum": 0,
"exclusiveMaximum": 150,
"multipleOf": 1
}
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::boolean('active')
->default(true)
->readOnly();
$schema->isValid(true); // true
$schema->isValid(false); // true
$schema->isValid(null); // false
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "boolean",
"title": "active",
"default": true,
"readOnly": true
}
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::null('deleted_at');
$schema->isValid(null); // true
$schema->isValid(true); // false
$schema->isValid(false); // false
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "null",
"title": "deleted_at"
}
use Cortex\JsonSchema\SchemaFactory;
// Simple array of strings
$schema = SchemaFactory::array('tags')
->items(SchemaFactory::string())
->minItems(1)
->maxItems(3)
->uniqueItems(true);
$schema->isValid(['foo', 'bar']); // true
$schema->isValid(['foo', 'foo']); // false (not unique)
$schema->isValid([]); // false (too few items)
$schema->isValid(['foo', 'bar', 'baz', 'qux']); // false (too many items)
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "tags",
"items": {
"type": "string"
},
"minItems": 1,
"maxItems": 3,
"uniqueItems": true
}
Arrays also support validation of specific items using contains
:
use Cortex\JsonSchema\SchemaFactory;
// Array must contain at least one number between 10 and 20
$schema = SchemaFactory::array('numbers')
->contains(
SchemaFactory::number()
->minimum(10)
->maximum(20)
)
->minContains(2) // must contain at least 2 such numbers
->maxContains(3); // must contain at most 3 such numbers
$schema->isValid([15, 12, 18]); // true (contains 3 numbers between 10-20)
$schema->isValid([15, 5, 25]); // false (only contains 1 number between 10-20)
$schema->isValid([15, 12, 18, 19]); // false (contains 4 numbers between 10-20)
You can also validate tuple-like arrays with different schemas for specific positions:
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::array('coordinates')
->items(
SchemaFactory::number()->description('latitude'),
SchemaFactory::number()->description('longitude'),
);
$schema->isValid([51.5074, -0.1278]); // true (valid lat/long)
$schema->isValid(['invalid', -0.1278]); // false (first item must be number)
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaFormat;
$schema = SchemaFactory::object('user')
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::string('email')->format(SchemaFormat::Email)->required(),
SchemaFactory::object('settings')->properties(
SchemaFactory::string('theme')->enum(['light', 'dark'])
),
)
->additionalProperties(false);
$schema->isValid([
'name' => 'John Doe',
'email' => '[email protected]',
]); // true
$schema->isValid([
'name' => 'John Doe',
'email' => '[email protected]',
'settings' => [
'theme' => 'dark',
],
]); // true
$schema->isValid([
'name' => 'John Doe',
'email' => '[email protected]',
'settings' => [
'theme' => 'dark',
],
'foo' => 'bar',
]); // false (additional properties)
Objects support additional validation features:
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::object('config')
// Validate property names against a pattern
->propertyNames(
SchemaFactory::string()->pattern('^[a-zA-Z]+$')
)
// Control number of properties
->minProperties(1)
->maxProperties(10)
// Control additional properties
->additionalProperties(false); // Disallow additional properties
// Or validate additional properties against a schema
$schema = SchemaFactory::object('config')
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::string('type')->required(),
)
->additionalProperties(
SchemaFactory::string()->minLength(3)
);
// Property names must be alphabetic
$schema->isValid(['123' => 'value']); // false
$schema->isValid(['validKey' => 'value']); // true
// Additional properties must match schema
$schema->isValid([
'name' => 'config1',
'type' => 'test',
'extra' => 'valid', // valid: string with length >= 3
]); // true
$schema->isValid([
'name' => 'config1',
'type' => 'test',
'extra' => 'no', // invalid: string too short
]); // false
$schema->isValid([
'name' => 'config1',
'type' => 'test',
'extra' => 123, // invalid: wrong type
]); // false
Objects also support pattern-based property validation using patternProperties
:
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::object('config')
// Add a single pattern property
->patternProperty('^prefix_',
SchemaFactory::string()->minLength(5)
)
// Add multiple pattern properties
->patternProperties([
'^[A-Z][a-z]+$' => SchemaFactory::string(), // CamelCase properties
'^\d+$' => SchemaFactory::number(), // Numeric properties
]);
// Valid data
$schema->isValid([
'prefix_hello' => 'world123', // Matches ^prefix_ and meets minLength
'Name' => 'John', // Matches ^[A-Z][a-z]+$
'123' => 42, // Matches ^\d+$
]); // true
// Invalid data
$schema->isValid([
'prefix_hi' => 'hi', // Too short for minLength
'invalid' => 'no pattern', // Doesn't match any pattern
'123' => 'not a number', // Wrong type for pattern
]); // false
Pattern properties can be combined with regular properties and additionalProperties
:
$schema = SchemaFactory::object('user')
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::integer('age')->required(),
)
->patternProperty('^custom_', SchemaFactory::string())
->additionalProperties(false);
// Valid:
$schema->isValid([
'name' => 'John',
'age' => 30,
'custom_field' => 'value', // Matches pattern
]);
// Invalid (property doesn't match pattern):
$schema->isValid([
'name' => 'John',
'age' => 30,
'invalid_field' => 'value',
]);
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "user",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer"
}
},
"patternProperties": {
"^custom_": {
"type": "string"
}
},
"required": ["name", "age"],
"additionalProperties": false
}
For advanced validation, you can use unevaluatedProperties
and unevaluatedItems
to control properties and items that weren't explicitly defined:
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaVersion;
// Strict object validation - no unevaluated properties allowed
$schema = SchemaFactory::object('user', SchemaVersion::Draft_2019_09)
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::string('email')->required(),
)
->unevaluatedProperties(false);
// Allow unevaluated properties with schema validation
$schema = SchemaFactory::object('metadata', SchemaVersion::Draft_2019_09)
->properties(
SchemaFactory::string('title')->required(),
)
->unevaluatedProperties(
SchemaFactory::string()->minLength(1) // Any extra properties must be non-empty strings
);
// Strict array validation - no unevaluated items allowed
$arraySchema = SchemaFactory::array('tags', SchemaVersion::Draft_2019_09)
->items(SchemaFactory::string())
->unevaluatedItems(false);
// Allow unevaluated items with schema validation
$arraySchema = SchemaFactory::array('mixed', SchemaVersion::Draft_2019_09)
->items(SchemaFactory::string())
->unevaluatedItems(
SchemaFactory::integer()->minimum(0) // Extra items must be non-negative integers
);
// unevaluatedProperties validation
$schema->isValid([
'name' => 'John',
'email' => '[email protected]',
]); // true
$schema->isValid([
'name' => 'John',
'email' => '[email protected]',
'extra' => 'not allowed', // unevaluatedProperties: false
]); // false
// unevaluatedItems validation
$arraySchema->isValid(['hello', 'world']); // true (all items match string schema)
$arraySchema->isValid(['hello', 'world', 'extra']); // false (unevaluatedItems: false)
View JSON Schema
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"title": "user",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
}
},
"required": ["name", "email"],
"unevaluatedProperties": false
}
Use dependentSchemas
to define conditional schemas based on property presence:
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaVersion;
// Simple dependent schema - when credit_card is present, billing_address is required
$schema = SchemaFactory::object('user', SchemaVersion::Draft_2019_09)
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::string('credit_card'),
)
->dependentSchema('credit_card',
SchemaFactory::object()->properties(
SchemaFactory::string('billing_address')->required()
)
);
// Multiple dependent schemas
$schema = SchemaFactory::object('registration', SchemaVersion::Draft_2019_09)
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::string('email')->required(),
SchemaFactory::string('payment_method')->enum(['credit_card', 'paypal']),
SchemaFactory::boolean('is_premium'),
)
->dependentSchemas([
'payment_method' => SchemaFactory::object()
->if(SchemaFactory::object()->properties(
SchemaFactory::string('payment_method')->const('credit_card')
))
->then(SchemaFactory::object()->properties(
SchemaFactory::string('card_number')->required(),
SchemaFactory::string('cvv')->required(),
)),
'is_premium' => SchemaFactory::object()
->if(SchemaFactory::object()->properties(
SchemaFactory::boolean('is_premium')->const(true)
))
->then(SchemaFactory::object()->properties(
SchemaFactory::string('premium_tier')->enum(['gold', 'platinum'])->required()
)),
]);
View JSON Schema
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"title": "user",
"properties": {
"name": {
"type": "string"
},
"credit_card": {
"type": "string"
}
},
"required": ["name"],
"dependentSchemas": {
"credit_card": {
"type": "object",
"properties": {
"billing_address": {
"type": "string"
}
},
"required": ["billing_address"]
}
}
}
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaType;
$schema = SchemaFactory::union([SchemaType::String, SchemaType::Integer], 'id')
->description('ID can be either a string or an integer')
->enum(['abc123', 'def456', 1, 2, 3])
->nullable();
$schema->isValid('abc123'); // true
$schema->isValid(1); // true
$schema->isValid(null); // true (because it's nullable)
$schema->isValid(true); // false (not a string or integer)
$schema->isValid('invalid'); // false (not in enum)
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": ["string", "integer", "null"],
"title": "id",
"description": "ID can be either a string or an integer",
"enum": ["abc123", "def456", 1, 2, 3]
}
Strings can be validated against various formats:
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaFormat;
$schema = SchemaFactory::object('user')
->properties(
SchemaFactory::string('email')->format(SchemaFormat::Email),
SchemaFactory::string('website')->format(SchemaFormat::Uri),
SchemaFactory::string('hostname')->format(SchemaFormat::Hostname),
SchemaFactory::string('ipv4')->format(SchemaFormat::Ipv4),
SchemaFactory::string('ipv6')->format(SchemaFormat::Ipv6),
SchemaFactory::string('date')->format(SchemaFormat::Date),
SchemaFactory::string('time')->format(SchemaFormat::Time),
SchemaFactory::string('date_time')->format(SchemaFormat::DateTime),
SchemaFactory::string('duration')->format(SchemaFormat::Duration),
SchemaFactory::string('json_pointer')->format(SchemaFormat::JsonPointer),
SchemaFactory::string('relative_json_pointer')->format(SchemaFormat::RelativeJsonPointer),
SchemaFactory::string('uri_template')->format(SchemaFormat::UriTemplate),
SchemaFactory::string('idn_email')->format(SchemaFormat::IdnEmail),
SchemaFactory::string('idn_hostname')->format(SchemaFormat::Hostname),
SchemaFactory::string('iri')->format(SchemaFormat::Iri),
SchemaFactory::string('iri_reference')->format(SchemaFormat::IriReference),
);
The schema specification supports several types of conditional validation:
use Cortex\JsonSchema\SchemaFactory;
// if/then/else condition
$schema = SchemaFactory::object('user')
->properties(
SchemaFactory::string('type')->enum(['personal', 'business']),
SchemaFactory::string('company_name'),
SchemaFactory::string('tax_id'),
)
->if(
SchemaFactory::object()->properties(
SchemaFactory::string('type')->const('business'),
),
)
->then(
SchemaFactory::object()->properties(
SchemaFactory::string('company_name')->required(),
SchemaFactory::string('tax_id')->required(),
),
)
->else(
SchemaFactory::object()->properties(
SchemaFactory::string('company_name')->const(null),
SchemaFactory::string('tax_id')->const(null),
),
);
// allOf - all schemas must match
$schema = SchemaFactory::object()
->allOf(
SchemaFactory::object()
->properties(
SchemaFactory::string('name')->required(),
),
SchemaFactory::object()
->properties(
SchemaFactory::integer('age')
->minimum(18)
->required(),
),
);
// anyOf - at least one schema must match
$schema = SchemaFactory::object('payment')
->anyOf(
SchemaFactory::object()
->properties(
SchemaFactory::string('credit_card')
->pattern('^\d{16}$')
->required(),
),
SchemaFactory::object()
->properties(
SchemaFactory::string('bank_transfer')
->pattern('^\w{8,}$')
->required(),
),
);
// oneOf - exactly one schema must match
$schema = SchemaFactory::object('contact')
->oneOf(
SchemaFactory::object()
->properties(
SchemaFactory::string('email')
->format(SchemaFormat::Email)
->required(),
),
SchemaFactory::object()
->properties(
SchemaFactory::string('phone')
->pattern('^\+\d{10,}$')
->required(),
),
);
// not - schema must not match
$schema = SchemaFactory::string('status')
->not(
SchemaFactory::string()
->enum(['deleted', 'banned']),
);
You can define reusable schema components using definitions and reference them using $ref
:
use Cortex\JsonSchema\SchemaFactory;
$schema = SchemaFactory::object('user')
// Define a reusable address schema
->addDefinition(
'address',
SchemaFactory::object()
->properties(
SchemaFactory::string('street')->required(),
SchemaFactory::string('city')->required(),
SchemaFactory::string('country')->required(),
),
)
// Use the address schema multiple times via $ref
->properties(
SchemaFactory::string('name')->required(),
SchemaFactory::object('billing_address')
->ref('#/definitions/address')
->required(),
SchemaFactory::object('shipping_address')
->ref('#/definitions/address')
->required(),
);
You can also add multiple definitions at once:
$schema = SchemaFactory::object('user')
->addDefinitions([
'address' => SchemaFactory::object()
->properties(
SchemaFactory::string('street')->required(),
SchemaFactory::string('city')->required(),
),
'contact' => SchemaFactory::object()
->properties(
SchemaFactory::string('email')
->format(SchemaFormat::Email)
->required(),
SchemaFactory::string('phone'),
),
]);
The resulting JSON Schema will include both the definitions and references:
View JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "user",
"definitions": {
"address": {
"type": "object",
"properties": {
"street": {
"type": "string"
},
"city": {
"type": "string"
}
},
"required": ["street", "city"]
}
},
"properties": {
"name": {
"type": "string"
},
"billing_address": {
"$ref": "#/definitions/address"
},
"shipping_address": {
"$ref": "#/definitions/address"
}
},
"required": ["name", "billing_address", "shipping_address"]
}
The validate method throws a SchemaException
when validation fails:
use Cortex\JsonSchema\Exceptions\SchemaException;
try {
$schema->validate($data);
} catch (SchemaException $e) {
echo $e->getMessage(); // "The data must match the 'email' format"
}
The package automatically validates that features are compatible with the specified schema version:
try {
// This will throw an exception because 'deprecated' requires Draft 2019-09+
$schema = SchemaFactory::string('oldField', SchemaVersion::Draft_07)
->deprecated();
} catch (SchemaException $e) {
echo $e->getMessage();
// "Feature 'Property deprecation annotation' is not supported in Draft 7.
// Minimum version required: Draft 2019-09."
}
try {
// This will throw an exception because minContains requires Draft 2019-09+
$arraySchema = SchemaFactory::array('items', SchemaVersion::Draft_07)
->minContains(2);
} catch (SchemaException $e) {
echo $e->getMessage();
// "Feature 'Minimum number of contains matches' is not supported in Draft 7.
// Minimum version required: Draft 2019-09."
}
All schema types support these common properties:
$schema
->title('Schema Title')
->description('Schema description')
->default('default value')
->examples(['example1', 'example2'])
->comment('Schema comment')
->readOnly()
->writeOnly();
This uses reflection to infer the schema from the parameters and docblocks.
use Cortex\JsonSchema\SchemaFactory;
/**
* This is the description of the closure
*
* @param string $name The name of the user
* @param array $meta The meta data of the user
* @param ?int $age The age of the user
*/
$closure = function (string $name, array $meta, ?int $age = null): void {};
// Build the schema from the closure
$schema = SchemaFactory::fromClosure($closure);
// Convert to JSON Schema
$schema->toJson();
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"description": "This is the description of the closure",
"properties": {
"name": {
"type": "string",
"description": "The name of the user"
},
"meta": {
"type": "array",
"description": "The meta data of the user"
},
"age": {
"type": ["integer", "null"],
"description": "The age of the user"
}
},
"required": ["name", "meta"]
}
use Cortex\JsonSchema\SchemaFactory;
/**
* This is the description of the class
*/
class User
{
/**
* @var string The name of the user
*/
public string $name;
/**
* @var ?int The age of the user
*/
public ?int $age = null;
/**
* @var float The height of the user in meters
*/
public float $height = 1.7;
}
// Build the schema from the class with specific version
$schema = SchemaFactory::fromClass(User::class, version: SchemaVersion::Draft_2019_09);
// Convert to JSON Schema
$schema->toJson();
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"title": "User",
"description": "This is the description of the class",
"properties": {
"name": {
"type": "string",
"description": "The name of the user"
},
"age": {
"type": [
"integer",
"null"
],
"description": "The age of the user",
"default": null
},
"height": {
"type": "number",
"description": "The height of the user in meters",
"default": 1.7
}
},
"required": ["name"]
}
use Cortex\JsonSchema\SchemaFactory;
/**
* This is the description of the enum
*/
enum PostType: int
{
case Article = 1;
case News = 2;
case Tutorial = 3;
}
// Build the schema from the enum
$schema = SchemaFactory::fromEnum(PostType::class);
// Convert to JSON Schema
$schema->toJson();
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "integer",
"title": "PostType",
"description": "This is the description of the enum",
"enum": [1, 2, 3]
}
You can also create schema objects from existing JSON Schema definitions:
use Cortex\JsonSchema\SchemaFactory;
use Cortex\JsonSchema\Enums\SchemaVersion;
// From a JSON string
$jsonString = '{
"type": "object",
"title": "User",
"properties": {
"name": {"type": "string", "minLength": 2},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name", "email"]
}';
$schema = SchemaFactory::fromJson($jsonString);
// From an array representation
$jsonArray = [
'type' => 'string',
'title' => 'ProductCode',
'pattern' => '^[A-Z]{3}-\d{4}$',
'examples' => ['ABC-1234', 'XYZ-5678']
];
$schema = SchemaFactory::fromJson($jsonArray, SchemaVersion::Draft_2019_09);
// The resulting schema objects work the same as any other schema
$schema->isValid('ABC-1234'); // true
$schema->isValid('invalid'); // false
// You can also modify the schema after creation
$schema->description('Product identification code')
->maxLength(8);
This is particularly useful when:
- Migrating from existing JSON Schema definitions
- Loading schemas from configuration files or databases
- Converting between different schema representations
- Working with external APIs that provide JSON Schema specifications
// Load from a file
$jsonSchema = file_get_contents('user-schema.json');
$schema = SchemaFactory::fromJson($jsonSchema);
// Validate some data
$userData = [
'name' => 'John Doe',
'email' => '[email protected]',
'age' => 30
];
if ($schema->isValid($userData)) {
echo "User data is valid!";
}
The MIT License (MIT). Please see License File for more information.