ObjectPhpTypeSchemaResolver   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 72
dl 0
loc 175
ccs 84
cts 84
cp 1
rs 9.68
c 0
b 0
f 0
wmc 34

10 Methods

Rating   Name   Duplication   Size   Complexity  
B resolvePhpTypeSchema() 0 43 6
A getWeight() 0 3 1
A setOpenApiPhpTypeSchemaResolverManager() 0 4 1
A supportsPhpType() 0 13 4
B hasPropertyDefaultValue() 0 19 7
A resolvePhpTypeSchemaName() 0 14 2
A isIgnoredProperty() 0 3 1
B getPropertyDefaultValue() 0 22 7
A normalizePropertyDefaultValue() 0 11 3
A getPropertyName() 0 10 2
1
<?php
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sunrise\Http\Router\OpenApi\PhpTypeSchemaResolver;
15
16
use BackedEnum;
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use ReflectionAttribute;
18
use ReflectionClass;
19
use ReflectionException;
20
use ReflectionProperty;
21
use Reflector;
22
use Sunrise\Http\Router\OpenApi\Annotation\SchemaName;
23
use Sunrise\Http\Router\OpenApi\Exception\UnsupportedPhpTypeException;
24
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaNameResolverInterface;
25
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverInterface;
26
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverManagerAwareInterface;
27
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverManagerInterface;
28
use Sunrise\Http\Router\OpenApi\Type;
29
use Sunrise\Http\Router\OpenApi\TypeFactory;
30
use Sunrise\Hydrator\Annotation\Alias;
31
use Sunrise\Hydrator\Annotation\DefaultValue;
32
use Sunrise\Hydrator\Annotation\Ignore;
33
use Sunrise\Hydrator\TypeConverter\ObjectTypeConverter;
34
35
use function class_exists;
36
use function is_scalar;
37
38
/**
39
 * @since 3.0.0
40
 */
41
final class ObjectPhpTypeSchemaResolver implements
42
    OpenApiPhpTypeSchemaResolverInterface,
43
    OpenApiPhpTypeSchemaNameResolverInterface,
44
    OpenApiPhpTypeSchemaResolverManagerAwareInterface
45
{
46
    private readonly OpenApiPhpTypeSchemaResolverManagerInterface $openApiPhpTypeSchemaResolverManager;
47
48 5
    public function setOpenApiPhpTypeSchemaResolverManager(
49
        OpenApiPhpTypeSchemaResolverManagerInterface $openApiPhpTypeSchemaResolverManager,
50
    ): void {
51 5
        $this->openApiPhpTypeSchemaResolverManager = $openApiPhpTypeSchemaResolverManager;
0 ignored issues
show
Bug introduced by
The property openApiPhpTypeSchemaResolverManager is declared read-only in Sunrise\Http\Router\Open...ctPhpTypeSchemaResolver.
Loading history...
52
    }
53
54
    /**
55
     * @see ObjectTypeConverter
56
     */
57 3
    public function supportsPhpType(Type $phpType, Reflector $phpTypeHolder): bool
58
    {
59 3
        $className = $phpType->name;
60 3
        if (!class_exists($className)) {
61 3
            return false;
62
        }
63
64 3
        $class = new ReflectionClass($className);
65 3
        if ($class->isInternal() || !$class->isInstantiable()) {
66 3
            return false;
67
        }
68
69 3
        return true;
70
    }
71
72
    /**
73
     * @inheritDoc
74
     *
75
     * @throws ReflectionException
76
     */
77 3
    public function resolvePhpTypeSchema(Type $phpType, Reflector $phpTypeHolder): array
78
    {
79 3
        $this->supportsPhpType($phpType, $phpTypeHolder) or throw new UnsupportedPhpTypeException();
80
81
        /** @var class-string $phpTypeName */
82 3
        $phpTypeName = $phpType->name;
83
84 3
        $phpTypeSchema = [
85 3
            'type' => 'object',
86 3
            'additionalProperties' => false,
87 3
        ];
88
89 3
        $class = new ReflectionClass($phpTypeName);
90
91 3
        foreach ($class->getProperties() as $property) {
92 3
            if (self::isIgnoredProperty($property)) {
93 3
                continue;
94
            }
95
96 3
            $propertyType = TypeFactory::fromPhpTypeReflection($property->getType());
97 3
            $propertyTypeSchema = $this->openApiPhpTypeSchemaResolverManager
98 3
                ->resolvePhpTypeSchema($propertyType, $property);
99
100 3
            $propertyDefaultValue = self::getPropertyDefaultValue($property);
101 3
            $normalizePropertyDefaultValue = self::normalizePropertyDefaultValue($propertyDefaultValue);
102 3
            if ($normalizePropertyDefaultValue !== null) {
103 3
                $propertyTypeSchema = [
104 3
                    'allOf' => [
105 3
                        $propertyTypeSchema,
106 3
                    ],
107 3
                    'default' => $normalizePropertyDefaultValue,
108 3
                ];
109
            }
110
111 3
            $propertyName = self::getPropertyName($property);
112 3
            $phpTypeSchema['properties'][$propertyName] = $propertyTypeSchema;
113
114 3
            if (!self::hasPropertyDefaultValue($property)) {
115 3
                $phpTypeSchema['required'][] = $propertyName;
116
            }
117
        }
118
119 3
        return $phpTypeSchema;
120
    }
121
122 3
    public function getWeight(): int
123
    {
124 3
        return -100;
125
    }
126
127 3
    public function resolvePhpTypeSchemaName(Type $phpType, Reflector $phpTypeHolder): string
128
    {
129
        /** @var class-string $className */
130 3
        $className = $phpType->name;
131 3
        $classReflection = new ReflectionClass($className);
132
133
        /** @var list<ReflectionAttribute<SchemaName>> $annotations */
134 3
        $annotations = $classReflection->getAttributes(SchemaName::class);
135 3
        if (isset($annotations[0])) {
136 3
            $annotation = $annotations[0]->newInstance();
137 3
            return $annotation->value;
138
        }
139
140 3
        return $classReflection->getShortName();
141
    }
142
143 3
    private static function isIgnoredProperty(ReflectionProperty $property): bool
144
    {
145 3
        return $property->getAttributes(Ignore::class) !== [];
146
    }
147
148 3
    private static function getPropertyName(ReflectionProperty $property): string
149
    {
150
        /** @var list<ReflectionAttribute<Alias>> $annotations */
151 3
        $annotations = $property->getAttributes(Alias::class);
152 3
        if (isset($annotations[0])) {
153 3
            $annotation = $annotations[0]->newInstance();
154 3
            return $annotation->value;
155
        }
156
157 3
        return $property->name;
158
    }
159
160 3
    private static function hasPropertyDefaultValue(ReflectionProperty $property): bool
161
    {
162 3
        if ($property->hasDefaultValue()) {
163 3
            return true;
164
        }
165
166 3
        if ($property->isPromoted()) {
167 3
            foreach ($property->getDeclaringClass()->getConstructor()?->getParameters() ?? [] as $parameter) {
168 3
                if ($parameter->name === $property->name && $parameter->isDefaultValueAvailable()) {
169 3
                    return true;
170
                }
171
            }
172
        }
173
174 3
        if ($property->getAttributes(DefaultValue::class) !== []) {
175 3
            return true;
176
        }
177
178 3
        return false;
179
    }
180
181 3
    private static function getPropertyDefaultValue(ReflectionProperty $property): mixed
182
    {
183 3
        if ($property->hasDefaultValue()) {
184 3
            return $property->getDefaultValue();
185
        }
186
187 3
        if ($property->isPromoted()) {
188 3
            foreach ($property->getDeclaringClass()->getConstructor()?->getParameters() ?? [] as $parameter) {
189 3
                if ($parameter->name === $property->name && $parameter->isDefaultValueAvailable()) {
190 3
                    return $parameter->getDefaultValue();
191
                }
192
            }
193
        }
194
195
        /** @var list<ReflectionAttribute<DefaultValue>> $annotations */
196 3
        $annotations = $property->getAttributes(DefaultValue::class);
197 3
        if (isset($annotations[0])) {
198 3
            $annotation = $annotations[0]->newInstance();
199 3
            return $annotation->value;
200
        }
201
202 3
        return null;
203
    }
204
205 3
    private static function normalizePropertyDefaultValue(mixed $value): mixed
206
    {
207 3
        if (is_scalar($value)) {
208 3
            return $value;
209
        }
210
211 3
        if ($value instanceof BackedEnum) {
212 3
            return $value->value;
213
        }
214
215 3
        return null;
216
    }
217
}
218