TypeBuilder::getResourceObjectType()   F
last analyzed

Complexity

Conditions 29
Paths 2720

Size

Total Lines 85
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 29
eloc 52
c 1
b 0
f 0
nc 2720
nop 7
dl 0
loc 85
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\GraphQl\Serializer\ItemNormalizer;
17
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
18
use GraphQL\Type\Definition\InputObjectType;
19
use GraphQL\Type\Definition\InterfaceType;
20
use GraphQL\Type\Definition\NonNull;
21
use GraphQL\Type\Definition\ObjectType;
22
use GraphQL\Type\Definition\Type as GraphQLType;
23
use Psr\Container\ContainerInterface;
24
use Symfony\Component\PropertyInfo\Type;
25
26
/**
27
 * Builds the GraphQL types.
28
 *
29
 * @experimental
30
 *
31
 * @author Alan Poulain <[email protected]>
32
 */
33
final class TypeBuilder implements TypeBuilderInterface
34
{
35
    private $typesContainer;
36
    private $defaultFieldResolver;
37
    private $fieldsBuilderLocator;
38
39
    public function __construct(TypesContainerInterface $typesContainer, callable $defaultFieldResolver, ContainerInterface $fieldsBuilderLocator)
40
    {
41
        $this->typesContainer = $typesContainer;
42
        $this->defaultFieldResolver = $defaultFieldResolver;
43
        $this->fieldsBuilderLocator = $fieldsBuilderLocator;
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function getResourceObjectType(?string $resourceClass, ResourceMetadata $resourceMetadata, bool $input, ?string $queryName, ?string $mutationName, bool $wrapped = false, int $depth = 0): GraphQLType
50
    {
51
        $shortName = $resourceMetadata->getShortName();
52
53
        if (null !== $mutationName) {
54
            $shortName = $mutationName.ucfirst($shortName);
55
        }
56
        if ($input) {
57
            $shortName .= 'Input';
58
        } elseif (null !== $mutationName) {
59
            if ($depth > 0) {
60
                $shortName .= 'Nested';
61
            }
62
            $shortName .= 'Payload';
63
        }
64
        if (('item_query' === $queryName || 'collection_query' === $queryName)
65
            && $resourceMetadata->getGraphqlAttribute('item_query', 'normalization_context', [], true) !== $resourceMetadata->getGraphqlAttribute('collection_query', 'normalization_context', [], true)) {
66
            if ('item_query' === $queryName) {
67
                $shortName .= 'Item';
68
            }
69
            if ('collection_query' === $queryName) {
70
                $shortName .= 'Collection';
71
            }
72
        }
73
        if ($wrapped && null !== $mutationName) {
74
            $shortName .= 'Data';
75
        }
76
77
        if ($this->typesContainer->has($shortName)) {
78
            $resourceObjectType = $this->typesContainer->get($shortName);
79
            if (!($resourceObjectType instanceof ObjectType || $resourceObjectType instanceof NonNull)) {
80
                throw new \UnexpectedValueException(sprintf(
81
                    'Expected GraphQL type "%s" to be %s.',
82
                    $shortName,
83
                    implode('|', [ObjectType::class, NonNull::class])
84
                ));
85
            }
86
87
            return $resourceObjectType;
88
        }
89
90
        $ioMetadata = $resourceMetadata->getGraphqlAttribute($mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
0 ignored issues
show
Bug introduced by Alan Poulain
It seems like $mutationName ?? $queryName can also be of type null; however, parameter $operationName of ApiPlatform\Core\Metadat...::getGraphqlAttribute() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
        $ioMetadata = $resourceMetadata->getGraphqlAttribute(/** @scrutinizer ignore-type */ $mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
Loading history...
91
        if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
92
            $resourceClass = $ioMetadata['class'];
93
        }
94
95
        $wrapData = !$wrapped && null !== $mutationName && !$input && $depth < 1;
96
97
        $configuration = [
98
            'name' => $shortName,
99
            'description' => $resourceMetadata->getDescription(),
100
            'resolveField' => $this->defaultFieldResolver,
101
            'fields' => function () use ($resourceClass, $resourceMetadata, $input, $mutationName, $queryName, $wrapData, $depth, $ioMetadata) {
102
                if ($wrapData) {
103
                    $queryNormalizationContext = $resourceMetadata->getGraphqlAttribute($queryName ?? '', 'normalization_context', [], true);
104
                    $mutationNormalizationContext = $resourceMetadata->getGraphqlAttribute($mutationName ?? '', 'normalization_context', [], true);
105
                    // Use a new type for the wrapped object only if there is a specific normalization context for the mutation.
106
                    // If not, use the query type in order to ensure the client cache could be used.
107
                    $useWrappedType = $queryNormalizationContext !== $mutationNormalizationContext;
108
109
                    return [
110
                        lcfirst($resourceMetadata->getShortName()) => $useWrappedType ?
111
                            $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, true, $depth) :
112
                            $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName ?? 'item_query', null, true, $depth),
113
                        'clientMutationId' => GraphQLType::string(),
114
                    ];
115
                }
116
117
                $fieldsBuilder = $this->fieldsBuilderLocator->get('api_platform.graphql.fields_builder');
118
119
                $fields = $fieldsBuilder->getResourceObjectTypeFields($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $depth, $ioMetadata);
120
121
                if ($input && null !== $mutationName && null !== $mutationArgs = $resourceMetadata->getGraphql()[$mutationName]['args'] ?? null) {
122
                    return $fieldsBuilder->resolveResourceArgs($mutationArgs, $mutationName, $resourceMetadata->getShortName()) + ['clientMutationId' => $fields['clientMutationId']];
123
                }
124
125
                return $fields;
126
            },
127
            'interfaces' => $wrapData ? [] : [$this->getNodeInterface()],
128
        ];
129
130
        $resourceObjectType = $input ? GraphQLType::nonNull(new InputObjectType($configuration)) : new ObjectType($configuration);
131
        $this->typesContainer->set($shortName, $resourceObjectType);
0 ignored issues
show
Bug introduced by Alan Poulain
It seems like $shortName can also be of type null; however, parameter $id of ApiPlatform\Core\GraphQl...ntainerInterface::set() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
        $this->typesContainer->set(/** @scrutinizer ignore-type */ $shortName, $resourceObjectType);
Loading history...
132
133
        return $resourceObjectType;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function getNodeInterface(): InterfaceType
140
    {
141
        if ($this->typesContainer->has('Node')) {
142
            $nodeInterface = $this->typesContainer->get('Node');
143
            if (!$nodeInterface instanceof InterfaceType) {
144
                throw new \UnexpectedValueException(sprintf('Expected GraphQL type "Node" to be %s.', InterfaceType::class));
145
            }
146
147
            return $nodeInterface;
148
        }
149
150
        $nodeInterface = new InterfaceType([
151
            'name' => 'Node',
152
            'description' => 'A node, according to the Relay specification.',
153
            'fields' => [
154
                'id' => [
155
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
156
                    'description' => 'The id of this node.',
157
                ],
158
            ],
159
            'resolveType' => function ($value) {
160
                if (!isset($value[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
161
                    return null;
162
                }
163
164
                $shortName = (new \ReflectionClass($value[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY]))->getShortName();
165
166
                return $this->typesContainer->has($shortName) ? $this->typesContainer->get($shortName) : null;
167
            },
168
        ]);
169
170
        $this->typesContainer->set('Node', $nodeInterface);
171
172
        return $nodeInterface;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function getResourcePaginatedCollectionType(GraphQLType $resourceType): GraphQLType
179
    {
180
        $shortName = $resourceType->name;
181
182
        if ($this->typesContainer->has("{$shortName}Connection")) {
183
            return $this->typesContainer->get("{$shortName}Connection");
184
        }
185
186
        $edgeObjectTypeConfiguration = [
187
            'name' => "{$shortName}Edge",
188
            'description' => "Edge of $shortName.",
189
            'fields' => [
190
                'node' => $resourceType,
191
                'cursor' => GraphQLType::nonNull(GraphQLType::string()),
192
            ],
193
        ];
194
        $edgeObjectType = new ObjectType($edgeObjectTypeConfiguration);
195
        $this->typesContainer->set("{$shortName}Edge", $edgeObjectType);
196
197
        $pageInfoObjectTypeConfiguration = [
198
            'name' => "{$shortName}PageInfo",
199
            'description' => 'Information about the current page.',
200
            'fields' => [
201
                'endCursor' => GraphQLType::string(),
202
                'startCursor' => GraphQLType::string(),
203
                'hasNextPage' => GraphQLType::nonNull(GraphQLType::boolean()),
204
                'hasPreviousPage' => GraphQLType::nonNull(GraphQLType::boolean()),
205
            ],
206
        ];
207
        $pageInfoObjectType = new ObjectType($pageInfoObjectTypeConfiguration);
208
        $this->typesContainer->set("{$shortName}PageInfo", $pageInfoObjectType);
209
210
        $configuration = [
211
            'name' => "{$shortName}Connection",
212
            'description' => "Connection for $shortName.",
213
            'fields' => [
214
                'edges' => GraphQLType::listOf($edgeObjectType),
215
                'pageInfo' => GraphQLType::nonNull($pageInfoObjectType),
216
                'totalCount' => GraphQLType::nonNull(GraphQLType::int()),
217
            ],
218
        ];
219
220
        $resourcePaginatedCollectionType = new ObjectType($configuration);
221
        $this->typesContainer->set("{$shortName}Connection", $resourcePaginatedCollectionType);
222
223
        return $resourcePaginatedCollectionType;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function isCollection(Type $type): bool
230
    {
231
        return $type->isCollection() && Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType();
232
    }
233
}
234