├── LICENSE
├── composer.json
├── psalm-baseline.xml
└── src
├── AbstractDefinition.php
├── Attribute
└── Field.php
├── Exception
├── DefinitionException.php
├── GeneratorException.php
└── InvalidTypeException.php
├── Generator.php
├── GeneratorInterface.php
├── Parser
├── ClassParser.php
├── ClassParserInterface.php
├── Parser.php
├── ParserInterface.php
├── Property.php
├── PropertyInterface.php
├── Type.php
└── TypeInterface.php
├── Schema.php
└── Schema
├── Definition.php
├── Format.php
├── Property.php
├── PropertyOption.php
├── PropertyOptions.php
├── Reference.php
└── Type.php
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Spiral Scout
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spiral/json-schema-generator",
3 | "description": "Provides the ability to generate JSON schemas from Data Transfer Object (DTO) classes",
4 | "keywords": [],
5 | "homepage": "https://github.com/spiral/json-schema-generator",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Anton Titov (wolfy-j)",
10 | "email": "wolfy-j@spiralscout.com"
11 | },
12 | {
13 | "name": "Pavel Buchnev (butschster)",
14 | "email": "pavel.buchnev@spiralscout.com"
15 | },
16 | {
17 | "name": "Aleksei Gagarin (roxblnfk)",
18 | "email": "alexey.gagarin@spiralscout.com"
19 | },
20 | {
21 | "name": "Maksim Smakouz (msmakouz)",
22 | "email": "maksim.smakouz@spiralscout.com"
23 | }
24 | ],
25 | "require": {
26 | "php": ">=8.1",
27 | "symfony/property-info": "^6.4.18 || ^7.2",
28 | "phpstan/phpdoc-parser": "^1.33 | ^2.1",
29 | "phpdocumentor/reflection-docblock": "^5.3"
30 | },
31 | "require-dev": {
32 | "phpunit/phpunit": "^10.5.45",
33 | "spiral/code-style": "^2.2.2",
34 | "vimeo/psalm": "^5.26.1 || ^6.10"
35 | },
36 | "autoload": {
37 | "psr-4": {
38 | "Spiral\\JsonSchemaGenerator\\": "src"
39 | }
40 | },
41 | "autoload-dev": {
42 | "psr-4": {
43 | "Spiral\\JsonSchemaGenerator\\Tests\\": "tests"
44 | }
45 | },
46 | "scripts": {
47 | "cs:fix": "php-cs-fixer fix -v",
48 | "test": "phpunit",
49 | "psalm": "psalm",
50 | "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml"
51 | },
52 | "config": {
53 | "sort-packages": true
54 | },
55 | "minimum-stability": "dev",
56 | "prefer-stable": true
57 | }
58 |
--------------------------------------------------------------------------------
/psalm-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/AbstractDefinition.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected array $properties = [];
15 |
16 | /**
17 | * @param non-empty-string $name
18 | */
19 | public function addProperty(string $name, Property $property): self
20 | {
21 | $this->properties[$name] = $property;
22 |
23 | return $this;
24 | }
25 |
26 | protected function renderProperties(array $schema): array
27 | {
28 | if ($this->properties === []) {
29 | return $schema;
30 | }
31 |
32 | $schema['properties'] = [];
33 |
34 | // Building properties
35 | $required = [];
36 | foreach ($this->properties as $name => $property) {
37 | $schema['properties'][$name] = $property->jsonSerialize();
38 |
39 | if ($property->required) {
40 | $required[] = $name;
41 | }
42 | }
43 |
44 | if ($required !== []) {
45 | $schema['required'] = $required;
46 | }
47 |
48 | return $schema;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Attribute/Field.php:
--------------------------------------------------------------------------------
1 | parser->parse($class);
30 |
31 | // check cached
32 | if (isset($this->cache[$class->getName()])) {
33 | return $this->cache[$class->getName()];
34 | }
35 |
36 | $schema = new Schema();
37 |
38 | $dependencies = [];
39 | // Generating properties
40 | foreach ($class->getProperties() as $property) {
41 | $psc = $this->generateProperty($property);
42 | if ($psc === null) {
43 | continue;
44 | }
45 |
46 | // does it refer to any other classes
47 | $dependencies = [...$dependencies, ...$psc->getDependencies()];
48 |
49 | $schema->addProperty($property->getName(), $psc);
50 | }
51 |
52 | // Generating dependencies
53 | $dependencies = \array_unique($dependencies);
54 | $rollingDependencies = [];
55 | $doneDependencies = [];
56 |
57 | do {
58 | foreach ($dependencies as $dependency) {
59 | $dependency = $this->parser->parse($dependency);
60 | $definition = $this->generateDefinition($dependency, $rollingDependencies);
61 | if ($definition === null) {
62 | continue;
63 | }
64 |
65 | $schema->addDefinition($dependency->getShortName(), $definition);
66 | }
67 |
68 | $doneDependencies = [...$doneDependencies, ...$dependencies];
69 | $rollingDependencies = \array_diff($rollingDependencies, $doneDependencies);
70 | if ($rollingDependencies === []) {
71 | break;
72 | }
73 |
74 | $dependencies = $rollingDependencies;
75 | } while (true);
76 |
77 | // caching
78 | $this->cache[$class->getName()] = $schema;
79 |
80 | return $schema;
81 | }
82 |
83 | protected function generateDefinition(ClassParserInterface $class, array &$dependencies = []): ?Definition
84 | {
85 | $properties = [];
86 | if ($class->isEnum()) {
87 | return new Definition(
88 | type: $class->getName(),
89 | options: $class->getEnumValues(),
90 | title: $class->getShortName(),
91 | );
92 | }
93 |
94 | // class properties
95 | foreach ($class->getProperties() as $property) {
96 | $psc = $this->generateProperty($property);
97 | if ($psc === null) {
98 | continue;
99 | }
100 |
101 | $dependencies = [...$dependencies, ...$psc->getDependencies()];
102 | $properties[$property->getName()] = $psc;
103 | }
104 |
105 | return new Definition(type: $class->getName(), title: $class->getShortName(), properties: $properties);
106 | }
107 |
108 | protected function generateProperty(PropertyInterface $property): ?Property
109 | {
110 | // Looking for Field attribute
111 | $title = '';
112 | $description = '';
113 | $default = null;
114 | $format = null;
115 |
116 | $attribute = $property->findAttribute(Field::class);
117 | if ($attribute !== null) {
118 | $title = $attribute->title;
119 | $description = $attribute->description;
120 | $default = $attribute->default;
121 | $format = $attribute->format;
122 | }
123 |
124 | if ($default === null && $property->hasDefaultValue()) {
125 | $default = $property->getDefaultValue();
126 | }
127 |
128 | $type = $property->getType();
129 |
130 | $options = [];
131 | if ($property->isCollection()) {
132 | $options = \array_map(
133 | static fn(TypeInterface $type) => $type->getName(),
134 | $property->getCollectionValueTypes(),
135 | );
136 | }
137 |
138 | $required = $default === null && !$type->allowsNull();
139 | if ($type->isBuiltin()) {
140 | return new Property($type->getName(), $options, $title, $description, $required, $default, $format);
141 | }
142 |
143 | // Class or enum
144 | $class = $type->getName();
145 |
146 | return \is_string($class) && \class_exists($class)
147 | ? new Property($class, [], $title, $description, $required, $default, $format)
148 | : null;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/GeneratorInterface.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | private array $constructorParameters = [];
26 |
27 | private readonly PropertyInfoExtractorInterface $propertyInfo;
28 |
29 | /**
30 | * @param \ReflectionClass|class-string $class
31 | */
32 | public function __construct(\ReflectionClass|string $class)
33 | {
34 | if (\is_string($class)) {
35 | try {
36 | $class = new \ReflectionClass($class);
37 | } catch (\ReflectionException $e) {
38 | throw new GeneratorException($e->getMessage(), $e->getCode(), $e);
39 | }
40 | }
41 |
42 | $this->class = $class;
43 | $this->propertyInfo = $this->createPropertyInfo();
44 |
45 | if ($this->class->hasMethod('__construct')) {
46 | $constructor = $this->class->getMethod('__construct');
47 | foreach ($constructor->getParameters() as $parameter) {
48 | $this->constructorParameters[$parameter->getName()] = $parameter;
49 | }
50 | }
51 | }
52 |
53 | /**
54 | * @return class-string
55 | */
56 | public function getName(): string
57 | {
58 | return $this->class->getName();
59 | }
60 |
61 | /**
62 | * @return non-empty-string
63 | */
64 | public function getShortName(): string
65 | {
66 | return $this->class->getShortName();
67 | }
68 |
69 | /**
70 | * @return array
71 | */
72 | public function getProperties(): array
73 | {
74 | $properties = [];
75 | foreach ($this->class->getProperties() as $property) {
76 | // skipping private, protected, static properties, properties without type
77 | if ($property->isPrivate() || $property->isProtected() || $property->isStatic() || !$property->hasType()) {
78 | continue;
79 | }
80 |
81 | /**
82 | * @var \ReflectionNamedType|null $type
83 | */
84 | $type = $property->getType();
85 | if (!$type instanceof \ReflectionNamedType) {
86 | continue;
87 | }
88 |
89 | $properties[] = new Property(
90 | property: $property,
91 | type: new Type(name: $type->getName(), builtin: $type->isBuiltin(), nullable: $type->allowsNull()),
92 | hasDefaultValue: $this->hasPropertyDefaultValue($property),
93 | defaultValue: $this->getPropertyDefaultValue($property),
94 | collectionValueTypes: $this->getPropertyCollectionTypes($property->getName()),
95 | );
96 | }
97 |
98 | return $properties;
99 | }
100 |
101 | public function isEnum(): bool
102 | {
103 | return $this->class->isEnum();
104 | }
105 |
106 | public function getEnumValues(): array
107 | {
108 | if (!$this->isEnum()) {
109 | throw new GeneratorException(\sprintf('Class `%s` is not an enum.', $this->class->getName()));
110 | }
111 |
112 | $values = [];
113 | foreach ($this->class->getReflectionConstants() as $constant) {
114 | $value = $constant->getValue();
115 | \assert($value instanceof \BackedEnum);
116 |
117 | $values[] = $value->value;
118 | }
119 |
120 | return $values;
121 | }
122 |
123 | /**
124 | * @param non-empty-string $property
125 | *
126 | * @return array
127 | */
128 | private function getPropertyCollectionTypes(string $property): array
129 | {
130 | $types = $this->propertyInfo->getTypes($this->class->getName(), $property);
131 |
132 | $collectionTypes = [];
133 | foreach ($types ?? [] as $type) {
134 | if ($type->isCollection()) {
135 | $collectionTypes = [...$type->getCollectionValueTypes(), ...$collectionTypes];
136 | }
137 | }
138 |
139 | $result = [];
140 | foreach ($collectionTypes as $type) {
141 | /**
142 | * @var non-empty-string $name
143 | */
144 | $name = $type->getBuiltinType() === SchemaType::Object->value
145 | ? $type->getClassName()
146 | : $type->getBuiltinType();
147 |
148 | $result[] = new Type(
149 | name: $name,
150 | builtin: $type->getBuiltinType() !== SchemaType::Object->value,
151 | nullable: $type->isNullable(),
152 | );
153 | }
154 |
155 | return $result;
156 | }
157 |
158 | private function hasPropertyDefaultValue(\ReflectionProperty $property): bool
159 | {
160 | $parameter = $this->constructorParameters[$property->getName()] ?? null;
161 |
162 | return $property->hasDefaultValue() || ($parameter !== null && $parameter->isDefaultValueAvailable());
163 | }
164 |
165 | private function getPropertyDefaultValue(\ReflectionProperty $property): mixed
166 | {
167 | if ($property->hasDefaultValue()) {
168 | $default = $property->getDefaultValue();
169 | }
170 |
171 | $parameter = $this->constructorParameters[$property->getName()] ?? null;
172 | if ($parameter !== null && $property->isPromoted() && $parameter->isDefaultValueAvailable()) {
173 | $default = $parameter->getDefaultValue();
174 | }
175 |
176 | return $default ?? null;
177 | }
178 |
179 | private function createPropertyInfo(): PropertyInfoExtractorInterface
180 | {
181 | return new PropertyInfoExtractor(typeExtractors: [
182 | new PhpStanExtractor(),
183 | new PhpDocExtractor(),
184 | new ReflectionExtractor(),
185 | ]);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/Parser/ClassParserInterface.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | public function getProperties(): array;
23 |
24 | public function isEnum(): bool;
25 |
26 | public function getEnumValues(): array;
27 | }
28 |
--------------------------------------------------------------------------------
/src/Parser/Parser.php:
--------------------------------------------------------------------------------
1 | property->getName();
28 | }
29 |
30 | /**
31 | * @template T
32 | *
33 | * @param class-string $name The class name of the attribute.
34 | *
35 | * @return T|null The attribute or {@see null}, if the requested attribute does not exist.
36 | */
37 | public function findAttribute(string $name): ?object
38 | {
39 | $name = $this->property->getAttributes($name);
40 | if ($name !== []) {
41 | return $name[0]->newInstance();
42 | }
43 |
44 | return null;
45 | }
46 |
47 | public function hasDefaultValue(): bool
48 | {
49 | return $this->hasDefaultValue;
50 | }
51 |
52 | public function getDefaultValue(): mixed
53 | {
54 | return $this->defaultValue;
55 | }
56 |
57 | public function isCollection(): bool
58 | {
59 | $type = $this->type->getName();
60 | if (!$type instanceof SchemaType) {
61 | return false;
62 | }
63 |
64 | return $type->value === SchemaType::Array->value;
65 | }
66 |
67 | /**
68 | * @return array
69 | */
70 | public function getCollectionValueTypes(): array
71 | {
72 | return $this->collectionValueTypes;
73 | }
74 |
75 | public function getType(): TypeInterface
76 | {
77 | return $this->type;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Parser/PropertyInterface.php:
--------------------------------------------------------------------------------
1 | $name The class name of the attribute.
18 | *
19 | * @return T|null The attribute or {@see null}, if the requested attribute does not exist.
20 | */
21 | public function findAttribute(string $name): ?object;
22 |
23 | public function hasDefaultValue(): bool;
24 |
25 | public function getDefaultValue(): mixed;
26 |
27 | public function isCollection(): bool;
28 |
29 | /**
30 | * @return array
31 | */
32 | public function getCollectionValueTypes(): array;
33 |
34 | public function getType(): TypeInterface;
35 | }
36 |
--------------------------------------------------------------------------------
/src/Parser/Type.php:
--------------------------------------------------------------------------------
1 | name = $this->builtin ? SchemaType::fromBuiltIn($name) : $name;
29 | }
30 |
31 | /**
32 | * @return class-string|SchemaType
33 | */
34 | public function getName(): string|SchemaType
35 | {
36 | return $this->name;
37 | }
38 |
39 | public function isBuiltin(): bool
40 | {
41 | return $this->builtin;
42 | }
43 |
44 | public function allowsNull(): bool
45 | {
46 | return $this->nullable;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Parser/TypeInterface.php:
--------------------------------------------------------------------------------
1 | definitions[$name] = $definition;
16 | return $this;
17 | }
18 |
19 | public function jsonSerialize(): array
20 | {
21 | $schema = $this->renderProperties([]);
22 |
23 | if ($this->definitions !== []) {
24 | $schema['definitions'] = [];
25 |
26 | foreach ($this->definitions as $name => $definition) {
27 | $schema['definitions'][$name] = $definition->jsonSerialize();
28 | }
29 | }
30 |
31 | return $schema;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Schema/Definition.php:
--------------------------------------------------------------------------------
1 | $property) {
23 | if (!$property instanceof Property) {
24 | throw new DefinitionException(\sprintf(
25 | 'Property `%s` is not an instance of `%s`.',
26 | // type name or class name
27 | \is_object($property) ? \get_class($property) : \gettype($property),
28 | Property::class,
29 | ));
30 | }
31 |
32 | $this->addProperty($name, $property);
33 | }
34 | }
35 |
36 | public function jsonSerialize(): array
37 | {
38 | $schema = [];
39 | if ($this->title !== '') {
40 | $schema['title'] = $this->title;
41 | }
42 |
43 | if ($this->description !== '') {
44 | $schema['description'] = $this->description;
45 | }
46 |
47 | if ($this->type instanceof Type) {
48 | $schema['type'] = $this->type->value;
49 | } else {
50 | $schema = $this->renderType($schema);
51 | }
52 |
53 | return $this->renderProperties($schema);
54 | }
55 |
56 | private function renderType(array $schema): array
57 | {
58 | if ($this->properties !== []) {
59 | $schema['type'] = 'object';
60 | return $this->renderProperties($schema);
61 | }
62 |
63 | $rf = new \ReflectionClass($this->type);
64 | if (!$rf->isEnum()) {
65 | throw new DefinitionException(\sprintf(
66 | 'Type `%s` must be a backed enum or class with properties.',
67 | $this->type instanceof Type ? $this->type->value : $this->type,
68 | ));
69 | }
70 |
71 | $rf = new \ReflectionEnum($this->type);
72 |
73 | /** @var \ReflectionEnum $rf */
74 | if (!$rf->isBacked()) {
75 | throw new DefinitionException(\sprintf(
76 | 'Type `%s` is not a backed enum.',
77 | $this->type instanceof Type ? $this->type->value : $this->type,
78 | ));
79 | }
80 |
81 | /**
82 | * @var \ReflectionNamedType $type
83 | */
84 | $type = $rf->getBackingType();
85 |
86 | // mapping to json schema type
87 | $schema['type'] = match ($type->getName()) {
88 | 'int', 'float' => 'number',
89 | 'string' => 'string',
90 | 'bool' => 'boolean',
91 | default => throw new DefinitionException(\sprintf(
92 | 'Type `%s` is not a backed enum.',
93 | $this->type instanceof Type ? $this->type->value : $this->type,
94 | )),
95 | };
96 |
97 | // options are scalar values at this point
98 | $schema['enum'] = $this->options;
99 |
100 | return $schema;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Schema/Format.php:
--------------------------------------------------------------------------------
1 | $options
16 | */
17 | public function __construct(
18 | public readonly Type|string $type,
19 | array $options = [],
20 | public readonly string $title = '',
21 | public readonly string $description = '',
22 | public readonly bool $required = false,
23 | public readonly mixed $default = null,
24 | public readonly ?Format $format = null,
25 | ) {
26 | if (\is_string($this->type) && !\class_exists($this->type)) {
27 | throw new InvalidTypeException('Invalid type definition.');
28 | }
29 |
30 | $this->options = new PropertyOptions($options);
31 | }
32 |
33 | public function jsonSerialize(): array
34 | {
35 | $property = [];
36 | if ($this->title !== '') {
37 | $property['title'] = $this->title;
38 | }
39 |
40 | if ($this->description !== '') {
41 | $property['description'] = $this->description;
42 | }
43 |
44 | if ($this->default !== null) {
45 | $property['default'] = $this->default;
46 | }
47 |
48 | if ($this->format instanceof Format) {
49 | $property['format'] = $this->format->value;
50 | }
51 |
52 |
53 | if ($this->type === Type::Union) {
54 | $property['anyOf'] = $this->options->jsonSerialize();
55 | return $property;
56 | }
57 |
58 | if (\is_string($this->type)) {
59 | // this is nested class
60 | $property['allOf'][] = ['$ref' => (new Reference($this->type))->jsonSerialize()];
61 | return $property;
62 | }
63 |
64 | $property['type'] = $this->type->value;
65 |
66 | if ($this->type === Type::Array) {
67 | if (\count($this->options) === 1) {
68 | if (\is_string($this->options[0]->value)) {
69 | // reference to class
70 | $property['items']['$ref'] = (new Reference($this->options[0]->value))->jsonSerialize();
71 | return $property;
72 | }
73 |
74 | $property['items']['type'] = $this->options[0]->value->value;
75 | } else {
76 | $property['items']['anyOf'] = $this->options->jsonSerialize();
77 | }
78 | }
79 |
80 | return $property;
81 | }
82 |
83 | public function getDependencies(): array
84 | {
85 | $dependencies = [];
86 | foreach ($this->options->getOptions() as $option) {
87 | if (\is_string($option->value)) {
88 | $dependencies[] = $option->value;
89 | }
90 | }
91 |
92 | if (\is_string($this->type)) {
93 | $dependencies[] = $this->type;
94 | }
95 |
96 | return $dependencies;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Schema/PropertyOption.php:
--------------------------------------------------------------------------------
1 | value) && !\class_exists($this->value)) {
21 | throw new InvalidTypeException('Invalid property option definition.');
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Schema/PropertyOptions.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | final class PropertyOptions implements \Countable, \ArrayAccess, \JsonSerializable
12 | {
13 | /**
14 | * @var array
15 | */
16 | private array $options = [];
17 |
18 | /**
19 | * @param array $options
20 | */
21 | public function __construct(array $options = [])
22 | {
23 | foreach ($options as $option) {
24 | $this->options[] = new PropertyOption($option);
25 | }
26 | }
27 |
28 | /**
29 | * @return array
30 | */
31 | public function getOptions(): array
32 | {
33 | return $this->options;
34 | }
35 |
36 | public function count(): int
37 | {
38 | return \count($this->options);
39 | }
40 |
41 | public function offsetExists(mixed $offset): bool
42 | {
43 | return isset($this->options[$offset]);
44 | }
45 |
46 | public function offsetGet(mixed $offset): PropertyOption
47 | {
48 | return $this->options[$offset];
49 | }
50 |
51 | /**
52 | * @param int $offset
53 | * @param PropertyOption|class-string|Type $value
54 | */
55 | public function offsetSet(mixed $offset, mixed $value): void
56 | {
57 | $this->options[$offset] = $value instanceof PropertyOption ? $value : new PropertyOption($value);
58 | }
59 |
60 | public function offsetUnset(mixed $offset): void
61 | {
62 | unset($this->options[$offset]);
63 | }
64 |
65 | public function jsonSerialize(): array
66 | {
67 | $types = [];
68 | foreach ($this->options as $option) {
69 | if (\is_string($option->value)) {
70 | // reference to class
71 | $types[] = ['$ref' => (new Reference($option->value))->jsonSerialize()];
72 | continue;
73 | }
74 |
75 | $types[] = ['type' => $option->value->value];
76 | }
77 | return $types;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Schema/Reference.php:
--------------------------------------------------------------------------------
1 | className, '\\');
22 |
23 | return '#/definitions/' . ($pos === false ? $this->className : \substr($this->className, $pos + 1));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Schema/Type.php:
--------------------------------------------------------------------------------
1 | self::String,
23 | 'integer', 'int' => self::Integer,
24 | 'float', 'double', 'number' => self::Number,
25 | 'boolean', 'bool' => self::Boolean,
26 | 'object' => self::Object,
27 | 'array' => self::Array,
28 | 'null' => self::Null,
29 | default => throw new \InvalidArgumentException(\sprintf('Invalid type `%s`.', $type)),
30 | };
31 | }
32 | }
33 |
--------------------------------------------------------------------------------