Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
84.44% |
76 / 90 |
|
77.78% |
7 / 9 |
CRAP | |
0.00% |
0 / 1 |
MetadataSchemaProvider | |
84.44% |
76 / 90 |
|
77.78% |
7 / 9 |
38.35 | |
0.00% |
0 / 1 |
supports | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createFromUnionType | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
createFromScalar | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
createFromEnum | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
uploadedFileCheck | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
8 | |||
createSchemaForMetadata | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
14 | |||
addDisplaySchemaFor | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
addCreationSchemaFor | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
addModificationSchemaFor | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | namespace Apie\SchemaGenerator\SchemaProviders; |
3 | |
4 | use Apie\Core\Attributes\Description; |
5 | use Apie\Core\Context\ApieContext; |
6 | use Apie\Core\Enums\DoNotChangeUploadedFile; |
7 | use Apie\Core\Enums\ScalarType; |
8 | use Apie\Core\Metadata\EnumMetadata; |
9 | use Apie\Core\Metadata\Fields\PublicProperty; |
10 | use Apie\Core\Metadata\Fields\SetterMethod; |
11 | use Apie\Core\Metadata\MetadataFactory; |
12 | use Apie\Core\Metadata\MetadataInterface; |
13 | use Apie\Core\Metadata\ScalarMetadata; |
14 | use Apie\Core\Metadata\UnionTypeMetadata; |
15 | use Apie\Core\Utils\ConverterUtils; |
16 | use Apie\SchemaGenerator\Builders\ComponentsBuilder; |
17 | use Apie\SchemaGenerator\Interfaces\ModifySchemaProvider; |
18 | use Apie\TypeConverter\ReflectionTypeFactory; |
19 | use cebe\openapi\spec\Components; |
20 | use cebe\openapi\spec\Reference; |
21 | use cebe\openapi\spec\Schema; |
22 | use Psr\Http\Message\UploadedFileInterface; |
23 | use ReflectionClass; |
24 | use ReflectionIntersectionType; |
25 | use ReflectionNamedType; |
26 | use ReflectionType; |
27 | use ReflectionUnionType; |
28 | |
29 | /** |
30 | * Get OpenAPI schema from the apie/core MetadataFactory class. |
31 | * |
32 | * @implements ModifySchemaProvider<object> |
33 | */ |
34 | class MetadataSchemaProvider implements ModifySchemaProvider |
35 | { |
36 | /** |
37 | * @var array<class-string<MetadataInterface>, string> $mapping |
38 | */ |
39 | private array $mapping = [ |
40 | EnumMetadata::class => 'createFromEnum', |
41 | ScalarMetadata::class => 'createFromScalar', |
42 | UnionTypeMetadata::class => 'createFromUnionType', |
43 | ]; |
44 | |
45 | public function supports(ReflectionClass $class): bool |
46 | { |
47 | return true; |
48 | } |
49 | |
50 | private function createFromUnionType(ComponentsBuilder $componentsBuilder, UnionTypeMetadata $metadata, bool $display): Schema |
51 | { |
52 | $oneOfs = []; |
53 | foreach ($metadata->getTypes() as $type) { |
54 | $oneOfs[] = $this->createSchemaForMetadata($componentsBuilder, $metadata, $display, false); |
55 | } |
56 | return new Schema([ |
57 | 'oneOf' => $oneOfs, |
58 | ]); |
59 | } |
60 | |
61 | private function createFromScalar(ComponentsBuilder $componentsBuilder, ScalarMetadata $metadata, bool $display): Schema |
62 | { |
63 | return match ($metadata->toScalarType()) { |
64 | ScalarType::NULLVALUE => new Schema(['nullable' => true]), |
65 | ScalarType::ARRAY => new Schema(['type' => 'array', 'items' => $componentsBuilder->getMixedReference()]), |
66 | ScalarType::STDCLASS => new Schema(['type' => 'object', 'additionalProperties' => $componentsBuilder->getMixedReference()]), |
67 | default => new Schema([ |
68 | 'type' => $metadata->toScalarType()->toJsonSchemaType(), |
69 | ]), |
70 | }; |
71 | } |
72 | |
73 | private function createFromEnum(ComponentsBuilder $componentsBuilder, EnumMetadata $metadata, bool $display): Schema |
74 | { |
75 | return new Schema([ |
76 | 'type' => $metadata->toScalarType()->toJsonSchemaType(), |
77 | 'enum' => array_values($metadata->getOptions(new ApieContext())), |
78 | ]); |
79 | } |
80 | |
81 | private function uploadedFileCheck(?ReflectionType $type): ?ReflectionType |
82 | { |
83 | if ($type === null) { |
84 | return null; |
85 | } |
86 | if ($type instanceof ReflectionUnionType || $type instanceof ReflectionIntersectionType) { |
87 | return $type; |
88 | } |
89 | assert($type instanceof ReflectionNamedType); |
90 | $class = ConverterUtils::toReflectionClass($type); |
91 | if ($class !== null && ($class->name === UploadedFileInterface::class || in_array(UploadedFileInterface::class, $class->getInterfaceNames()))) { |
92 | return ReflectionTypeFactory::createReflectionType(implode( |
93 | '|', |
94 | $type->allowsNull() |
95 | ? [$class->name, DoNotChangeUploadedFile::class, 'null'] |
96 | : [$class->name, DoNotChangeUploadedFile::class] |
97 | )); |
98 | } |
99 | return $type; |
100 | } |
101 | |
102 | private function createSchemaForMetadata(ComponentsBuilder $componentsBuilder, MetadataInterface $metadata, bool $display, bool $nullable): Schema|Reference |
103 | { |
104 | $className = get_class($metadata); |
105 | if (isset($this->mapping[$className])) { |
106 | return $this->{$this->mapping[$className]}($componentsBuilder, $metadata, $display); |
107 | } |
108 | |
109 | $schema = new Schema(['type' => 'object']); |
110 | $properties = []; |
111 | foreach ($metadata->getHashmap() as $fieldName => $field) { |
112 | if (!$field->isField()) { |
113 | continue; |
114 | } |
115 | $type = $field->getTypehint(); |
116 | if (!$display && ($field instanceof PublicProperty || $field instanceof SetterMethod)) { |
117 | $type = $this->uploadedFileCheck($type); |
118 | } |
119 | $properties[$fieldName] = $type ? $componentsBuilder->getSchemaForType($type, false, $display) : $componentsBuilder->getMixedReference(); |
120 | if ($properties[$fieldName] instanceof Schema) { |
121 | $properties[$fieldName]->nullable = $field->allowsNull(); |
122 | } |
123 | if ($properties[$fieldName] instanceof Reference) { |
124 | foreach ($field->getAttributes(Description::class) as $descriptionAttribute) { |
125 | $refSchema = $componentsBuilder->getSchemaForReference($properties[$fieldName]); |
126 | if ($refSchema) { |
127 | $refSchema->description = $descriptionAttribute->description; |
128 | } |
129 | break; |
130 | } |
131 | } |
132 | } |
133 | $required = $metadata->getRequiredFields()->toArray(); |
134 | if (!empty($required)) { |
135 | $schema->required = $required; |
136 | } |
137 | if ($nullable) { |
138 | $schema->nullable = true; |
139 | } |
140 | $schema->properties = $properties; |
141 | return $schema; |
142 | } |
143 | |
144 | public function addDisplaySchemaFor( |
145 | ComponentsBuilder $componentsBuilder, |
146 | string $componentIdentifier, |
147 | ReflectionClass $class, |
148 | bool $nullable = false |
149 | ): Components { |
150 | $componentsBuilder->setSchema( |
151 | $componentIdentifier, |
152 | $this->createSchemaForMetadata( |
153 | $componentsBuilder, |
154 | MetadataFactory::getResultMetadata($class, new ApieContext()), |
155 | true, |
156 | $nullable |
157 | ) |
158 | ); |
159 | return $componentsBuilder->getComponents(); |
160 | } |
161 | |
162 | public function addCreationSchemaFor( |
163 | ComponentsBuilder $componentsBuilder, |
164 | string $componentIdentifier, |
165 | ReflectionClass $class, |
166 | bool $nullable = false |
167 | ): Components { |
168 | $componentsBuilder->setSchema( |
169 | $componentIdentifier, |
170 | $this->createSchemaForMetadata( |
171 | $componentsBuilder, |
172 | MetadataFactory::getCreationMetadata($class, new ApieContext()), |
173 | false, |
174 | $nullable |
175 | ) |
176 | ); |
177 | return $componentsBuilder->getComponents(); |
178 | } |
179 | |
180 | public function addModificationSchemaFor( |
181 | ComponentsBuilder $componentsBuilder, |
182 | string $componentIdentifier, |
183 | ReflectionClass $class |
184 | ): Components { |
185 | $componentsBuilder->setSchema( |
186 | $componentIdentifier, |
187 | $this->createSchemaForMetadata( |
188 | $componentsBuilder, |
189 | MetadataFactory::getModificationMetadata($class, new ApieContext()), |
190 | false, |
191 | false |
192 | ) |
193 | ); |
194 | return $componentsBuilder->getComponents(); |
195 | } |
196 | } |