• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 3713134090

pending completion
3713134090

Pull #5254

github

GitHub
Merge b2ec54b3c into ac711530f
Pull Request #5254: [OpenApi] Add ApiResource::openapi and deprecate openapiContext

197 of 197 new or added lines in 5 files covered. (100.0%)

7493 of 12362 relevant lines covered (60.61%)

67.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.29
/src/JsonSchema/TypeFactory.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\JsonSchema;
15

16
use ApiPlatform\Api\ResourceClassResolverInterface;
17
use ApiPlatform\Util\ResourceClassInfoTrait;
18
use Ramsey\Uuid\UuidInterface;
19
use Symfony\Component\PropertyInfo\Type;
20
use Symfony\Component\Uid\Ulid;
21
use Symfony\Component\Uid\Uuid;
22

23
/**
24
 * {@inheritdoc}
25
 *
26
 * @author Kévin Dunglas <dunglas@gmail.com>
27
 */
28
final class TypeFactory implements TypeFactoryInterface
29
{
30
    use ResourceClassInfoTrait;
31

32
    private ?SchemaFactoryInterface $schemaFactory = null;
33

34
    public function __construct(ResourceClassResolverInterface $resourceClassResolver = null)
35
    {
36
        $this->resourceClassResolver = $resourceClassResolver;
8✔
37
    }
38

39
    public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
40
    {
41
        $this->schemaFactory = $schemaFactory;
8✔
42
    }
43

44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function getType(Type $type, string $format = 'json', ?bool $readableLink = null, ?array $serializerContext = null, Schema $schema = null): array
48
    {
49
        if ($type->isCollection()) {
4✔
50
            $keyType = $type->getCollectionKeyTypes()[0] ?? null;
4✔
51
            $subType = ($type->getCollectionValueTypes()[0] ?? null) ?? new Type($type->getBuiltinType(), false, $type->getClassName(), false);
4✔
52

53
            if (null !== $keyType && Type::BUILTIN_TYPE_STRING === $keyType->getBuiltinType()) {
4✔
54
                return $this->addNullabilityToTypeDefinition([
×
55
                    'type' => 'object',
×
56
                    'additionalProperties' => $this->getType($subType, $format, $readableLink, $serializerContext, $schema),
×
57
                ], $type, $schema);
×
58
            }
59

60
            return $this->addNullabilityToTypeDefinition([
4✔
61
                'type' => 'array',
4✔
62
                'items' => $this->getType($subType, $format, $readableLink, $serializerContext, $schema),
4✔
63
            ], $type, $schema);
4✔
64
        }
65

66
        return $this->addNullabilityToTypeDefinition($this->makeBasicType($type, $format, $readableLink, $serializerContext, $schema), $type, $schema);
4✔
67
    }
68

69
    private function makeBasicType(Type $type, string $format = 'json', ?bool $readableLink = null, ?array $serializerContext = null, Schema $schema = null): array
70
    {
71
        return match ($type->getBuiltinType()) {
4✔
72
            Type::BUILTIN_TYPE_INT => ['type' => 'integer'],
4✔
73
            Type::BUILTIN_TYPE_FLOAT => ['type' => 'number'],
4✔
74
            Type::BUILTIN_TYPE_BOOL => ['type' => 'boolean'],
4✔
75
            Type::BUILTIN_TYPE_OBJECT => $this->getClassType($type->getClassName(), $type->isNullable(), $format, $readableLink, $serializerContext, $schema),
4✔
76
            default => ['type' => 'string'],
4✔
77
        };
4✔
78
    }
79

80
    /**
81
     * Gets the JSON Schema document which specifies the data type corresponding to the given PHP class, and recursively adds needed new schema to the current schema if provided.
82
     */
83
    private function getClassType(?string $className, bool $nullable, string $format, ?bool $readableLink, ?array $serializerContext, ?Schema $schema): array
84
    {
85
        if (null === $className) {
4✔
86
            return ['type' => 'string'];
×
87
        }
88

89
        if (is_a($className, \DateTimeInterface::class, true)) {
4✔
90
            return [
4✔
91
                'type' => 'string',
4✔
92
                'format' => 'date-time',
4✔
93
            ];
4✔
94
        }
95
        if (is_a($className, \DateInterval::class, true)) {
4✔
96
            return [
×
97
                'type' => 'string',
×
98
                'format' => 'duration',
×
99
            ];
×
100
        }
101
        if (is_a($className, UuidInterface::class, true) || is_a($className, Uuid::class, true)) {
4✔
102
            return [
×
103
                'type' => 'string',
×
104
                'format' => 'uuid',
×
105
            ];
×
106
        }
107
        if (is_a($className, Ulid::class, true)) {
4✔
108
            return [
×
109
                'type' => 'string',
×
110
                'format' => 'ulid',
×
111
            ];
×
112
        }
113
        if (is_a($className, \SplFileInfo::class, true)) {
4✔
114
            return [
×
115
                'type' => 'string',
×
116
                'format' => 'binary',
×
117
            ];
×
118
        }
119
        if (!$this->isResourceClass($className) && is_a($className, \BackedEnum::class, true)) {
4✔
120
            $rEnum = new \ReflectionEnum($className);
4✔
121
            $enumCases = array_map(static fn (\ReflectionEnumBackedCase $rCase) => $rCase->getBackingValue(), $rEnum->getCases());
4✔
122
            if ($nullable) {
4✔
123
                $enumCases[] = null;
4✔
124
            }
125

126
            return [
4✔
127
                'type' => (string) $rEnum->getBackingType(),
4✔
128
                'enum' => $enumCases,
4✔
129
            ];
4✔
130
        }
131

132
        // Skip if $schema is null (filters only support basic types)
133
        if (null === $schema) {
4✔
134
            return ['type' => 'string'];
×
135
        }
136

137
        if (true !== $readableLink && $this->isResourceClass($className)) {
4✔
138
            return [
4✔
139
                'type' => 'string',
4✔
140
                'format' => 'iri-reference',
4✔
141
            ];
4✔
142
        }
143

144
        $version = $schema->getVersion();
4✔
145

146
        $subSchema = new Schema($version);
4✔
147
        $subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema
4✔
148

149
        if (null === $this->schemaFactory) {
4✔
150
            throw new \LogicException('The schema factory must be injected by calling the "setSchemaFactory" method.');
×
151
        }
152

153
        $subSchema = $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, $subSchema, $serializerContext, false);
4✔
154

155
        return ['$ref' => $subSchema['$ref']];
4✔
156
    }
157

158
    /**
159
     * @param array<string, mixed> $jsonSchema
160
     *
161
     * @return array<string, mixed>
162
     */
163
    private function addNullabilityToTypeDefinition(array $jsonSchema, Type $type, ?Schema $schema): array
164
    {
165
        if ($schema && Schema::VERSION_SWAGGER === $schema->getVersion()) {
4✔
166
            return $jsonSchema;
×
167
        }
168

169
        if (!$type->isNullable()) {
4✔
170
            return $jsonSchema;
4✔
171
        }
172

173
        if (\array_key_exists('$ref', $jsonSchema)) {
4✔
174
            $typeDefinition = ['anyOf' => [$jsonSchema]];
4✔
175

176
            if ($schema && Schema::VERSION_JSON_SCHEMA === $schema->getVersion()) {
4✔
177
                $typeDefinition['anyOf'][] = ['type' => 'null'];
×
178
            } else {
179
                // OpenAPI < 3.1
180
                $typeDefinition['nullable'] = true;
4✔
181
            }
182

183
            return $typeDefinition;
4✔
184
        }
185

186
        if ($schema && Schema::VERSION_JSON_SCHEMA === $schema->getVersion()) {
4✔
187
            return [...$jsonSchema, ...[
×
188
                'type' => \is_array($jsonSchema['type'])
×
189
                    ? array_merge($jsonSchema['type'], ['null'])
×
190
                    : [$jsonSchema['type'], 'null'],
×
191
            ]];
×
192
        }
193

194
        return [...$jsonSchema, ...['nullable' => true]];
4✔
195
    }
196
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc