• 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

92.21
/src/GraphQl/Type/FieldsBuilder.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\Api\ResourceClassResolverInterface;
17
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
18
use ApiPlatform\GraphQl\Type\Definition\TypeInterface;
19
use ApiPlatform\Metadata\GraphQl\Mutation;
20
use ApiPlatform\Metadata\GraphQl\Operation;
21
use ApiPlatform\Metadata\GraphQl\Query;
22
use ApiPlatform\Metadata\GraphQl\Subscription;
23
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
24
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
25
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
26
use ApiPlatform\State\Pagination\Pagination;
27
use ApiPlatform\Util\Inflector;
28
use GraphQL\Type\Definition\InputObjectType;
29
use GraphQL\Type\Definition\ListOfType;
30
use GraphQL\Type\Definition\NonNull;
31
use GraphQL\Type\Definition\NullableType;
32
use GraphQL\Type\Definition\Type as GraphQLType;
33
use GraphQL\Type\Definition\WrappingType;
34
use Psr\Container\ContainerInterface;
35
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
36
use Symfony\Component\PropertyInfo\Type;
37
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
38
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
39

40
/**
41
 * Builds the GraphQL fields.
42
 *
43
 * @author Alan Poulain <contact@alanpoulain.eu>
44
 */
45
final class FieldsBuilder implements FieldsBuilderInterface, FieldsBuilderEnumInterface
46
{
47
    private readonly TypeBuilderEnumInterface|TypeBuilderInterface $typeBuilder;
48

49
    public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, TypeBuilderEnumInterface|TypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $itemResolverFactory, private readonly ResolverFactoryInterface $collectionResolverFactory, private readonly ResolverFactoryInterface $itemMutationResolverFactory, private readonly ResolverFactoryInterface $itemSubscriptionResolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator)
50
    {
51
        if ($typeBuilder instanceof TypeBuilderInterface) {
39✔
52
            @trigger_error(sprintf('$typeBuilder argument of FieldsBuilder implementing "%s" is deprecated since API Platform 3.1. It has to implement "%s" instead.', TypeBuilderInterface::class, TypeBuilderEnumInterface::class), \E_USER_DEPRECATED);
×
53
        }
54
        $this->typeBuilder = $typeBuilder;
39✔
55
    }
56

57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function getNodeQueryFields(): array
61
    {
62
        return [
1✔
63
            'type' => $this->typeBuilder->getNodeInterface(),
1✔
64
            'args' => [
1✔
65
                'id' => ['type' => GraphQLType::nonNull(GraphQLType::id())],
1✔
66
            ],
1✔
67
            'resolve' => ($this->itemResolverFactory)(),
1✔
68
        ];
1✔
69
    }
70

71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function getItemQueryFields(string $resourceClass, Operation $operation, array $configuration): array
75
    {
76
        if ($operation instanceof Query && $operation->getNested()) {
6✔
77
            return [];
1✔
78
        }
79

80
        $fieldName = lcfirst('item_query' === $operation->getName() ? $operation->getShortName() : $operation->getName().$operation->getShortName());
5✔
81

82
        if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $operation->getDescription(), $operation->getDeprecationReason(), new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass, false, $operation)) {
5✔
83
            $args = $this->resolveResourceArgs($configuration['args'] ?? [], $operation);
4✔
84
            $configuration['args'] = $args ?: $configuration['args'] ?? ['id' => ['type' => GraphQLType::nonNull(GraphQLType::id())]];
4✔
85

86
            return [$fieldName => array_merge($fieldConfiguration, $configuration)];
4✔
87
        }
88

89
        return [];
1✔
90
    }
91

92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function getCollectionQueryFields(string $resourceClass, Operation $operation, array $configuration): array
96
    {
97
        if ($operation instanceof Query && $operation->getNested()) {
7✔
98
            return [];
1✔
99
        }
100

101
        $fieldName = lcfirst('collection_query' === $operation->getName() ? $operation->getShortName() : $operation->getName().$operation->getShortName());
6✔
102

103
        if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $operation->getDescription(), $operation->getDeprecationReason(), new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass, false, $operation)) {
6✔
104
            $args = $this->resolveResourceArgs($configuration['args'] ?? [], $operation);
5✔
105
            $configuration['args'] = $args ?: $configuration['args'] ?? $fieldConfiguration['args'];
5✔
106

107
            return [Inflector::pluralize($fieldName) => array_merge($fieldConfiguration, $configuration)];
5✔
108
        }
109

110
        return [];
1✔
111
    }
112

113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function getMutationFields(string $resourceClass, Operation $operation): array
117
    {
118
        $mutationFields = [];
2✔
119
        $resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass);
2✔
120
        $description = $operation->getDescription() ?? ucfirst("{$operation->getName()}s a {$operation->getShortName()}.");
2✔
121

122
        if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $operation->getDeprecationReason(), $resourceType, $resourceClass, false, $operation)) {
2✔
123
            $fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, null, $operation->getDeprecationReason(), $resourceType, $resourceClass, true, $operation)];
2✔
124
        }
125

126
        $mutationFields[$operation->getName().$operation->getShortName()] = $fieldConfiguration ?? [];
2✔
127

128
        return $mutationFields;
2✔
129
    }
130

131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function getSubscriptionFields(string $resourceClass, Operation $operation): array
135
    {
136
        $subscriptionFields = [];
3✔
137
        $resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass);
3✔
138
        $description = $operation->getDescription() ?? sprintf('Subscribes to the action event of a %s.', $operation->getShortName());
3✔
139

140
        if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $operation->getDeprecationReason(), $resourceType, $resourceClass, false, $operation)) {
3✔
141
            $fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, null, $operation->getDeprecationReason(), $resourceType, $resourceClass, true, $operation)];
2✔
142
        }
143

144
        if (!$fieldConfiguration) {
3✔
145
            return [];
1✔
146
        }
147

148
        $subscriptionName = $operation->getName();
2✔
149
        // TODO: 3.0 change this
150
        if ('update_subscription' === $subscriptionName) {
2✔
151
            $subscriptionName = 'update';
×
152
        }
153

154
        $subscriptionFields[$subscriptionName.$operation->getShortName().'Subscribe'] = $fieldConfiguration;
2✔
155

156
        return $subscriptionFields;
2✔
157
    }
158

159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function getResourceObjectTypeFields(?string $resourceClass, Operation $operation, bool $input, int $depth = 0, ?array $ioMetadata = null): array
163
    {
164
        $fields = [];
16✔
165
        $idField = ['type' => GraphQLType::nonNull(GraphQLType::id())];
16✔
166
        $optionalIdField = ['type' => GraphQLType::id()];
16✔
167
        $clientMutationId = GraphQLType::string();
16✔
168
        $clientSubscriptionId = GraphQLType::string();
16✔
169

170
        if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null === $ioMetadata['class']) {
16✔
171
            if ($input) {
2✔
172
                return ['clientMutationId' => $clientMutationId];
1✔
173
            }
174

175
            return [];
1✔
176
        }
177

178
        if ($operation instanceof Subscription && $input) {
14✔
179
            return [
1✔
180
                'id' => $idField,
1✔
181
                'clientSubscriptionId' => $clientSubscriptionId,
1✔
182
            ];
1✔
183
        }
184

185
        if ('delete' === $operation->getName()) {
13✔
186
            $fields = [
1✔
187
                'id' => $idField,
1✔
188
            ];
1✔
189

190
            if ($input) {
1✔
191
                $fields['clientMutationId'] = $clientMutationId;
1✔
192
            }
193

194
            return $fields;
1✔
195
        }
196

197
        if (!$input || 'create' !== $operation->getName()) {
12✔
198
            $fields['id'] = $idField;
11✔
199
        }
200
        if ($input && $depth >= 1) {
12✔
201
            $fields['id'] = $optionalIdField;
1✔
202
        }
203

204
        ++$depth; // increment the depth for the call to getResourceFieldConfiguration.
12✔
205

206
        if (null !== $resourceClass) {
12✔
207
            foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
12✔
208
                $context = [
12✔
209
                    'normalization_groups' => $operation->getNormalizationContext()['groups'] ?? null,
12✔
210
                    'denormalization_groups' => $operation->getDenormalizationContext()['groups'] ?? null,
12✔
211
                ];
12✔
212
                $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $context);
12✔
213

214
                if (
215
                    null === ($propertyType = $propertyMetadata->getBuiltinTypes()[0] ?? null)
12✔
216
                    || (!$input && false === $propertyMetadata->isReadable())
12✔
217
                    || ($input && $operation instanceof Mutation && false === $propertyMetadata->isWritable())
12✔
218
                ) {
219
                    continue;
5✔
220
                }
221

222
                if ($fieldConfiguration = $this->getResourceFieldConfiguration($property, $propertyMetadata->getDescription(), $propertyMetadata->getDeprecationReason(), $propertyType, $resourceClass, $input, $operation, $depth, null !== $propertyMetadata->getSecurity())) {
12✔
223
                    $fields['id' === $property ? '_id' : $this->normalizePropertyName($property, $resourceClass)] = $fieldConfiguration;
11✔
224
                }
225
            }
226
        }
227

228
        if ($operation instanceof Mutation && $input) {
12✔
229
            $fields['clientMutationId'] = $clientMutationId;
4✔
230
        }
231

232
        return $fields;
12✔
233
    }
234

235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getEnumFields(string $enumClass): array
239
    {
240
        $rEnum = new \ReflectionEnum($enumClass);
1✔
241

242
        $enumCases = [];
1✔
243
        foreach ($rEnum->getCases() as $rCase) {
1✔
244
            $enumCase = ['value' => $rCase->getBackingValue()];
1✔
245
            $propertyMetadata = $this->propertyMetadataFactory->create($enumClass, $rCase->getName());
1✔
246
            if ($enumCaseDescription = $propertyMetadata->getDescription()) {
1✔
247
                $enumCase['description'] = $enumCaseDescription;
1✔
248
            }
249
            $enumCases[$rCase->getName()] = $enumCase;
1✔
250
        }
251

252
        return $enumCases;
1✔
253
    }
254

255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function resolveResourceArgs(array $args, Operation $operation): array
259
    {
260
        foreach ($args as $id => $arg) {
12✔
261
            if (!isset($arg['type'])) {
4✔
262
                throw new \InvalidArgumentException(sprintf('The argument "%s" of the custom operation "%s" in %s needs a "type" option.', $id, $operation->getName(), $operation->getShortName()));
1✔
263
            }
264

265
            $args[$id]['type'] = $this->typeConverter->resolveType($arg['type']);
3✔
266
        }
267

268
        return $args;
11✔
269
    }
270

271
    /**
272
     * Get the field configuration of a resource.
273
     *
274
     * @see http://webonyx.github.io/graphql-php/type-system/object-types/
275
     */
276
    private function getResourceFieldConfiguration(?string $property, ?string $fieldDescription, ?string $deprecationReason, Type $type, string $rootResource, bool $input, Operation $rootOperation, int $depth = 0, bool $forceNullable = false): ?array
277
    {
278
        try {
279
            $isCollectionType = $this->typeBuilder->isCollection($type);
28✔
280

281
            if (
282
                $isCollectionType &&
28✔
283
                $collectionValueType = $type->getCollectionValueTypes()[0] ?? null
28✔
284
            ) {
285
                $resourceClass = $collectionValueType->getClassName();
6✔
286
            } else {
287
                $resourceClass = $type->getClassName();
22✔
288
            }
289

290
            $resourceOperation = $rootOperation;
28✔
291
            if ($resourceClass && $rootOperation->getClass() && $this->resourceClassResolver->isResourceClass($resourceClass) && $rootOperation->getClass() !== $resourceClass) {
28✔
292
                $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
1✔
293
                $resourceOperation = $resourceMetadataCollection->getOperation($isCollectionType ? 'collection_query' : 'item_query');
1✔
294
            }
295

296
            if (!$resourceOperation instanceof Operation) {
28✔
297
                throw new \LogicException('The resource operation should be a GraphQL operation.');
×
298
            }
299

300
            $graphqlType = $this->convertType($type, $input, $resourceOperation, $rootOperation, $resourceClass ?? '', $rootResource, $property, $depth, $forceNullable);
28✔
301

302
            $graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType(true) : $graphqlType;
25✔
303
            $isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true);
25✔
304
            if ($isStandardGraphqlType) {
25✔
305
                $resourceClass = '';
13✔
306
            }
307

308
            // Check mercure attribute if it's a subscription at the root level.
309
            if ($rootOperation instanceof Subscription && null === $property && !$rootOperation->getMercure()) {
25✔
310
                return null;
1✔
311
            }
312

313
            $args = [];
24✔
314

315
            if (!$input && !$rootOperation instanceof Mutation && !$rootOperation instanceof Subscription && !$isStandardGraphqlType && $isCollectionType) {
24✔
316
                if ($this->pagination->isGraphQlEnabled($resourceOperation)) {
5✔
317
                    $args = $this->getGraphQlPaginationArgs($resourceOperation);
5✔
318
                }
319

320
                $args = $this->getFilterArgs($args, $resourceClass, $rootResource, $resourceOperation, $rootOperation, $property, $depth);
5✔
321
            }
322

323
            if ($isStandardGraphqlType || $input) {
24✔
324
                $resolve = null;
17✔
325
            } elseif (($rootOperation instanceof Mutation || $rootOperation instanceof Subscription) && $depth <= 0) {
12✔
326
                $resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resourceOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
4✔
327
            } elseif ($this->typeBuilder->isCollection($type)) {
8✔
328
                $resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
5✔
329
            } else {
330
                $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation);
3✔
331
            }
332

333
            return [
24✔
334
                'type' => $graphqlType,
24✔
335
                'description' => $fieldDescription,
24✔
336
                'args' => $args,
24✔
337
                'resolve' => $resolve,
24✔
338
                'deprecationReason' => $deprecationReason,
24✔
339
            ];
24✔
340
        } catch (InvalidTypeException) {
3✔
341
            // just ignore invalid types
342
        }
343

344
        return null;
3✔
345
    }
346

347
    private function getGraphQlPaginationArgs(Operation $queryOperation): array
348
    {
349
        $paginationType = $this->pagination->getGraphQlPaginationType($queryOperation);
5✔
350

351
        if ('cursor' === $paginationType) {
5✔
352
            return [
4✔
353
                'first' => [
4✔
354
                    'type' => GraphQLType::int(),
4✔
355
                    'description' => 'Returns the first n elements from the list.',
4✔
356
                ],
4✔
357
                'last' => [
4✔
358
                    'type' => GraphQLType::int(),
4✔
359
                    'description' => 'Returns the last n elements from the list.',
4✔
360
                ],
4✔
361
                'before' => [
4✔
362
                    'type' => GraphQLType::string(),
4✔
363
                    'description' => 'Returns the elements in the list that come before the specified cursor.',
4✔
364
                ],
4✔
365
                'after' => [
4✔
366
                    'type' => GraphQLType::string(),
4✔
367
                    'description' => 'Returns the elements in the list that come after the specified cursor.',
4✔
368
                ],
4✔
369
            ];
4✔
370
        }
371

372
        $paginationOptions = $this->pagination->getOptions();
1✔
373

374
        $args = [
1✔
375
            $paginationOptions['page_parameter_name'] => [
1✔
376
                'type' => GraphQLType::int(),
1✔
377
                'description' => 'Returns the current page.',
1✔
378
            ],
1✔
379
        ];
1✔
380

381
        if ($paginationOptions['client_items_per_page']) {
1✔
382
            $args[$paginationOptions['items_per_page_parameter_name']] = [
×
383
                'type' => GraphQLType::int(),
×
384
                'description' => 'Returns the number of items per page.',
×
385
            ];
×
386
        }
387

388
        return $args;
1✔
389
    }
390

391
    private function getFilterArgs(array $args, ?string $resourceClass, string $rootResource, Operation $resourceOperation, Operation $rootOperation, ?string $property, int $depth): array
392
    {
393
        if (null === $resourceClass) {
5✔
394
            return $args;
×
395
        }
396

397
        foreach ($resourceOperation->getFilters() ?? [] as $filterId) {
5✔
398
            if (!$this->filterLocator->has($filterId)) {
2✔
399
                continue;
×
400
            }
401

402
            foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) {
2✔
403
                $nullable = isset($value['required']) ? !$value['required'] : true;
2✔
404
                $filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']);
2✔
405
                $graphqlFilterType = $this->convertType($filterType, false, $resourceOperation, $rootOperation, $resourceClass, $rootResource, $property, $depth);
2✔
406

407
                if (str_ends_with($key, '[]')) {
2✔
408
                    $graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
2✔
409
                    $key = substr($key, 0, -2).'_list';
2✔
410
                }
411

412
                /** @var string $key */
413
                $key = str_replace('.', $this->nestingSeparator, $key);
2✔
414

415
                parse_str($key, $parsed);
2✔
416
                if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) {
2✔
417
                    $parsed = [$key => ''];
×
418
                }
419
                array_walk_recursive($parsed, static function (&$value) use ($graphqlFilterType): void {
2✔
420
                    $value = $graphqlFilterType;
2✔
421
                });
2✔
422
                $args = $this->mergeFilterArgs($args, $parsed, $resourceOperation, $key);
2✔
423
            }
424
        }
425

426
        return $this->convertFilterArgsToTypes($args);
5✔
427
    }
428

429
    private function mergeFilterArgs(array $args, array $parsed, ?Operation $operation = null, string $original = ''): array
430
    {
431
        foreach ($parsed as $key => $value) {
2✔
432
            // Never override keys that cannot be merged
433
            if (isset($args[$key]) && !\is_array($args[$key])) {
2✔
434
                continue;
×
435
            }
436

437
            if (\is_array($value)) {
2✔
438
                $value = $this->mergeFilterArgs($args[$key] ?? [], $value);
2✔
439
                if (!isset($value['#name'])) {
2✔
440
                    $name = (false === $pos = strrpos($original, '[')) ? $original : substr($original, 0, (int) $pos);
2✔
441
                    $value['#name'] = ($operation ? $operation->getShortName() : '').'Filter_'.strtr($name, ['[' => '_', ']' => '', '.' => '__']);
2✔
442
                }
443
            }
444

445
            $args[$key] = $value;
2✔
446
        }
447

448
        return $args;
2✔
449
    }
450

451
    private function convertFilterArgsToTypes(array $args): array
452
    {
453
        foreach ($args as $key => $value) {
5✔
454
            if (strpos($key, '.')) {
5✔
455
                // Declare relations/nested fields in a GraphQL compatible syntax.
456
                $args[str_replace('.', $this->nestingSeparator, $key)] = $value;
×
457
                unset($args[$key]);
×
458
            }
459
        }
460

461
        foreach ($args as $key => $value) {
5✔
462
            if (!\is_array($value) || !isset($value['#name'])) {
5✔
463
                continue;
5✔
464
            }
465

466
            $name = $value['#name'];
2✔
467

468
            if ($this->typesContainer->has($name)) {
2✔
469
                $args[$key] = $this->typesContainer->get($name);
×
470
                continue;
×
471
            }
472

473
            unset($value['#name']);
2✔
474

475
            $filterArgType = GraphQLType::listOf(new InputObjectType([
2✔
476
                'name' => $name,
2✔
477
                'fields' => $this->convertFilterArgsToTypes($value),
2✔
478
            ]));
2✔
479

480
            $this->typesContainer->set($name, $filterArgType);
2✔
481

482
            $args[$key] = $filterArgType;
2✔
483
        }
484

485
        return $args;
5✔
486
    }
487

488
    /**
489
     * Converts a built-in type to its GraphQL equivalent.
490
     *
491
     * @throws InvalidTypeException
492
     */
493
    private function convertType(Type $type, bool $input, Operation $resourceOperation, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth, bool $forceNullable = false): GraphQLType|ListOfType|NonNull
494
    {
495
        $graphqlType = $this->typeConverter->convertType($type, $input, $rootOperation, $resourceClass, $rootResource, $property, $depth);
28✔
496

497
        if (null === $graphqlType) {
28✔
498
            throw new InvalidTypeException(sprintf('The type "%s" is not supported.', $type->getBuiltinType()));
3✔
499
        }
500

501
        if (\is_string($graphqlType)) {
26✔
502
            if (!$this->typesContainer->has($graphqlType)) {
1✔
503
                throw new InvalidTypeException(sprintf('The GraphQL type %s is not valid. Valid types are: %s. Have you registered this type by implementing %s?', $graphqlType, implode(', ', array_keys($this->typesContainer->all())), TypeInterface::class));
1✔
504
            }
505

506
            $graphqlType = $this->typesContainer->get($graphqlType);
×
507
        }
508

509
        if ($this->typeBuilder->isCollection($type)) {
25✔
510
            if (!$input && $this->pagination->isGraphQlEnabled($resourceOperation)) {
5✔
511
                // Deprecated path, to remove in API Platform 4.
512
                if ($this->typeBuilder instanceof TypeBuilderInterface) {
5✔
513
                    return $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $resourceOperation);
×
514
                }
515

516
                return $this->typeBuilder->getPaginatedCollectionType($graphqlType, $resourceOperation);
5✔
517
            }
518

519
            return GraphQLType::listOf($graphqlType);
×
520
        }
521

522
        return $forceNullable || !$graphqlType instanceof NullableType || $type->isNullable() || ($rootOperation instanceof Mutation && 'update' === $rootOperation->getName())
20✔
523
            ? $graphqlType
11✔
524
            : GraphQLType::nonNull($graphqlType);
20✔
525
    }
526

527
    private function normalizePropertyName(string $property, string $resourceClass): string
528
    {
529
        if (null === $this->nameConverter) {
11✔
530
            return $property;
×
531
        }
532
        if ($this->nameConverter instanceof AdvancedNameConverterInterface) {
11✔
533
            return $this->nameConverter->normalize($property, $resourceClass);
1✔
534
        }
535

536
        return $this->nameConverter->normalize($property);
10✔
537
    }
538
}
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