ObjectPhpTypeSchemaResolver::isIgnoredProperty()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Exception\UnsupportedPhpTypeException;
23
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaNameResolverInterface;
24
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverInterface;
25
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverManagerAwareInterface;
26
use Sunrise\Http\Router\OpenApi\OpenApiPhpTypeSchemaResolverManagerInterface;
27
use Sunrise\Http\Router\OpenApi\Type;
28
use Sunrise\Http\Router\OpenApi\TypeFactory;
29
use Sunrise\Hydrator\Annotation\Alias;
30
use Sunrise\Hydrator\Annotation\DefaultValue;
31
use Sunrise\Hydrator\Annotation\Ignore;
32
use Sunrise\Hydrator\TypeConverter\ObjectTypeConverter;
33
34
use function class_exists;
35
use function is_scalar;
36
use function strtr;
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 3
        return strtr($phpType->name, '\\', '.');
130
    }
131
132 3
    private static function isIgnoredProperty(ReflectionProperty $property): bool
133
    {
134 3
        return $property->getAttributes(Ignore::class) !== [];
135
    }
136
137 3
    private static function getPropertyName(ReflectionProperty $property): string
138
    {
139
        /** @var list<ReflectionAttribute<Alias>> $annotations */
140 3
        $annotations = $property->getAttributes(Alias::class);
141 3
        if (isset($annotations[0])) {
142 3
            $annotation = $annotations[0]->newInstance();
143 3
            return $annotation->value;
144
        }
145
146 3
        return $property->name;
147
    }
148
149 3
    private static function hasPropertyDefaultValue(ReflectionProperty $property): bool
150
    {
151 3
        if ($property->hasDefaultValue()) {
152 3
            return true;
153
        }
154
155 3
        if ($property->isPromoted()) {
156 3
            foreach ($property->getDeclaringClass()->getConstructor()?->getParameters() ?? [] as $parameter) {
157 3
                if ($parameter->name === $property->name && $parameter->isDefaultValueAvailable()) {
158 3
                    return true;
159
                }
160
            }
161
        }
162
163 3
        if ($property->getAttributes(DefaultValue::class) !== []) {
164 3
            return true;
165
        }
166
167 3
        return false;
168
    }
169
170 3
    private static function getPropertyDefaultValue(ReflectionProperty $property): mixed
171
    {
172 3
        if ($property->hasDefaultValue()) {
173 3
            return $property->getDefaultValue();
174
        }
175
176 3
        if ($property->isPromoted()) {
177 3
            foreach ($property->getDeclaringClass()->getConstructor()?->getParameters() ?? [] as $parameter) {
178 3
                if ($parameter->name === $property->name && $parameter->isDefaultValueAvailable()) {
179 3
                    return $parameter->getDefaultValue();
180
                }
181
            }
182
        }
183
184
        /** @var list<ReflectionAttribute<DefaultValue>> $annotations */
185 3
        $annotations = $property->getAttributes(DefaultValue::class);
186 3
        if (isset($annotations[0])) {
187 3
            $annotation = $annotations[0]->newInstance();
188 3
            return $annotation->value;
189
        }
190
191 3
        return null;
192
    }
193
194 3
    private static function normalizePropertyDefaultValue(mixed $value): mixed
195
    {
196 3
        if (is_scalar($value)) {
197 3
            return $value;
198
        }
199
200 3
        if ($value instanceof BackedEnum) {
201 3
            return $value->value;
202
        }
203
204 3
        return null;
205
    }
206
}
207