Passed
Push — master ( 8bd912...d93388 )
by Alan
06:58 queued 02:20
created

src/GraphQl/Type/TypeConverter.php (1 issue)

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
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\Core\GraphQl\Type;
15
16
use ApiPlatform\Core\Exception\InvalidArgumentException;
17
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
18
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19
use GraphQL\Error\SyntaxError;
20
use GraphQL\Language\AST\ListTypeNode;
21
use GraphQL\Language\AST\NamedTypeNode;
22
use GraphQL\Language\AST\NonNullTypeNode;
23
use GraphQL\Language\AST\TypeNode;
24
use GraphQL\Language\Parser;
25
use GraphQL\Type\Definition\NullableType;
26
use GraphQL\Type\Definition\Type as GraphQLType;
27
use Symfony\Component\PropertyInfo\Type;
28
29
/**
30
 * Converts a type to its GraphQL equivalent.
31
 *
32
 * @experimental
33
 *
34
 * @author Alan Poulain <[email protected]>
35
 */
36
final class TypeConverter implements TypeConverterInterface
37
{
38
    private $typeBuilder;
39
    private $typesContainer;
40
    private $resourceMetadataFactory;
41
42
    public function __construct(TypeBuilderInterface $typeBuilder, TypesContainerInterface $typesContainer, ResourceMetadataFactoryInterface $resourceMetadataFactory)
43
    {
44
        $this->typeBuilder = $typeBuilder;
45
        $this->typesContainer = $typesContainer;
46
        $this->resourceMetadataFactory = $resourceMetadataFactory;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, string $resourceClass, string $rootResource, ?string $property, int $depth)
53
    {
54
        switch ($type->getBuiltinType()) {
55
            case Type::BUILTIN_TYPE_BOOL:
56
                return GraphQLType::boolean();
57
            case Type::BUILTIN_TYPE_INT:
58
                return GraphQLType::int();
59
            case Type::BUILTIN_TYPE_FLOAT:
60
                return GraphQLType::float();
61
            case Type::BUILTIN_TYPE_STRING:
62
                return GraphQLType::string();
63
            case Type::BUILTIN_TYPE_ARRAY:
64
            case Type::BUILTIN_TYPE_ITERABLE:
65
                return 'Iterable';
66
            case Type::BUILTIN_TYPE_OBJECT:
67
                if ($input && $depth > 0) {
68
                    return GraphQLType::string();
69
                }
70
71
                if (is_a($type->getClassName(), \DateTimeInterface::class, true)) {
72
                    return GraphQLType::string();
73
                }
74
75
                return $this->getResourceType($type, $input, $queryName, $mutationName, $depth);
76
            default:
77
                return null;
78
        }
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function resolveType(string $type): ?GraphQLType
85
    {
86
        try {
87
            $astTypeNode = Parser::parseType($type);
88
        } catch (SyntaxError $e) {
89
            throw new InvalidArgumentException(sprintf('"%s" is not a valid GraphQL type.', $type), 0, $e);
90
        }
91
92
        if ($graphQlType = $this->resolveAstTypeNode($astTypeNode, $type)) {
93
            return $graphQlType;
94
        }
95
96
        throw new InvalidArgumentException(sprintf('The type "%s" was not resolved.', $type));
97
    }
98
99
    private function getResourceType(Type $type, bool $input, ?string $queryName, ?string $mutationName, int $depth): ?GraphQLType
100
    {
101
        $resourceClass = $this->typeBuilder->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();
102
        if (null === $resourceClass) {
103
            return null;
104
        }
105
106
        try {
107
            $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
108
            if ([] === ($resourceMetadata->getGraphql() ?? [])) {
109
                return null;
110
            }
111
        } catch (ResourceClassNotFoundException $e) {
112
            // Skip objects that are not resources for now
113
            return null;
114
        }
115
116
        return $this->typeBuilder->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, false, $depth);
117
    }
118
119
    private function resolveAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType
120
    {
121
        if ($astTypeNode instanceof NonNullTypeNode) {
122
            /** @var NullableType|null $nullableAstTypeNode */
123
            $nullableAstTypeNode = $this->resolveNullableAstTypeNode($astTypeNode->type, $fromType);
124
125
            return $nullableAstTypeNode ? GraphQLType::nonNull($nullableAstTypeNode) : null;
0 ignored issues
show
$nullableAstTypeNode is of type GraphQL\Type\Definition\ListOfType, thus it always evaluated to true.
Loading history...
126
        }
127
128
        return $this->resolveNullableAstTypeNode($astTypeNode, $fromType);
129
    }
130
131
    private function resolveNullableAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType
132
    {
133
        if ($astTypeNode instanceof ListTypeNode) {
134
            /** @var TypeNode $astTypeNodeElement */
135
            $astTypeNodeElement = $astTypeNode->type;
136
137
            return GraphQLType::listOf($this->resolveAstTypeNode($astTypeNodeElement, $fromType));
138
        }
139
140
        if (!$astTypeNode instanceof NamedTypeNode) {
141
            return null;
142
        }
143
144
        $typeName = $astTypeNode->name->value;
145
146
        switch ($typeName) {
147
            case GraphQLType::STRING:
148
                return GraphQLType::string();
149
            case GraphQLType::INT:
150
                return GraphQLType::int();
151
            case GraphQLType::BOOLEAN:
152
                return GraphQLType::boolean();
153
            case GraphQLType::FLOAT:
154
                return GraphQLType::float();
155
            case GraphQLType::ID:
156
                return GraphQLType::id();
157
            default:
158
                if ($this->typesContainer->has($typeName)) {
159
                    return $this->typesContainer->get($typeName);
160
                }
161
162
                return null;
163
        }
164
    }
165
}
166