TypeBuilder::getResourceObjectType()   F
last analyzed

Complexity

Conditions 35
Paths 6720

Size

Total Lines 94
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 35
eloc 56
c 1
b 0
f 0
nc 6720
nop 8
dl 0
loc 94
rs 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

92
        $ioMetadata = $resourceMetadata->getGraphqlAttribute(/** @scrutinizer ignore-type */ $subscriptionName ?? $mutationName ?? $queryName, $input ? 'input' : 'output', null, true);
Loading history...
93
        if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
94
            $resourceClass = $ioMetadata['class'];
95
        }
96
97
        $wrapData = !$wrapped && (null !== $mutationName || null !== $subscriptionName) && !$input && $depth < 1;
98
99
        $configuration = [
100
            'name' => $shortName,
101
            'description' => $resourceMetadata->getDescription(),
102
            'resolveField' => $this->defaultFieldResolver,
103
            'fields' => function () use ($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, $wrapData, $depth, $ioMetadata) {
104
                if ($wrapData) {
105
                    $queryNormalizationContext = $resourceMetadata->getGraphqlAttribute($queryName ?? '', 'normalization_context', [], true);
106
                    $mutationNormalizationContext = $resourceMetadata->getGraphqlAttribute($mutationName ?? $subscriptionName ?? '', 'normalization_context', [], true);
107
                    // Use a new type for the wrapped object only if there is a specific normalization context for the mutation or the subscription.
108
                    // If not, use the query type in order to ensure the client cache could be used.
109
                    $useWrappedType = $queryNormalizationContext !== $mutationNormalizationContext;
110
111
                    $fields = [
112
                        lcfirst($resourceMetadata->getShortName()) => $useWrappedType ?
113
                            $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, true, $depth) :
114
                            $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName ?? 'item_query', null, null, true, $depth),
115
                    ];
116
117
                    if (null !== $subscriptionName) {
118
                        $fields['clientSubscriptionId'] = GraphQLType::string();
119
                        if ($resourceMetadata->getAttribute('mercure', false)) {
120
                            $fields['mercureUrl'] = GraphQLType::string();
121
                        }
122
123
                        return $fields;
124
                    }
125
126
                    return $fields + ['clientMutationId' => GraphQLType::string()];
127
                }
128
129
                $fieldsBuilder = $this->fieldsBuilderLocator->get('api_platform.graphql.fields_builder');
130
131
                $fields = $fieldsBuilder->getResourceObjectTypeFields($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, $depth, $ioMetadata);
132
133
                if ($input && null !== $mutationName && null !== $mutationArgs = $resourceMetadata->getGraphql()[$mutationName]['args'] ?? null) {
134
                    return $fieldsBuilder->resolveResourceArgs($mutationArgs, $mutationName, $resourceMetadata->getShortName()) + ['clientMutationId' => $fields['clientMutationId']];
135
                }
136
137
                return $fields;
138
            },
139
            'interfaces' => $wrapData ? [] : [$this->getNodeInterface()],
140
        ];
141
142
        $resourceObjectType = $input ? GraphQLType::nonNull(new InputObjectType($configuration)) : new ObjectType($configuration);
143
        $this->typesContainer->set($shortName, $resourceObjectType);
0 ignored issues
show
Bug introduced by
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

143
        $this->typesContainer->set(/** @scrutinizer ignore-type */ $shortName, $resourceObjectType);
Loading history...
144
145
        return $resourceObjectType;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getNodeInterface(): InterfaceType
152
    {
153
        if ($this->typesContainer->has('Node')) {
154
            $nodeInterface = $this->typesContainer->get('Node');
155
            if (!$nodeInterface instanceof InterfaceType) {
156
                throw new \LogicException(sprintf('Expected GraphQL type "Node" to be %s.', InterfaceType::class));
157
            }
158
159
            return $nodeInterface;
160
        }
161
162
        $nodeInterface = new InterfaceType([
163
            'name' => 'Node',
164
            'description' => 'A node, according to the Relay specification.',
165
            'fields' => [
166
                'id' => [
167
                    'type' => GraphQLType::nonNull(GraphQLType::id()),
168
                    'description' => 'The id of this node.',
169
                ],
170
            ],
171
            'resolveType' => function ($value) {
172
                if (!isset($value[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
173
                    return null;
174
                }
175
176
                $shortName = (new \ReflectionClass($value[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY]))->getShortName();
177
178
                return $this->typesContainer->has($shortName) ? $this->typesContainer->get($shortName) : null;
179
            },
180
        ]);
181
182
        $this->typesContainer->set('Node', $nodeInterface);
183
184
        return $nodeInterface;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function getResourcePaginatedCollectionType(GraphQLType $resourceType, string $resourceClass, string $operationName): GraphQLType
191
    {
192
        $shortName = $resourceType->name;
193
194
        if ($this->typesContainer->has("{$shortName}Connection")) {
195
            return $this->typesContainer->get("{$shortName}Connection");
196
        }
197
198
        $paginationType = $this->pagination->getGraphQlPaginationType($resourceClass, $operationName);
199
200
        $fields = 'cursor' === $paginationType ?
201
            $this->getCursorBasedPaginationFields($resourceType) :
202
            $this->getPageBasedPaginationFields($resourceType);
203
204
        $configuration = [
205
            'name' => "{$shortName}Connection",
206
            'description' => "Connection for $shortName.",
207
            'fields' => $fields,
208
        ];
209
210
        $resourcePaginatedCollectionType = new ObjectType($configuration);
211
        $this->typesContainer->set("{$shortName}Connection", $resourcePaginatedCollectionType);
212
213
        return $resourcePaginatedCollectionType;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function isCollection(Type $type): bool
220
    {
221
        return $type->isCollection() && ($collectionValueType = $type->getCollectionValueType()) && null !== $collectionValueType->getClassName();
222
    }
223
224
    private function getCursorBasedPaginationFields(GraphQLType $resourceType): array
225
    {
226
        $shortName = $resourceType->name;
227
228
        $edgeObjectTypeConfiguration = [
229
            'name' => "{$shortName}Edge",
230
            'description' => "Edge of $shortName.",
231
            'fields' => [
232
                'node' => $resourceType,
233
                'cursor' => GraphQLType::nonNull(GraphQLType::string()),
234
            ],
235
        ];
236
        $edgeObjectType = new ObjectType($edgeObjectTypeConfiguration);
237
        $this->typesContainer->set("{$shortName}Edge", $edgeObjectType);
238
239
        $pageInfoObjectTypeConfiguration = [
240
            'name' => "{$shortName}PageInfo",
241
            'description' => 'Information about the current page.',
242
            'fields' => [
243
                'endCursor' => GraphQLType::string(),
244
                'startCursor' => GraphQLType::string(),
245
                'hasNextPage' => GraphQLType::nonNull(GraphQLType::boolean()),
246
                'hasPreviousPage' => GraphQLType::nonNull(GraphQLType::boolean()),
247
            ],
248
        ];
249
        $pageInfoObjectType = new ObjectType($pageInfoObjectTypeConfiguration);
250
        $this->typesContainer->set("{$shortName}PageInfo", $pageInfoObjectType);
251
252
        return [
253
            'edges' => GraphQLType::listOf($edgeObjectType),
254
            'pageInfo' => GraphQLType::nonNull($pageInfoObjectType),
255
            'totalCount' => GraphQLType::nonNull(GraphQLType::int()),
256
        ];
257
    }
258
259
    private function getPageBasedPaginationFields(GraphQLType $resourceType): array
260
    {
261
        $shortName = $resourceType->name;
262
263
        $paginationInfoObjectTypeConfiguration = [
264
            'name' => "{$shortName}PaginationInfo",
265
            'description' => 'Information about the pagination.',
266
            'fields' => [
267
                'itemsPerPage' => GraphQLType::nonNull(GraphQLType::int()),
268
                'lastPage' => GraphQLType::nonNull(GraphQLType::int()),
269
                'totalCount' => GraphQLType::nonNull(GraphQLType::int()),
270
            ],
271
        ];
272
        $paginationInfoObjectType = new ObjectType($paginationInfoObjectTypeConfiguration);
273
        $this->typesContainer->set("{$shortName}PaginationInfo", $paginationInfoObjectType);
274
275
        return [
276
            'collection' => GraphQLType::listOf($resourceType),
277
            'paginationInfo' => GraphQLType::nonNull($paginationInfoObjectType),
278
        ];
279
    }
280
}
281