Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

021/json-schema

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

021/json-schema - npm Package Compare versions

Comparing version
v1.0.0
to
dev-main
+2
021-projects-json-schema-2fe93b0/.gitignore
.idea
vendor
{
"name": "021/json-schema",
"description": "Object-Oriented JSON Schema Generation",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"O21\\JsonSchema\\": "src/"
}
},
"authors": [
{
"name": "021-projects",
"email": "20326979+021-projects@users.noreply.github.com"
}
],
"require": {
"php": ">=8.1"
}
}
MIT License
Copyright (c) 2024 021-projects
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Object-Oriented JSON Schema Generation in PHP
<p align="center">
<a href="https://packagist.org/packages/021/json-schema"><img src="https://img.shields.io/packagist/dt/021/json-schema" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/021/json-schema"><img src="https://img.shields.io/packagist/v/021/json-schema" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/021/json-schema"><img src="https://img.shields.io/packagist/l/021/json-schema" alt="License"></a>
</p>
This library provides a way to generate JSON schemas in PHP using an object-oriented approach. It is inspired by the [JSON Schema](https://json-schema.org/) standard and aims to provide a way to generate JSON schemas in a more readable and maintainable way.
## Installation
```bash
composer require 021/json-schema
```
## Usage
```php
use O21\JsonSchema\Schema;
use O21\JsonSchema\Enums\Type;
use O21\JsonSchema\Enums\Format;
$schema = new Schema(
schema: 'http://json-schema.org/draft-07/schema#',
type: Type::OBJECT,
properties: [
'name' => new Schema(
type: Type::STRING,
minLength: 1,
maxLength: 255
),
'age' => new Schema(
type: Type::INTEGER,
minimum: 0,
maximum: 150
),
'addresses' => new Schema(
type: Type::ARRAY,
items: new Schema(
type: Type::OBJECT,
properties: [
'street' => new Schema(
type: Type::STRING,
minLength: 1,
maxLength: 255
),
'city' => new Schema(
type: Type::STRING,
minLength: 1,
maxLength: 255
),
'zip' => new Schema(
type: Type::STRING,
pattern: '^[0-9]{5}$'
)
],
required: ['street', 'city', 'zip']
),
),
'email' => new Schema(
type: Type::STRING,
format: Format::EMAIL
),
'phone' => new Schema(
type: Type::STRING,
pattern: '^\+[0-9]{1,3}\.[0-9]{1,14}$'
),
'is_active' => new Schema(
type: Type::BOOLEAN,
default: true,
),
]
);
// Convert the schema to JSON
$json = $schema->toJson();
// Convert the schema to an object
$obj = $schema->toObject();
```
### Transform
Almost all properties listed on the [JSON Schema reference](https://json-schema.org/understanding-json-schema/reference) are supported, but if some properties are missing in the current version of the library or you want to add your own, you can use the transform method.
It is called when the `Schema` class is transformed into the `stdClass` class to generate JSON from it:
```php
use O21\JsonSchema\Schema;
$schema = new Schema(
transform: function (stdClass $schema): void {
$schema->foo = 'bar';
}
);
```
## Support Us
- **Bitcoin**: 1G4U12A7VVVaUrmj4KmNt4C5SaDmCXuW49
- **Litecoin**: LXjysogo9AHiNE7AnUm4zjprDzCCWVESai
- **Ethereum**: 0xd23B42D0A84aB51a264953f1a9c9A393c5Ffe4A1
- **Tron**: TWEcfzu2UAPsbotZJh8DrEpvdZGho79jTg
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Format;
use O21\JsonSchema\Enums\Type;
use O21\JsonSchema\Schema;
trait AllTypesConstruct
{
protected array $callSetAliases = [
'schema' => 'dialect',
'properties' => 'addProps',
'patternProperties' => 'patternProps',
'additionalProperties' => 'additionalProps',
'unevaluatedProperties' => 'unevaluatedProps',
'minimum' => 'min',
'maximum' => 'max',
'exclusiveMinimum' => 'exclusiveMin',
'exclusiveMaximum' => 'exclusiveMax',
];
public function __construct(
// Dialect
?string $schema = null,
protected ?Type $type = null,
// GenericKeywords
?string $title = null,
?string $description = null,
mixed $default = null,
?array $examples = null,
?bool $deprecated = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?string $comment = null,
?array $enum = null,
mixed $const = null,
// Composition
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
?Schema $not = null,
// ArrayTypeSchema
?array $prefixItems = null,
Schema|bool|null $items = null,
?Schema $contains = null,
?int $minContains = null,
?int $maxContains = null,
?int $minItems = null,
?int $maxItems = null,
?bool $uniqueItems = null,
// StringTypeSchema
?int $minLength = null,
?int $maxLength = null,
?Format $format = null,
?string $pattern = null,
// NumericTypeSchema
?int $minimum = null,
?int $maximum = null,
int|bool|null $exclusiveMinimum = null,
int|bool|null $exclusiveMaximum = null,
?int $multipleOf = null,
// ObjectTypeSchema
?array $properties = null,
?array $required = null,
?array $patternProperties = null,
array|bool|null $additionalProperties = null,
array|bool|null $unevaluatedProperties = null,
?Schema $propertyNames = null,
?int $minProperties = null,
?int $maxProperties = null,
// Conditions
?array $dependentRequired = null,
?array $dependentSchemas = null,
?array $if = null,
?array $then = null,
?array $else = null,
// Media
?string $contentMediaType = null,
?string $contentEncoding = null,
?Schema $contentSchema = null,
// Bundling
protected ?string $id = null,
protected ?string $anchor = null,
protected ?string $ref = null,
protected ?array $defs = null,
// Transformation
protected mixed $transform = null,
) {
$args = get_defined_vars();
unset($args['this']);
$args = array_filter($args, static fn($v) => $v !== null);
foreach ($args as $key => $value) {
$this->callSet($key, $value);
}
}
protected function callSet(string $key, mixed $value): void
{
if ($method = $this->callSetMethod($key)) {
$this->$method($value);
}
}
protected function callSetMethod(string $key): ?string
{
$method = $this->callSetAliases[$key] ?? $key;
return method_exists($this, $method) ? $method : null;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
use O21\JsonSchema\Enums\Type;
trait ArrayTypeSchema
{
protected ?array $_prefixItems = null;
protected Schema|bool|null $_items = null;
protected ?Schema $_contains = null;
protected ?int $_minContains = null;
protected ?int $_maxContains = null;
protected ?int $_minItems = null;
protected ?int $_maxItems = null;
protected ?bool $_uniqueItems = null;
protected function arrayObject(): \stdClass
{
$obj = $this->defaultObject();
if ($this->_prefixItems !== null) {
$obj->prefixItems = $this->mapToObject($this->_prefixItems);
}
if ($this->_items !== null) {
$obj->items = is_bool($this->_items)
? $this->_items
: $this->_items->toObject();
}
if ($this->_contains !== null) {
$obj->contains = $this->_contains->toObject();
}
if ($this->_minContains !== null) {
$obj->minContains = $this->_minContains;
}
if ($this->_maxContains !== null) {
$obj->maxContains = $this->_maxContains;
}
if ($this->_minItems !== null) {
$obj->minItems = $this->_minItems;
}
if ($this->_maxItems !== null) {
$obj->maxItems = $this->_maxItems;
}
if ($this->_uniqueItems !== null) {
$obj->uniqueItems = $this->_uniqueItems;
}
return $obj;
}
public function prefixItems(array $items): self
{
$this->assertType(Type::ARRAY);
$this->assertArrayItemsInstanceOf($items, Schema::class);
$this->_prefixItems = $items;
return $this;
}
public function items(Schema|bool $items): self
{
$this->assertType(Type::ARRAY);
$this->_items = $items;
return $this;
}
public function contains(Schema $contains): self
{
$this->assertType(Type::ARRAY);
$this->_contains = $contains;
return $this;
}
public function minContains(int $minContains): self
{
$this->assertType(Type::ARRAY);
$this->_minContains = $minContains;
return $this;
}
public function maxContains(int $maxContains): self
{
$this->assertType(Type::ARRAY);
$this->_maxContains = $maxContains;
return $this;
}
public function minItems(int $minItems): self
{
$this->assertType(Type::ARRAY);
$this->_minItems = $minItems;
return $this;
}
public function maxItems(int $maxItems): self
{
$this->assertType(Type::ARRAY);
$this->_maxItems = $maxItems;
return $this;
}
public function uniqueItems(bool $uniqueItems = true): self
{
$this->assertType(Type::ARRAY);
$this->_uniqueItems = $uniqueItems;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Bundling
{
protected ?string $_id = null;
protected ?string $_anchor = null;
protected ?string $_ref = null;
protected ?array $_defs = null;
public function id(string $id): self
{
$this->_id = $id;
return $this;
}
public function anchor(string $anchor): self
{
$this->_anchor = $anchor;
return $this;
}
public function ref(string $ref): self
{
$this->_ref = $ref;
return $this;
}
public function defs(array $defs): self
{
$this->assertArrayItemsInstanceOf($defs, Schema::class);
$this->_defs = $defs;
return $this;
}
protected function applyBundling(\stdClass $schema): void
{
if ($this->_id) {
$schema->{'$id'} = $this->_id;
}
if ($this->_anchor) {
$schema->{'$anchor'} = $this->_anchor;
}
if ($this->_ref) {
$schema->{'$ref'} = $this->_ref;
}
if ($this->_defs) {
$schema->{'$defs'} = $this->arrayToObject($this->_defs);
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Composition
{
protected array $_allOf = [];
protected array $_anyOf = [];
protected array $_oneOf = [];
protected ?Schema $_not = null;
public function allOf(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_allOf = $schemas;
return $this;
}
public function anyOf(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_anyOf = $schemas;
return $this;
}
public function oneOf(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_oneOf = $schemas;
return $this;
}
public function not(Schema $schema): self
{
$this->_not = $schema;
return $this;
}
protected function applyComposition(\stdClass $schema): void
{
if (! empty($this->_allOf)) {
$schema->allOf = $this->_allOf;
}
if (! empty($this->_anyOf)) {
$schema->anyOf = $this->_anyOf;
}
if (! empty($this->_oneOf)) {
$schema->oneOf = $this->_oneOf;
}
if (! is_null($this->_not)) {
$schema->not = $this->_not->toObject();
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Conditions
{
protected array $_dependentRequired = [];
protected array $_dependentSchemas = [];
protected array $_if = [];
protected array $_then = [];
protected array $_else = [];
public function dependentRequired(array|string $key, ?array $required = null): self
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->_dependentRequired[$k] = $v;
}
return $this;
}
$this->_dependentRequired[$key] = $required;
return $this;
}
public function dependentSchemas(array|string $key, ?array $schemas = null): self
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->_dependentSchemas[$k] = $v;
}
return $this;
}
$this->_dependentSchemas[$key] = $schemas;
return $this;
}
public function if(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_if = $schemas;
return $this;
}
public function then(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_then = $schemas;
return $this;
}
public function else(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_else = $schemas;
return $this;
}
protected function applyConditions(\stdClass $schema): void
{
if (! empty($this->_dependentRequired)) {
$schema->dependentRequired = $this->arrayToObject($this->_dependentRequired);
}
if (! empty($this->_dependentSchemas)) {
$schema->dependentSchemas = $this->arrayToObject($this->_dependentSchemas);
}
if (! empty($this->_if)) {
$schema->if = $this->mapToObject($this->_if);
}
if (! empty($this->_then)) {
$schema->then = $this->mapToObject($this->_then);
}
if (! empty($this->_else)) {
$schema->else = $this->mapToObject($this->_else);
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait Dialect
{
protected ?string $_dialect = null;
public function dialect(string $dialect): self
{
$this->_dialect = $dialect;
return $this;
}
protected function applyDialect(\stdClass $schema): void
{
if ($this->_dialect !== null) {
$schema->{'$schema'} = $this->_dialect;
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait GenericKeywords
{
protected ?string $_title = null;
protected ?string $_description = null;
protected mixed $_default = null;
protected ?array $_examples = null;
protected ?bool $_deprecated = null;
protected ?bool $_readOnly = null;
protected ?bool $_writeOnly = null;
protected ?string $_comment = null;
protected ?array $_enum = null;
protected mixed $_const = null;
public function title(string $title): static
{
$this->_title = $title;
return $this;
}
public function description(string $description): static
{
$this->_description = $description;
return $this;
}
public function default(mixed $default): static
{
$this->_default = $default;
return $this;
}
public function examples(array $examples): static
{
$this->_examples = $examples;
return $this;
}
public function deprecated(bool $deprecated = true): static
{
$this->_deprecated = $deprecated;
return $this;
}
public function readOnly(bool $readOnly = true): static
{
$this->_readOnly = $readOnly;
return $this;
}
public function writeOnly(bool $writeOnly = true): static
{
$this->_writeOnly = $writeOnly;
return $this;
}
public function comment(string $comment): static
{
$this->_comment = $comment;
return $this;
}
public function enum(array $enum): static
{
$this->_enum = $enum;
return $this;
}
public function const(mixed $const): static
{
$this->_const = $const;
return $this;
}
protected function applyGenericKeywords(\stdClass $schema): void
{
if ($this->_title !== null) {
$schema->title = $this->_title;
}
if ($this->_description !== null) {
$schema->description = $this->_description;
}
if ($this->_default !== null) {
$schema->default = $this->_default;
}
if ($this->_examples !== null) {
$schema->examples = $this->_examples;
}
if ($this->_deprecated !== null) {
$schema->deprecated = $this->_deprecated;
}
if ($this->_readOnly !== null) {
$schema->readOnly = $this->_readOnly;
}
if ($this->_writeOnly !== null) {
$schema->writeOnly = $this->_writeOnly;
}
if ($this->_comment !== null) {
$schema->{'$comment'} = $this->_comment;
}
if ($this->_enum !== null) {
$schema->enum = $this->_enum;
}
if ($this->_const !== null) {
$schema->const = $this->_const;
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Media
{
protected ?string $_contentEncoding = null;
protected ?string $_contentMediaType = null;
protected ?Schema $_contentSchema = null;
public function contentEncoding(string $contentEncoding): self
{
$this->_contentEncoding = $contentEncoding;
return $this;
}
public function contentMediaType(string $contentMediaType): self
{
$this->_contentMediaType = $contentMediaType;
return $this;
}
public function contentSchema(Schema $contentSchema): self
{
$this->_contentSchema = $contentSchema;
return $this;
}
protected function applyMedia(\stdClass $schema): void
{
if ($this->_contentEncoding !== null) {
$schema->contentEncoding = $this->_contentEncoding;
}
if ($this->_contentMediaType !== null) {
$schema->contentMediaType = $this->_contentMediaType;
}
if ($this->_contentSchema !== null) {
$schema->contentSchema = $this->_contentSchema->toObject();
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Type;
trait NumericTypeSchema
{
protected int|float|null $minimum = null;
protected int|float|null $maximum = null;
protected int|float|bool|null $exclusiveMinimum = null;
protected int|float|bool|null $exclusiveMaximum = null;
protected int|float|null $_multipleOf = null;
protected function numericObject(): \stdClass
{
$obj = $this->defaultObject();
$props = $this->filterArray([
'minimum' => $this->minimum,
'maximum' => $this->maximum,
'exclusiveMinimum' => $this->exclusiveMinimum,
'exclusiveMaximum' => $this->exclusiveMaximum,
'multipleOf' => $this->_multipleOf,
]);
$this->applyArrayProps($obj, $props);
return $obj;
}
public function min(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->minimum = $value;
return $this;
}
public function max(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->maximum = $value;
return $this;
}
public function exclusiveMin(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->exclusiveMinimum = $value;
return $this;
}
public function exclusiveMax(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->exclusiveMaximum = $value;
return $this;
}
public function multipleOf(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->_multipleOf = $value;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
use O21\JsonSchema\Enums\Type;
trait ObjectTypeSchema
{
protected array $_required = [];
protected array $properties = [];
protected array $patternProperties = [];
protected array|bool $additionalProperties = [];
protected array|bool $unevaluatedProperties = [];
protected ?Schema $_propertyNames = null;
protected ?int $_minProperties = null;
protected ?int $_maxProperties = null;
protected function objectObject(): \stdClass
{
$obj = $this->defaultObject();
if (! empty($this->properties)) {
$obj->properties = $this->arrayToObject($this->properties);
}
if (! empty($this->_required)) {
$obj->required = $this->_required;
}
if (! empty($this->patternProperties)) {
$obj->patternProperties = $this->arrayToObject($this->patternProperties);
}
if (! is_array($this->additionalProperties)
|| ! empty($this->additionalProperties)
) {
$obj->additionalProperties = $this->additionalProperties;
}
if (! is_array($this->unevaluatedProperties)
|| ! empty($this->unevaluatedProperties)
) {
$obj->unevaluatedProperties = $this->unevaluatedProperties;
}
if ($this->_propertyNames !== null) {
$obj->propertyNames = $this->_propertyNames->toObject();
}
if ($this->_minProperties !== null) {
$obj->minProperties = $this->_minProperties;
}
if ($this->_maxProperties !== null) {
$obj->maxProperties = $this->_maxProperties;
}
return $obj;
}
public function addProps(array $props): void
{
$this->assertArrayItemsInstanceOf($props, Schema::class);
foreach ($props as $key => $schema) {
$this->addProp($key, $schema);
}
}
public function addProp(
string $key,
Schema $schema,
): self {
$this->assertType(Type::OBJECT);
$this->properties[$key] = $schema;
return $this;
}
public function removeProps(array|string ...$keys): void
{
if (count($keys) === 1 && is_array($keys[0])) {
$keys = $keys[0];
}
foreach ($keys as $key) {
$this->removeProp($key);
}
}
public function removeProp(string $key): self
{
$this->assertType(Type::OBJECT);
unset($this->properties[$key]);
return $this;
}
public function required(array|string ...$keys): self
{
$this->assertType(Type::OBJECT);
if (count($keys) === 1 && is_array($keys[0])) {
$keys = $keys[0];
}
$this->_required = array_merge($this->_required, $keys);
return $this;
}
public function notRequired(array|string ...$keys): self
{
$this->assertType(Type::OBJECT);
if (count($keys) === 1 && is_array($keys[0])) {
$keys = $keys[0];
}
$this->_required = array_diff($this->_required, $keys);
return $this;
}
public function minProperties(int $min): self
{
$this->assertType(Type::OBJECT);
$this->_minProperties = $min;
return $this;
}
public function maxProperties(int $max): self
{
$this->assertType(Type::OBJECT);
$this->_maxProperties = $max;
return $this;
}
public function additionalProps(bool|Schema $props): self
{
$this->assertType(Type::OBJECT);
$this->additionalProperties = $props;
return $this;
}
public function unevaluatedProps(bool|Schema $props): self
{
$this->assertType(Type::OBJECT);
$this->unevaluatedProperties = $props;
return $this;
}
public function patternProps(array $props): self
{
$this->assertType(Type::OBJECT);
$this->assertArrayItemsInstanceOf($props, Schema::class);
$this->patternProperties = $props;
return $this;
}
public function propertyNames(Schema $schema): self
{
$this->assertType(Type::OBJECT);
$this->_propertyNames = $schema;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait PropGetter
{
public function getProp(string $prop): mixed
{
if (property_exists($this, $prop)) {
return $this->$prop;
}
if (! str_starts_with($prop, '_')) {
return $this->getProp('_'.$prop);
}
return null;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait StdBuilding
{
protected function arrayToObject(array $array): \stdClass
{
$obj = new \stdClass();
foreach ($array as $key => $value) {
$obj->{$key} = is_a($value, Schema::class)
? $value->toObject()
: $value;
}
return $obj;
}
protected function applyArrayProps(\stdClass $object, array $props): void
{
foreach ($props as $key => $value) {
$object->{$key} = $value;
}
}
protected function mapToObject(array $array): array
{
return array_map(static fn($value) => $value->toObject(), $array);
}
protected function filterArray(array $array): array
{
return array_filter($array, static fn($value) => ! empty($value));
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Format;
use O21\JsonSchema\Enums\Type;
trait StringTypeSchema
{
protected ?int $_minLength = null;
protected ?int $_maxLength = null;
protected ?Format $_format = null;
protected ?string $_pattern = null;
public function stringObject(): \stdClass
{
$obj = $this->defaultObject();
if ($this->_minLength !== null) {
$obj->minLength = $this->_minLength;
}
if ($this->_maxLength !== null) {
$obj->maxLength = $this->_maxLength;
}
if ($this->_format !== null) {
$obj->format = $this->_format->value;
}
if ($this->_pattern !== null) {
$obj->pattern = $this->_pattern;
}
return $obj;
}
public function minLength(int $value): self
{
$this->assertType(Type::STRING);
$this->_minLength = $value;
return $this;
}
public function maxLength(int $value): self
{
$this->assertType(Type::STRING);
$this->_maxLength = $value;
return $this;
}
public function format(Format $value): self
{
$this->assertType(Type::STRING);
$this->_format = $value;
return $this;
}
public function pattern(string $value): self
{
$this->assertType(Type::STRING);
$this->_pattern = $value;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait Transformation
{
protected $_transform = null;
/**
* Apply transformation to the Schema when calling toObject
*
* @param callable $transform
* @return \O21\JsonSchema\Concerns\Transformation|\O21\JsonSchema\Schema
*/
public function transform(callable $transform): self
{
$this->_transform = $transform;
return $this;
}
protected function applyTransform(\stdClass $obj): void
{
if ($this->_transform === null) {
return;
}
call_user_func($this->_transform, $obj);
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Type;
trait Validation
{
protected function assertArrayItemsInstanceOf(array $items, string $class): void
{
foreach ($items as $item) {
if (! is_a($item, $class)) {
throw new \InvalidArgumentException(
'Items must be an instance of '.$class
);
}
}
}
protected function assertType(Type ...$type): void
{
if (! in_array($this->type, $type, true)) {
$types = array_map(static fn($t) => $t->value, $type);
throw new \InvalidArgumentException(
'This method can be called only for ' . implode(', ', $types) . ' type'
);
}
}
}
<?php
namespace O21\JsonSchema\Enums;
enum Format: string
{
case DATE = 'date';
case TIME = 'time';
case DATE_TIME = 'date-time';
case EMAIL = 'email';
case IDN_EMAIL = 'idn-email';
case HOSTNAME = 'hostname';
case IDN_HOSTNAME = 'idn-hostname';
case IPV4 = 'ipv4';
case IPV6 = 'ipv6';
case UUID = 'uuid';
case URI = 'uri';
case URI_REFERENCE = 'uri-reference';
case URI_TEMPLATE = 'uri-template';
case IRI = 'iri';
case IRI_REFERENCE = 'iri-reference';
case JSON_POINTER = 'json-pointer';
case RELATIVE_JSON_POINTER = 'relative-json-pointer';
case REGEX = 'regex';
}
<?php
namespace O21\JsonSchema\Enums;
enum Type: string
{
case OBJECT = 'object';
case ARRAY = 'array';
case STRING = 'string';
case NUMBER = 'number';
case INTEGER = 'integer';
case BOOLEAN = 'boolean';
case NULL = 'null';
}
<?php
namespace O21\JsonSchema;
use O21\JsonSchema\Concerns\AllTypesConstruct;
use O21\JsonSchema\Concerns\ArrayTypeSchema;
use O21\JsonSchema\Concerns\Bundling;
use O21\JsonSchema\Concerns\Composition;
use O21\JsonSchema\Concerns\Conditions;
use O21\JsonSchema\Concerns\Dialect;
use O21\JsonSchema\Concerns\GenericKeywords;
use O21\JsonSchema\Concerns\Media;
use O21\JsonSchema\Concerns\NumericTypeSchema;
use O21\JsonSchema\Concerns\ObjectTypeSchema;
use O21\JsonSchema\Concerns\PropGetter;
use O21\JsonSchema\Concerns\StdBuilding;
use O21\JsonSchema\Concerns\StringTypeSchema;
use O21\JsonSchema\Concerns\Transformation;
use O21\JsonSchema\Concerns\Validation;
use O21\JsonSchema\Enums\Type;
/**
* Class Schema
*
* @package O21\JsonSchema
* @see https://json-schema.org/understanding-json-schema/reference
*/
class Schema
{
use AllTypesConstruct;
use ArrayTypeSchema;
use StringTypeSchema;
use ObjectTypeSchema;
use NumericTypeSchema;
use Bundling;
use Dialect;
use GenericKeywords;
use Composition;
use Conditions;
use Media;
use Validation;
use Transformation;
use StdBuilding;
use PropGetter;
/**
* @throws \JsonException
*/
public function toJson(): string
{
return json_encode($this->toObject(), JSON_THROW_ON_ERROR);
}
public function toObject(): \stdClass
{
$obj = match ($this->type) {
Type::ARRAY => $this->arrayObject(),
Type::STRING => $this->stringObject(),
Type::OBJECT => $this->objectObject(),
Type::NUMBER, Type::INTEGER => $this->numericObject(),
default => $this->defaultObject(),
};
$this->applyDialect($obj);
$this->applyComposition($obj);
$this->applyGenericKeywords($obj);
$this->applyMedia($obj);
$this->applyConditions($obj);
$this->applyBundling($obj);
$this->applyTransform($obj);
return $obj;
}
protected function defaultObject(): \stdClass
{
$obj = new \stdClass();
if (! empty($this->type)) {
$obj->type = $this->type->value;
}
return $obj;
}
}
-2
.idea
vendor
{
"name": "021/json-schema",
"description": "Object-Oriented JSON Schema Generation",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"O21\\JsonSchema\\": "src/"
}
},
"authors": [
{
"name": "021-projects",
"email": "20326979+021-projects@users.noreply.github.com"
}
],
"require": {
"php": ">=8.1"
}
}
MIT License
Copyright (c) 2024 021-projects
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Object-Oriented JSON Schema Generation in PHP
<p align="center">
<a href="https://packagist.org/packages/021/json-schema"><img src="https://img.shields.io/packagist/dt/021/json-schema" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/021/json-schema"><img src="https://img.shields.io/packagist/v/021/json-schema" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/021/json-schema"><img src="https://img.shields.io/packagist/l/021/json-schema" alt="License"></a>
</p>
This library provides a way to generate JSON schemas in PHP using an object-oriented approach. It is inspired by the [JSON Schema](https://json-schema.org/) standard and aims to provide a way to generate JSON schemas in a more readable and maintainable way.
## Installation
```bash
composer require 021/json-schema
```
## Usage
```php
use O21\JsonSchema\Schema;
use O21\JsonSchema\Enums\Type;
use O21\JsonSchema\Enums\Format;
$schema = new Schema(
schema: 'http://json-schema.org/draft-07/schema#',
type: Type::OBJECT,
properties: [
'name' => new Schema(
type: Type::STRING,
minLength: 1,
maxLength: 255
),
'age' => new Schema(
type: Type::INTEGER,
minimum: 0,
maximum: 150
),
'addresses' => new Schema(
type: Type::ARRAY,
items: new Schema(
type: Type::OBJECT,
properties: [
'street' => new Schema(
type: Type::STRING,
minLength: 1,
maxLength: 255
),
'city' => new Schema(
type: Type::STRING,
minLength: 1,
maxLength: 255
),
'zip' => new Schema(
type: Type::STRING,
pattern: '^[0-9]{5}$'
)
],
required: ['street', 'city', 'zip']
),
),
'email' => new Schema(
type: Type::STRING,
format: Format::EMAIL
),
'phone' => new Schema(
type: Type::STRING,
pattern: '^\+[0-9]{1,3}\.[0-9]{1,14}$'
),
'is_active' => new Schema(
type: Type::BOOLEAN,
default: true,
),
]
);
// Convert the schema to JSON
$json = $schema->toJson();
// Convert the schema to an object
$obj = $schema->toObject();
```
### Transform
Almost all properties listed on the [JSON Schema reference](https://json-schema.org/understanding-json-schema/reference) are supported, but if some properties are missing in the current version of the library or you want to add your own, you can use the transform method.
It is called when the `Schema` class is transformed into the `stdClass` class to generate JSON from it:
```php
use O21\JsonSchema\Schema;
$schema = new Schema(
transform: function (stdClass $schema): void {
$schema->foo = 'bar';
}
);
```
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Format;
use O21\JsonSchema\Enums\Type;
use O21\JsonSchema\Schema;
trait AllTypesConstruct
{
protected array $callSetAliases = [
'schema' => 'dialect',
'properties' => 'addProps',
'patternProperties' => 'patternProps',
'additionalProperties' => 'additionalProps',
'unevaluatedProperties' => 'unevaluatedProps',
'minimum' => 'min',
'maximum' => 'max',
'exclusiveMinimum' => 'exclusiveMin',
'exclusiveMaximum' => 'exclusiveMax',
];
public function __construct(
// Dialect
?string $schema = null,
protected ?Type $type = null,
// GenericKeywords
?string $title = null,
?string $description = null,
mixed $default = null,
?array $examples = null,
?bool $deprecated = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?string $comment = null,
?array $enum = null,
mixed $const = null,
// Composition
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
?Schema $not = null,
// ArrayTypeSchema
?array $prefixItems = null,
Schema|bool|null $items = null,
?Schema $contains = null,
?int $minContains = null,
?int $maxContains = null,
?int $minItems = null,
?int $maxItems = null,
?bool $uniqueItems = null,
// StringTypeSchema
?int $minLength = null,
?int $maxLength = null,
?Format $format = null,
?string $pattern = null,
// NumericTypeSchema
?int $minimum = null,
?int $maximum = null,
int|bool|null $exclusiveMinimum = null,
int|bool|null $exclusiveMaximum = null,
?int $multipleOf = null,
// ObjectTypeSchema
?array $properties = null,
?array $required = null,
?array $patternProperties = null,
array|bool|null $additionalProperties = null,
array|bool|null $unevaluatedProperties = null,
?Schema $propertyNames = null,
?int $minProperties = null,
?int $maxProperties = null,
// Conditions
?array $dependentRequired = null,
?array $dependentSchemas = null,
?array $if = null,
?array $then = null,
?array $else = null,
// Media
?string $contentMediaType = null,
?string $contentEncoding = null,
?Schema $contentSchema = null,
// Bundling
protected ?string $id = null,
protected ?string $anchor = null,
protected ?string $ref = null,
protected ?array $defs = null,
) {
$args = get_defined_vars();
unset($args['this']);
$args = array_filter($args, static fn($v) => $v !== null);
foreach ($args as $key => $value) {
$this->callSet($key, $value);
}
}
protected function callSet(string $key, mixed $value): void
{
if ($method = $this->callSetMethod($key)) {
$this->$method($value);
}
}
protected function callSetMethod(string $key): ?string
{
$method = $this->callSetAliases[$key] ?? $key;
return method_exists($this, $method) ? $method : null;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
use O21\JsonSchema\Enums\Type;
trait ArrayTypeSchema
{
protected ?array $_prefixItems = null;
protected Schema|bool|null $_items = null;
protected ?Schema $_contains = null;
protected ?int $_minContains = null;
protected ?int $_maxContains = null;
protected ?int $_minItems = null;
protected ?int $_maxItems = null;
protected ?bool $_uniqueItems = null;
protected function arrayObject(): \stdClass
{
$obj = $this->defaultObject();
if ($this->_prefixItems !== null) {
$obj->prefixItems = $this->mapToObject($this->_prefixItems);
}
if ($this->_items !== null) {
$obj->items = is_bool($this->_items)
? $this->_items
: $this->_items->toObject();
}
if ($this->_contains !== null) {
$obj->contains = $this->_contains->toObject();
}
if ($this->_minContains !== null) {
$obj->minContains = $this->_minContains;
}
if ($this->_maxContains !== null) {
$obj->maxContains = $this->_maxContains;
}
if ($this->_minItems !== null) {
$obj->minItems = $this->_minItems;
}
if ($this->_maxItems !== null) {
$obj->maxItems = $this->_maxItems;
}
if ($this->_uniqueItems !== null) {
$obj->uniqueItems = $this->_uniqueItems;
}
return $obj;
}
public function prefixItems(array $items): self
{
$this->assertType(Type::ARRAY);
$this->assertArrayItemsInstanceOf($items, Schema::class);
$this->_prefixItems = $items;
return $this;
}
public function items(Schema|bool $items): self
{
$this->assertType(Type::ARRAY);
$this->_items = $items;
return $this;
}
public function contains(Schema $contains): self
{
$this->assertType(Type::ARRAY);
$this->_contains = $contains;
return $this;
}
public function minContains(int $minContains): self
{
$this->assertType(Type::ARRAY);
$this->_minContains = $minContains;
return $this;
}
public function maxContains(int $maxContains): self
{
$this->assertType(Type::ARRAY);
$this->_maxContains = $maxContains;
return $this;
}
public function minItems(int $minItems): self
{
$this->assertType(Type::ARRAY);
$this->_minItems = $minItems;
return $this;
}
public function maxItems(int $maxItems): self
{
$this->assertType(Type::ARRAY);
$this->_maxItems = $maxItems;
return $this;
}
public function uniqueItems(bool $uniqueItems = true): self
{
$this->assertType(Type::ARRAY);
$this->_uniqueItems = $uniqueItems;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Bundling
{
protected ?string $_id = null;
protected ?string $_anchor = null;
protected ?string $_ref = null;
protected ?array $_defs = null;
public function id(string $id): self
{
$this->_id = $id;
return $this;
}
public function anchor(string $anchor): self
{
$this->_anchor = $anchor;
return $this;
}
public function ref(string $ref): self
{
$this->_ref = $ref;
return $this;
}
public function defs(array $defs): self
{
$this->assertArrayItemsInstanceOf($defs, Schema::class);
$this->_defs = $defs;
return $this;
}
protected function applyBundling(\stdClass $schema): void
{
if ($this->_id) {
$schema->{'$id'} = $this->_id;
}
if ($this->_anchor) {
$schema->{'$anchor'} = $this->_anchor;
}
if ($this->_ref) {
$schema->{'$ref'} = $this->_ref;
}
if ($this->_defs) {
$schema->{'$defs'} = $this->arrayToObject($this->_defs);
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Composition
{
protected array $_allOf = [];
protected array $_anyOf = [];
protected array $_oneOf = [];
protected ?Schema $_not = null;
public function allOf(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_allOf = $schemas;
return $this;
}
public function anyOf(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_anyOf = $schemas;
return $this;
}
public function oneOf(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_oneOf = $schemas;
return $this;
}
public function not(Schema $schema): self
{
$this->_not = $schema;
return $this;
}
protected function applyComposition(\stdClass $schema): void
{
if (! empty($this->_allOf)) {
$schema->allOf = $this->_allOf;
}
if (! empty($this->_anyOf)) {
$schema->anyOf = $this->_anyOf;
}
if (! empty($this->_oneOf)) {
$schema->oneOf = $this->_oneOf;
}
if (! is_null($this->_not)) {
$schema->not = $this->_not->toObject();
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Conditions
{
protected array $_dependentRequired = [];
protected array $_dependentSchemas = [];
protected array $_if = [];
protected array $_then = [];
protected array $_else = [];
public function dependentRequired(array|string $key, ?array $required = null): self
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->_dependentRequired[$k] = $v;
}
return $this;
}
$this->_dependentRequired[$key] = $required;
return $this;
}
public function dependentSchemas(array|string $key, ?array $schemas = null): self
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->_dependentSchemas[$k] = $v;
}
return $this;
}
$this->_dependentSchemas[$key] = $schemas;
return $this;
}
public function if(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_if = $schemas;
return $this;
}
public function then(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_then = $schemas;
return $this;
}
public function else(array $schemas): self
{
$this->assertArrayItemsInstanceOf($schemas, Schema::class);
$this->_else = $schemas;
return $this;
}
protected function applyConditions(\stdClass $schema): void
{
if (! empty($this->_dependentRequired)) {
$schema->dependentRequired = $this->arrayToObject($this->_dependentRequired);
}
if (! empty($this->_dependentSchemas)) {
$schema->dependentSchemas = $this->arrayToObject($this->_dependentSchemas);
}
if (! empty($this->_if)) {
$schema->if = $this->mapToObject($this->_if);
}
if (! empty($this->_then)) {
$schema->then = $this->mapToObject($this->_then);
}
if (! empty($this->_else)) {
$schema->else = $this->mapToObject($this->_else);
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait Dialect
{
protected ?string $_dialect = null;
public function dialect(string $dialect): self
{
$this->_dialect = $dialect;
return $this;
}
protected function applyDialect(\stdClass $schema): void
{
if ($this->_dialect !== null) {
$schema->{'$schema'} = $this->_dialect;
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait GenericKeywords
{
protected ?string $_title = null;
protected ?string $_description = null;
protected mixed $_default = null;
protected ?array $_examples = null;
protected ?bool $_deprecated = null;
protected ?bool $_readOnly = null;
protected ?bool $_writeOnly = null;
protected ?string $_comment = null;
protected ?array $_enum = null;
protected mixed $_const = null;
public function title(string $title): static
{
$this->_title = $title;
return $this;
}
public function description(string $description): static
{
$this->_description = $description;
return $this;
}
public function default(mixed $default): static
{
$this->_default = $default;
return $this;
}
public function examples(array $examples): static
{
$this->_examples = $examples;
return $this;
}
public function deprecated(bool $deprecated = true): static
{
$this->_deprecated = $deprecated;
return $this;
}
public function readOnly(bool $readOnly = true): static
{
$this->_readOnly = $readOnly;
return $this;
}
public function writeOnly(bool $writeOnly = true): static
{
$this->_writeOnly = $writeOnly;
return $this;
}
public function comment(string $comment): static
{
$this->_comment = $comment;
return $this;
}
public function enum(array $enum): static
{
$this->_enum = $enum;
return $this;
}
public function const(mixed $const): static
{
$this->_const = $const;
return $this;
}
protected function applyGenericKeywords(\stdClass $schema): void
{
if ($this->_title !== null) {
$schema->title = $this->_title;
}
if ($this->_description !== null) {
$schema->description = $this->_description;
}
if ($this->_default !== null) {
$schema->default = $this->_default;
}
if ($this->_examples !== null) {
$schema->examples = $this->_examples;
}
if ($this->_deprecated !== null) {
$schema->deprecated = $this->_deprecated;
}
if ($this->_readOnly !== null) {
$schema->readOnly = $this->_readOnly;
}
if ($this->_writeOnly !== null) {
$schema->writeOnly = $this->_writeOnly;
}
if ($this->_comment !== null) {
$schema->{'$comment'} = $this->_comment;
}
if ($this->_enum !== null) {
$schema->enum = $this->_enum;
}
if ($this->_const !== null) {
$schema->const = $this->_const;
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait Media
{
protected ?string $_contentEncoding = null;
protected ?string $_contentMediaType = null;
protected ?Schema $_contentSchema = null;
public function contentEncoding(string $contentEncoding): self
{
$this->_contentEncoding = $contentEncoding;
return $this;
}
public function contentMediaType(string $contentMediaType): self
{
$this->_contentMediaType = $contentMediaType;
return $this;
}
public function contentSchema(Schema $contentSchema): self
{
$this->_contentSchema = $contentSchema;
return $this;
}
protected function applyMedia(\stdClass $schema): void
{
if ($this->_contentEncoding !== null) {
$schema->contentEncoding = $this->_contentEncoding;
}
if ($this->_contentMediaType !== null) {
$schema->contentMediaType = $this->_contentMediaType;
}
if ($this->_contentSchema !== null) {
$schema->contentSchema = $this->_contentSchema->toObject();
}
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Type;
trait NumericTypeSchema
{
protected int|float|null $minimum = null;
protected int|float|null $maximum = null;
protected int|float|bool|null $exclusiveMinimum = null;
protected int|float|bool|null $exclusiveMaximum = null;
protected int|float|null $_multipleOf = null;
protected function numericObject(): \stdClass
{
$obj = $this->defaultObject();
$props = $this->filterArray([
'minimum' => $this->minimum,
'maximum' => $this->maximum,
'exclusiveMinimum' => $this->exclusiveMinimum,
'exclusiveMaximum' => $this->exclusiveMaximum,
'multipleOf' => $this->_multipleOf,
]);
$this->applyArrayProps($obj, $props);
return $obj;
}
public function min(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->minimum = $value;
return $this;
}
public function max(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->maximum = $value;
return $this;
}
public function exclusiveMin(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->exclusiveMinimum = $value;
return $this;
}
public function exclusiveMax(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->exclusiveMaximum = $value;
return $this;
}
public function multipleOf(int|float $value): self
{
$this->assertType(Type::NUMBER, Type::INTEGER);
$this->_multipleOf = $value;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
use O21\JsonSchema\Enums\Type;
trait ObjectTypeSchema
{
protected array $_required = [];
protected array $properties = [];
protected array $patternProperties = [];
protected array|bool $additionalProperties = [];
protected array|bool $unevaluatedProperties = [];
protected ?Schema $_propertyNames = null;
protected ?int $_minProperties = null;
protected ?int $_maxProperties = null;
protected function objectObject(): \stdClass
{
$obj = $this->defaultObject();
if (! empty($this->properties)) {
$obj->properties = $this->arrayToObject($this->properties);
}
if (! empty($this->_required)) {
$obj->required = $this->_required;
}
if (! empty($this->patternProperties)) {
$obj->patternProperties = $this->arrayToObject($this->patternProperties);
}
if (! is_array($this->additionalProperties)
|| ! empty($this->additionalProperties)
) {
$obj->additionalProperties = $this->additionalProperties;
}
if (! is_array($this->unevaluatedProperties)
|| ! empty($this->unevaluatedProperties)
) {
$obj->unevaluatedProperties = $this->unevaluatedProperties;
}
if ($this->_propertyNames !== null) {
$obj->propertyNames = $this->_propertyNames->toObject();
}
if ($this->_minProperties !== null) {
$obj->minProperties = $this->_minProperties;
}
if ($this->_maxProperties !== null) {
$obj->maxProperties = $this->_maxProperties;
}
return $obj;
}
public function addProps(array $props): void
{
$this->assertArrayItemsInstanceOf($props, Schema::class);
foreach ($props as $key => $schema) {
$this->addProp($key, $schema);
}
}
public function addProp(
string $key,
Schema $schema,
): self {
$this->assertType(Type::OBJECT);
$this->properties[$key] = $schema;
return $this;
}
public function removeProps(array|string ...$keys): void
{
if (count($keys) === 1 && is_array($keys[0])) {
$keys = $keys[0];
}
foreach ($keys as $key) {
$this->removeProp($key);
}
}
public function removeProp(string $key): self
{
$this->assertType(Type::OBJECT);
unset($this->properties[$key]);
return $this;
}
public function required(array|string ...$keys): self
{
$this->assertType(Type::OBJECT);
if (count($keys) === 1 && is_array($keys[0])) {
$keys = $keys[0];
}
$this->_required = array_merge($this->_required, $keys);
return $this;
}
public function notRequired(array|string ...$keys): self
{
$this->assertType(Type::OBJECT);
if (count($keys) === 1 && is_array($keys[0])) {
$keys = $keys[0];
}
$this->_required = array_diff($this->_required, $keys);
return $this;
}
public function minProperties(int $min): self
{
$this->assertType(Type::OBJECT);
$this->_minProperties = $min;
return $this;
}
public function maxProperties(int $max): self
{
$this->assertType(Type::OBJECT);
$this->_maxProperties = $max;
return $this;
}
public function additionalProps(bool|Schema $props): self
{
$this->assertType(Type::OBJECT);
$this->additionalProperties = $props;
return $this;
}
public function unevaluatedProps(bool|Schema $props): self
{
$this->assertType(Type::OBJECT);
$this->unevaluatedProperties = $props;
return $this;
}
public function patternProps(array $props): self
{
$this->assertType(Type::OBJECT);
$this->assertArrayItemsInstanceOf($props, Schema::class);
$this->patternProperties = $props;
return $this;
}
public function propertyNames(Schema $schema): self
{
$this->assertType(Type::OBJECT);
$this->_propertyNames = $schema;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait PropGetter
{
public function getProp(string $prop): mixed
{
if (property_exists($this, $prop)) {
return $this->$prop;
}
if (! str_starts_with($prop, '_')) {
return $this->getProp('_'.$prop);
}
return null;
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Schema;
trait StdBuilding
{
protected function arrayToObject(array $array): \stdClass
{
$obj = new \stdClass();
foreach ($array as $key => $value) {
$obj->{$key} = is_a($value, Schema::class)
? $value->toObject()
: $value;
}
return $obj;
}
protected function applyArrayProps(\stdClass $object, array $props): void
{
foreach ($props as $key => $value) {
$object->{$key} = $value;
}
}
protected function mapToObject(array $array): array
{
return array_map(static fn($value) => $value->toObject(), $array);
}
protected function filterArray(array $array): array
{
return array_filter($array, static fn($value) => ! empty($value));
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Format;
use O21\JsonSchema\Enums\Type;
trait StringTypeSchema
{
protected ?int $_minLength = null;
protected ?int $_maxLength = null;
protected ?Format $_format = null;
protected ?string $_pattern = null;
public function stringObject(): \stdClass
{
$obj = $this->defaultObject();
if ($this->_minLength !== null) {
$obj->minLength = $this->_minLength;
}
if ($this->_maxLength !== null) {
$obj->maxLength = $this->_maxLength;
}
if ($this->_format !== null) {
$obj->format = $this->_format->value;
}
if ($this->_pattern !== null) {
$obj->pattern = $this->_pattern;
}
return $obj;
}
public function minLength(int $value): self
{
$this->assertType(Type::STRING);
$this->_minLength = $value;
return $this;
}
public function maxLength(int $value): self
{
$this->assertType(Type::STRING);
$this->_maxLength = $value;
return $this;
}
public function format(Format $value): self
{
$this->assertType(Type::STRING);
$this->_format = $value;
return $this;
}
public function pattern(string $value): self
{
$this->assertType(Type::STRING);
$this->_pattern = $value;
return $this;
}
}
<?php
namespace O21\JsonSchema\Concerns;
trait Transformation
{
protected $_transform = null;
/**
* Apply transformation to the Schema when calling toObject
*
* @param callable $transform
* @return \O21\JsonSchema\Concerns\Transformation|\O21\JsonSchema\Schema
*/
public function transform(callable $transform): self
{
$this->_transform = $transform;
return $this;
}
protected function applyTransform(\stdClass $obj): void
{
if ($this->_transform === null) {
return;
}
call_user_func($this->_transform, $obj);
}
}
<?php
namespace O21\JsonSchema\Concerns;
use O21\JsonSchema\Enums\Type;
trait Validation
{
protected function assertArrayItemsInstanceOf(array $items, string $class): void
{
foreach ($items as $item) {
if (! is_a($item, $class)) {
throw new \InvalidArgumentException(
'Items must be an instance of '.$class
);
}
}
}
protected function assertType(Type ...$type): void
{
if (! in_array($this->type, $type, true)) {
$types = array_map(static fn($t) => $t->value, $type);
throw new \InvalidArgumentException(
'This method can be called only for ' . implode(', ', $types) . ' type'
);
}
}
}
<?php
namespace O21\JsonSchema\Enums;
enum Format: string
{
case DATE = 'date';
case TIME = 'time';
case DATE_TIME = 'date-time';
case EMAIL = 'email';
case IDN_EMAIL = 'idn-email';
case HOSTNAME = 'hostname';
case IDN_HOSTNAME = 'idn-hostname';
case IPV4 = 'ipv4';
case IPV6 = 'ipv6';
case UUID = 'uuid';
case URI = 'uri';
case URI_REFERENCE = 'uri-reference';
case URI_TEMPLATE = 'uri-template';
case IRI = 'iri';
case IRI_REFERENCE = 'iri-reference';
case JSON_POINTER = 'json-pointer';
case RELATIVE_JSON_POINTER = 'relative-json-pointer';
case REGEX = 'regex';
}
<?php
namespace O21\JsonSchema\Enums;
enum Type: string
{
case OBJECT = 'object';
case ARRAY = 'array';
case STRING = 'string';
case NUMBER = 'number';
case INTEGER = 'integer';
case BOOLEAN = 'boolean';
case NULL = 'null';
}
<?php
namespace O21\JsonSchema;
use O21\JsonSchema\Concerns\AllTypesConstruct;
use O21\JsonSchema\Concerns\ArrayTypeSchema;
use O21\JsonSchema\Concerns\Bundling;
use O21\JsonSchema\Concerns\Composition;
use O21\JsonSchema\Concerns\Conditions;
use O21\JsonSchema\Concerns\Dialect;
use O21\JsonSchema\Concerns\GenericKeywords;
use O21\JsonSchema\Concerns\Media;
use O21\JsonSchema\Concerns\NumericTypeSchema;
use O21\JsonSchema\Concerns\ObjectTypeSchema;
use O21\JsonSchema\Concerns\PropGetter;
use O21\JsonSchema\Concerns\StdBuilding;
use O21\JsonSchema\Concerns\StringTypeSchema;
use O21\JsonSchema\Concerns\Transformation;
use O21\JsonSchema\Concerns\Validation;
use O21\JsonSchema\Enums\Type;
/**
* Class Schema
*
* @package O21\JsonSchema
* @see https://json-schema.org/understanding-json-schema/reference
*/
class Schema
{
use AllTypesConstruct;
use ArrayTypeSchema;
use StringTypeSchema;
use ObjectTypeSchema;
use NumericTypeSchema;
use Bundling;
use Dialect;
use GenericKeywords;
use Composition;
use Conditions;
use Media;
use Validation;
use Transformation;
use StdBuilding;
use PropGetter;
/**
* @throws \JsonException
*/
public function toJson(): string
{
return json_encode($this->toObject(), JSON_THROW_ON_ERROR);
}
public function toObject(): \stdClass
{
$obj = match ($this->type) {
Type::ARRAY => $this->arrayObject(),
Type::STRING => $this->stringObject(),
Type::OBJECT => $this->objectObject(),
Type::NUMBER, Type::INTEGER => $this->numericObject(),
default => $this->defaultObject(),
};
$this->applyDialect($obj);
$this->applyComposition($obj);
$this->applyGenericKeywords($obj);
$this->applyMedia($obj);
$this->applyConditions($obj);
$this->applyBundling($obj);
$this->applyTransform($obj);
return $obj;
}
protected function defaultObject(): \stdClass
{
$obj = new \stdClass();
if (! empty($this->type)) {
$obj->type = $this->type->value;
}
return $obj;
}
}