• 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%)

10372 of 12438 relevant lines covered (83.39%)

11.97 hits per line

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

93.1
/src/GraphQl/Type/TypeConverter.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\GraphQl\Type;
15

16
use ApiPlatform\Exception\InvalidArgumentException;
17
use ApiPlatform\Exception\OperationNotFoundException;
18
use ApiPlatform\Exception\ResourceClassNotFoundException;
19
use ApiPlatform\Metadata\GraphQl\Operation;
20
use ApiPlatform\Metadata\GraphQl\Query;
21
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
22
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
23
use GraphQL\Error\SyntaxError;
24
use GraphQL\Language\AST\ListTypeNode;
25
use GraphQL\Language\AST\NamedTypeNode;
26
use GraphQL\Language\AST\NonNullTypeNode;
27
use GraphQL\Language\AST\TypeNode;
28
use GraphQL\Language\Parser;
29
use GraphQL\Type\Definition\NullableType;
30
use GraphQL\Type\Definition\Type as GraphQLType;
31
use Symfony\Component\PropertyInfo\Type;
32

33
/**
34
 * Converts a type to its GraphQL equivalent.
35
 *
36
 * @author Alan Poulain <contact@alanpoulain.eu>
37
 */
38
final class TypeConverter implements TypeConverterInterface
39
{
40
    public function __construct(private readonly TypeBuilderEnumInterface|TypeBuilderInterface $typeBuilder, private readonly TypesContainerInterface $typesContainer, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory)
41
    {
42
        if ($typeBuilder instanceof TypeBuilderInterface) {
32✔
43
            @trigger_error(sprintf('$typeBuilder argument of TypeConverter implementing "%s" is deprecated since API Platform 3.1. It has to implement "%s" instead.', TypeBuilderInterface::class, TypeBuilderEnumInterface::class), \E_USER_DEPRECATED);
×
44
        }
45
    }
46

47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function convertType(Type $type, bool $input, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth): GraphQLType|string|null
51
    {
52
        switch ($type->getBuiltinType()) {
19✔
53
            case Type::BUILTIN_TYPE_BOOL:
54
                return GraphQLType::boolean();
1✔
55
            case Type::BUILTIN_TYPE_INT:
56
                return GraphQLType::int();
1✔
57
            case Type::BUILTIN_TYPE_FLOAT:
58
                return GraphQLType::float();
1✔
59
            case Type::BUILTIN_TYPE_STRING:
60
                return GraphQLType::string();
1✔
61
            case Type::BUILTIN_TYPE_ARRAY:
62
            case Type::BUILTIN_TYPE_ITERABLE:
63
                if ($resourceType = $this->getResourceType($type, $input, $rootOperation, $rootResource, $property, $depth)) {
3✔
64
                    return $resourceType;
1✔
65
                }
66

67
                return 'Iterable';
2✔
68
            case Type::BUILTIN_TYPE_OBJECT:
69
                if (is_a($type->getClassName(), \DateTimeInterface::class, true)) {
9✔
70
                    return GraphQLType::string();
1✔
71
                }
72

73
                $resourceType = $this->getResourceType($type, $input, $rootOperation, $rootResource, $property, $depth);
8✔
74

75
                if (!$resourceType && is_a($type->getClassName(), \BackedEnum::class, true)) {
7✔
76
                    // Remove the condition in API Platform 4.
77
                    if ($this->typeBuilder instanceof TypeBuilderEnumInterface) {
1✔
78
                        $operation = null;
1✔
79
                        try {
80
                            $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($type->getClassName());
1✔
81
                            $operation = $resourceMetadataCollection->getOperation();
×
82
                        } catch (ResourceClassNotFoundException|OperationNotFoundException) {
1✔
83
                        }
84
                        /** @var Query $enumOperation */
85
                        $enumOperation = (new Query())
1✔
86
                            ->withClass($type->getClassName())
1✔
87
                            ->withShortName($operation?->getShortName() ?? (new \ReflectionClass($type->getClassName()))->getShortName())
1✔
88
                            ->withDescription($operation?->getDescription());
1✔
89

90
                        return $this->typeBuilder->getEnumType($enumOperation);
1✔
91
                    }
92
                }
93

94
                return $resourceType;
6✔
95
            default:
96
                return null;
3✔
97
        }
98
    }
99

100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function resolveType(string $type): ?GraphQLType
104
    {
105
        try {
106
            $astTypeNode = Parser::parseType($type);
13✔
107
        } catch (SyntaxError $e) {
1✔
108
            throw new InvalidArgumentException(sprintf('"%s" is not a valid GraphQL type.', $type), 0, $e);
1✔
109
        }
110

111
        if ($graphQlType = $this->resolveAstTypeNode($astTypeNode, $type)) {
12✔
112
            return $graphQlType;
10✔
113
        }
114

115
        throw new InvalidArgumentException(sprintf('The type "%s" was not resolved.', $type));
2✔
116
    }
117

118
    private function getResourceType(Type $type, bool $input, Operation $rootOperation, string $rootResource, ?string $property, int $depth): ?GraphQLType
119
    {
120
        if (
121
            $this->typeBuilder->isCollection($type) &&
11✔
122
            $collectionValueType = $type->getCollectionValueTypes()[0] ?? null
11✔
123
        ) {
124
            $resourceClass = $collectionValueType->getClassName();
2✔
125
        } else {
126
            $resourceClass = $type->getClassName();
9✔
127
        }
128

129
        if (null === $resourceClass) {
11✔
130
            return null;
3✔
131
        }
132

133
        try {
134
            $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
8✔
135
        } catch (ResourceClassNotFoundException) {
2✔
136
            return null;
2✔
137
        }
138

139
        $hasGraphQl = false;
6✔
140
        foreach ($resourceMetadataCollection as $resourceMetadata) {
6✔
141
            if (null !== $resourceMetadata->getGraphQlOperations()) {
6✔
142
                $hasGraphQl = true;
5✔
143
                break;
5✔
144
            }
145
        }
146

147
        if (isset($resourceMetadataCollection[0]) && 'Node' === $resourceMetadataCollection[0]->getShortName()) {
6✔
148
            throw new \UnexpectedValueException('A "Node" resource cannot be used with GraphQL because the type is already used by the Relay specification.');
1✔
149
        }
150

151
        if (!$hasGraphQl) {
5✔
152
            return null;
1✔
153
        }
154

155
        $propertyMetadata = null;
4✔
156
        if ($property) {
4✔
157
            $context = [
2✔
158
                'normalization_groups' => $rootOperation->getNormalizationContext()['groups'] ?? null,
2✔
159
                'denormalization_groups' => $rootOperation->getDenormalizationContext()['groups'] ?? null,
2✔
160
            ];
2✔
161
            $propertyMetadata = $this->propertyMetadataFactory->create($rootResource, $property, $context);
2✔
162
        }
163

164
        if ($input && $depth > 0 && (!$propertyMetadata || !$propertyMetadata->isWritableLink())) {
4✔
165
            return GraphQLType::string();
1✔
166
        }
167

168
        $operationName = $rootOperation->getName();
3✔
169
        $isCollection = $this->typeBuilder->isCollection($type);
3✔
170

171
        // We're retrieving the type of a property which is a relation to the root resource.
172
        if ($resourceClass !== $rootResource && $rootOperation instanceof Query) {
3✔
173
            $operationName = $isCollection ? 'collection_query' : 'item_query';
3✔
174
        }
175

176
        try {
177
            $operation = $resourceMetadataCollection->getOperation($operationName);
3✔
178
        } catch (OperationNotFoundException) {
×
179
            $operation = $resourceMetadataCollection->getOperation($isCollection ? 'collection_query' : 'item_query');
×
180
        }
181
        if (!$operation instanceof Operation) {
3✔
182
            throw new OperationNotFoundException();
×
183
        }
184

185
        return $this->typeBuilder->getResourceObjectType($resourceClass, $resourceMetadataCollection, $operation, $input, false, $depth);
3✔
186
    }
187

188
    private function resolveAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType
189
    {
190
        if ($astTypeNode instanceof NonNullTypeNode) {
12✔
191
            /** @var NullableType|null $nullableAstTypeNode */
192
            $nullableAstTypeNode = $this->resolveNullableAstTypeNode($astTypeNode->type, $fromType);
6✔
193

194
            return $nullableAstTypeNode ? GraphQLType::nonNull($nullableAstTypeNode) : null;
6✔
195
        }
196

197
        return $this->resolveNullableAstTypeNode($astTypeNode, $fromType);
8✔
198
    }
199

200
    private function resolveNullableAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType
201
    {
202
        if ($astTypeNode instanceof ListTypeNode) {
12✔
203
            /** @var TypeNode $astTypeNodeElement */
204
            $astTypeNodeElement = $astTypeNode->type;
4✔
205

206
            return GraphQLType::listOf($this->resolveAstTypeNode($astTypeNodeElement, $fromType));
4✔
207
        }
208

209
        if (!$astTypeNode instanceof NamedTypeNode) {
12✔
210
            return null;
×
211
        }
212

213
        $typeName = $astTypeNode->name->value;
12✔
214

215
        return match ($typeName) {
12✔
216
            GraphQLType::STRING => GraphQLType::string(),
12✔
217
            GraphQLType::INT => GraphQLType::int(),
12✔
218
            GraphQLType::BOOLEAN => GraphQLType::boolean(),
12✔
219
            GraphQLType::FLOAT => GraphQLType::float(),
12✔
220
            GraphQLType::ID => GraphQLType::id(),
12✔
221
            default => $this->typesContainer->has($typeName) ? $this->typesContainer->get($typeName) : null,
12✔
222
        };
12✔
223
    }
224
}
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

© 2026 Coveralls, Inc