• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

overblog / GraphQLBundle / 21577605669

29 Jan 2026 07:39AM UTC coverage: 98.546%. Remained the same
21577605669

push

github

web-flow
Test on PHP 8.5 (#1237)

* Test on PHP 8.5

* fix remove dummy attribute

4541 of 4608 relevant lines covered (98.55%)

76.57 hits per line

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

97.67
/src/Config/Parser/MetadataParser/MetadataParser.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Overblog\GraphQLBundle\Config\Parser\MetadataParser;
6

7
use Doctrine\Common\Annotations\AnnotationException;
8
use GraphQL\Type\Definition\ResolveInfo;
9
use Overblog\GraphQLBundle\Annotation\Annotation as Meta;
10
use Overblog\GraphQLBundle\Annotation as Metadata;
11
use Overblog\GraphQLBundle\Annotation\InputField;
12
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\DocBlockTypeGuesser;
13
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\DoctrineTypeGuesser;
14
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\TypeGuessingException;
15
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\TypeHintTypeGuesser;
16
use Overblog\GraphQLBundle\Config\Parser\PreParserInterface;
17
use Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface;
18
use Overblog\GraphQLBundle\Relay\Connection\EdgeInterface;
19
use Overblog\GraphQLBundle\Transformer\ArgumentsTransformer;
20
use ReflectionClass;
21
use ReflectionClassConstant;
22
use ReflectionException;
23
use ReflectionMethod;
24
use ReflectionNamedType;
25
use ReflectionParameter;
26
use ReflectionProperty;
27
use Reflector;
28
use RuntimeException;
29
use SplFileInfo;
30
use Symfony\Component\Config\Resource\FileResource;
31
use Symfony\Component\DependencyInjection\ContainerBuilder;
32
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
33

34
use function array_filter;
35
use function array_keys;
36
use function array_map;
37
use function array_unshift;
38
use function current;
39
use function file_get_contents;
40
use function implode;
41
use function in_array;
42
use function is_string;
43
use function preg_match;
44
use function sprintf;
45
use function str_replace;
46
use function strlen;
47
use function substr;
48
use function trim;
49

50
use const PHP_VERSION_ID;
51

52
abstract class MetadataParser implements PreParserInterface
53
{
54
    public const ANNOTATION_NAMESPACE = 'Overblog\GraphQLBundle\Annotation\\';
55
    public const METADATA_FORMAT = '%s';
56

57
    private static ClassesTypesMap $map;
58
    private static array $typeGuessers = [];
59
    private static array $providers = [];
60
    private static array $reflections = [];
61

62
    private const GQL_SCALAR = 'scalar';
63
    private const GQL_ENUM = 'enum';
64
    private const GQL_TYPE = 'type';
65
    private const GQL_INPUT = 'input';
66
    private const GQL_UNION = 'union';
67
    private const GQL_INTERFACE = 'interface';
68

69
    /**
70
     * @see https://facebook.github.io/graphql/draft/#sec-Input-and-Output-Types
71
     */
72
    private const VALID_INPUT_TYPES = [self::GQL_SCALAR, self::GQL_ENUM, self::GQL_INPUT];
73
    private const VALID_OUTPUT_TYPES = [self::GQL_SCALAR, self::GQL_TYPE, self::GQL_INTERFACE, self::GQL_UNION, self::GQL_ENUM];
74

75
    /**
76
     * {@inheritdoc}
77
     *
78
     * @throws InvalidArgumentException
79
     * @throws ReflectionException
80
     */
81
    public static function preParse(SplFileInfo $file, ContainerBuilder $container, array $configs = []): void
82
    {
83
        $container->setParameter('overblog_graphql_types.classes_map', self::processFile($file, $container, $configs, true));
103✔
84
    }
85

86
    /**
87
     * @throws InvalidArgumentException
88
     * @throws ReflectionException
89
     */
90
    public static function parse(SplFileInfo $file, ContainerBuilder $container, array $configs = []): array
91
    {
92
        return self::processFile($file, $container, $configs, false);
103✔
93
    }
94

95
    public static function finalize(ContainerBuilder $container): void
96
    {
97
        $parameter = 'overblog_graphql_types.interfaces_map';
88✔
98
        $value = $container->hasParameter($parameter) ? $container->getParameter($parameter) : [];
88✔
99
        foreach (self::$map->interfacesToArray() as $interface => $types) {
88✔
100
            /** @phpstan-ignore-next-line */
101
            if (!isset($value[$interface])) {
3✔
102
                /** @phpstan-ignore-next-line */
103
                $value[$interface] = [];
3✔
104
            }
105
            foreach ($types as $className => $typeName) {
3✔
106
                $value[$interface][$className] = $typeName;
3✔
107
            }
108
        }
109

110
        $container->setParameter('overblog_graphql_types.interfaces_map', $value);
88✔
111
    }
112

113
    /**
114
     * @internal
115
     */
116
    public static function reset(array $configs): void
117
    {
118
        self::$map = new ClassesTypesMap();
188✔
119
        self::$typeGuessers = [
188✔
120
            new DocBlockTypeGuesser(self::$map),
188✔
121
            new TypeHintTypeGuesser(self::$map),
188✔
122
            new DoctrineTypeGuesser(self::$map, $configs['doctrine']['types_mapping']),
188✔
123
        ];
188✔
124
        self::$providers = [];
188✔
125
        self::$reflections = [];
188✔
126
    }
127

128
    /**
129
     * Process a file.
130
     *
131
     * @throws InvalidArgumentException|ReflectionException|AnnotationException
132
     */
133
    private static function processFile(SplFileInfo $file, ContainerBuilder $container, array $configs, bool $preProcess): array
134
    {
135
        $container->addResource(new FileResource($file->getRealPath()));
103✔
136

137
        try {
138
            $className = $file->getBasename('.php');
103✔
139
            if (preg_match('#namespace (.+);#', file_get_contents($file->getRealPath()), $matches)) {
103✔
140
                $className = trim($matches[1]).'\\'.$className;
103✔
141
            }
142

143
            $gqlTypes = [];
103✔
144
            /** @phpstan-ignore-next-line */
145
            $reflectionClass = self::getClassReflection($className);
103✔
146

147
            foreach (static::getMetadatas($reflectionClass) as $classMetadata) {
103✔
148
                if ($classMetadata instanceof Meta) {
103✔
149
                    $gqlTypes = self::classMetadatasToGQLConfiguration(
103✔
150
                        $reflectionClass,
103✔
151
                        $classMetadata,
103✔
152
                        $configs,
103✔
153
                        $gqlTypes,
103✔
154
                        $preProcess
103✔
155
                    );
103✔
156
                }
157
            }
158

159
            return $preProcess ? self::$map->classesToArray() : $gqlTypes;
103✔
160
        } catch (ReflectionException $e) {
44✔
161
            return $gqlTypes;
4✔
162
        } catch (\InvalidArgumentException $e) {
40✔
163
            throw new InvalidArgumentException(sprintf('Failed to parse GraphQL metadata from file "%s".', $file), $e->getCode(), $e);
40✔
164
        }
165
    }
166

167
    /**
168
     * @return array<string,array>
169
     */
170
    private static function classMetadatasToGQLConfiguration(
171
        ReflectionClass $reflectionClass,
172
        Meta $classMetadata,
173
        array $configs,
174
        array $gqlTypes,
175
        bool $preProcess
176
    ): array {
177
        $gqlConfiguration = $gqlType = $gqlName = null;
103✔
178

179
        switch (true) {
180
            case $classMetadata instanceof Metadata\Type:
103✔
181
                $gqlType = self::GQL_TYPE;
103✔
182
                $gqlName = $classMetadata->name ?? $reflectionClass->getShortName();
103✔
183
                if (!$preProcess) {
103✔
184
                    $gqlConfiguration = self::typeMetadataToGQLConfiguration($reflectionClass, $classMetadata, $gqlName, $configs);
103✔
185

186
                    if ($classMetadata instanceof Metadata\Relay\Connection) {
103✔
187
                        if (!$reflectionClass->implementsInterface(ConnectionInterface::class)) {
98✔
188
                            throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" can only be used on class implementing the ConnectionInterface.', self::formatMetadata('Connection'), $reflectionClass->getName()));
×
189
                        }
190

191
                        if (!(isset($classMetadata->edge) xor isset($classMetadata->node))) {
98✔
192
                            throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" is invalid. You must define either the "edge" OR the "node" attribute, but not both.', self::formatMetadata('Connection'), $reflectionClass->getName()));
×
193
                        }
194

195
                        $edgeType = $classMetadata->edge ?? false;
98✔
196
                        if (!$edgeType) {
98✔
197
                            $edgeType = $gqlName.'Edge';
98✔
198
                            $gqlTypes[$edgeType] = [
98✔
199
                                'type' => 'object',
98✔
200
                                'config' => [
98✔
201
                                    'builders' => [
98✔
202
                                        ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classMetadata->node]],
98✔
203
                                    ],
98✔
204
                                ],
98✔
205
                            ];
98✔
206
                        }
207

208
                        if (!isset($gqlConfiguration['config']['builders'])) {
98✔
209
                            $gqlConfiguration['config']['builders'] = [];
98✔
210
                        }
211

212
                        array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-connection', 'builderConfig' => ['edgeType' => $edgeType]]);
98✔
213
                    }
214

215
                    $interfaces = $gqlConfiguration['config']['interfaces'] ?? [];
103✔
216
                    foreach ($interfaces as $interface) {
103✔
217
                        self::$map->addInterfaceType($interface, $gqlName, $reflectionClass->getName());
101✔
218
                    }
219
                }
220
                break;
103✔
221

222
            case $classMetadata instanceof Metadata\Input:
101✔
223
                $gqlType = self::GQL_INPUT;
98✔
224
                $gqlName = $classMetadata->name ?? self::suffixName($reflectionClass->getShortName(), 'Input');
98✔
225
                if (!$preProcess) {
98✔
226
                    $gqlConfiguration = self::inputMetadataToGQLConfiguration($reflectionClass, $classMetadata);
98✔
227
                }
228
                break;
98✔
229

230
            case $classMetadata instanceof Metadata\Scalar:
101✔
231
                $gqlType = self::GQL_SCALAR;
98✔
232
                if (!$preProcess) {
98✔
233
                    $gqlConfiguration = self::scalarMetadataToGQLConfiguration($reflectionClass, $classMetadata);
98✔
234
                }
235
                break;
98✔
236

237
            case $classMetadata instanceof Metadata\Enum:
101✔
238
                $gqlType = self::GQL_ENUM;
98✔
239
                if (!$preProcess) {
98✔
240
                    $gqlConfiguration = self::enumMetadataToGQLConfiguration($reflectionClass, $classMetadata);
98✔
241
                }
242
                break;
98✔
243

244
            case $classMetadata instanceof Metadata\Union:
101✔
245
                $gqlType = self::GQL_UNION;
98✔
246
                if (!$preProcess) {
98✔
247
                    $gqlConfiguration = self::unionMetadataToGQLConfiguration($reflectionClass, $classMetadata);
98✔
248
                }
249
                break;
98✔
250

251
            case $classMetadata instanceof Metadata\TypeInterface:
101✔
252
                $gqlType = self::GQL_INTERFACE;
101✔
253
                if (!$preProcess) {
101✔
254
                    $gqlName = !empty($classMetadata->name) ? $classMetadata->name : $reflectionClass->getShortName();
101✔
255
                    $gqlConfiguration = self::typeInterfaceMetadataToGQLConfiguration($reflectionClass, $classMetadata, $gqlName);
101✔
256
                }
257
                break;
101✔
258

259
            case $classMetadata instanceof Metadata\Provider:
101✔
260
                if ($preProcess) {
101✔
261
                    self::$providers[] = ['reflectionClass' => $reflectionClass, 'metadata' => $classMetadata];
101✔
262
                }
263

264
                return [];
101✔
265
        }
266

267
        if (null !== $gqlType) {
103✔
268
            if (!$gqlName) {
103✔
269
                $gqlName = !empty($classMetadata->name) ? $classMetadata->name : $reflectionClass->getShortName();
101✔
270
            }
271

272
            if ($preProcess) {
103✔
273
                if (self::$map->hasType($gqlName)) {
103✔
274
                    throw new InvalidArgumentException(sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$map->getType($gqlName)['class']));
4✔
275
                }
276
                self::$map->addClassType($gqlName, $reflectionClass->getName(), $gqlType);
103✔
277
            } else {
278
                $gqlTypes = [$gqlName => $gqlConfiguration] + $gqlTypes;
103✔
279
            }
280
        }
281

282
        return $gqlTypes;
103✔
283
    }
284

285
    /**
286
     * @phpstan-param class-string $className
287
     *
288
     * @throws ReflectionException
289
     */
290
    private static function getClassReflection(string $className): ReflectionClass
291
    {
292
        self::$reflections[$className] ??= new ReflectionClass($className);
103✔
293

294
        return self::$reflections[$className];
103✔
295
    }
296

297
    private static function typeMetadataToGQLConfiguration(
298
        ReflectionClass $reflectionClass,
299
        Metadata\Type $classMetadata,
300
        string $gqlName,
301
        array $configs
302
    ): array {
303
        $isMutation = $isDefault = $isRoot = false;
103✔
304
        if (isset($configs['definitions']['schema'])) {
103✔
305
            $defaultSchemaName = isset($configs['definitions']['schema']['default']) ? 'default' : array_key_first($configs['definitions']['schema']);
101✔
306
            foreach ($configs['definitions']['schema'] as $schemaName => $schema) {
101✔
307
                $schemaQuery = $schema['query'] ?? null;
101✔
308
                $schemaMutation = $schema['mutation'] ?? null;
101✔
309
                $schemaSubscription = $schema['subscription'] ?? null;
101✔
310

311
                if ($gqlName === $schemaQuery) {
101✔
312
                    $isRoot = true;
101✔
313
                    if ($defaultSchemaName === $schemaName) {
101✔
314
                        $isDefault = true;
101✔
315
                    }
316
                } elseif ($gqlName === $schemaMutation) {
101✔
317
                    $isMutation = true;
101✔
318
                    $isRoot = true;
101✔
319
                    if ($defaultSchemaName === $schemaName) {
101✔
320
                        $isDefault = true;
101✔
321
                    }
322
                } elseif ($gqlName === $schemaSubscription) {
101✔
323
                    $isRoot = true;
×
324
                }
325
            }
326
        }
327

328
        $currentValue = $isRoot ? sprintf("service('%s')", self::formatNamespaceForExpression($reflectionClass->getName())) : 'value';
103✔
329

330
        $gqlConfiguration = self::graphQLTypeConfigFromAnnotation($reflectionClass, $classMetadata, $currentValue);
103✔
331

332
        $providerFields = self::getGraphQLFieldsFromProviders($reflectionClass, $isMutation ? Metadata\Mutation::class : Metadata\Query::class, $gqlName, $isDefault);
103✔
333
        $gqlConfiguration['config']['fields'] = array_merge($gqlConfiguration['config']['fields'], $providerFields);
103✔
334

335
        if ($classMetadata instanceof Metadata\Relay\Edge) {
103✔
336
            if (!$reflectionClass->implementsInterface(EdgeInterface::class)) {
98✔
337
                throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" can only be used on class implementing the EdgeInterface.', self::formatMetadata('Edge'), $reflectionClass->getName()));
×
338
            }
339
            if (!isset($gqlConfiguration['config']['builders'])) {
98✔
340
                $gqlConfiguration['config']['builders'] = [];
98✔
341
            }
342
            array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classMetadata->node]]);
98✔
343
        }
344

345
        return $gqlConfiguration;
103✔
346
    }
347

348
    /**
349
     * @return array{type: 'relay-mutation-payload'|'object', config: array}
350
     */
351
    private static function graphQLTypeConfigFromAnnotation(ReflectionClass $reflectionClass, Metadata\Type $typeAnnotation, string $currentValue): array
352
    {
353
        $typeConfiguration = [];
103✔
354
        $metadatas = static::getMetadatas($reflectionClass);
103✔
355

356
        $fieldsFromProperties = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, self::getClassProperties($reflectionClass), Metadata\Field::class, $currentValue);
103✔
357
        $fieldsFromMethods = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $reflectionClass->getMethods(), Metadata\Field::class, $currentValue);
103✔
358

359
        $typeConfiguration['fields'] = array_merge($fieldsFromProperties, $fieldsFromMethods);
103✔
360
        $typeConfiguration = self::getDescriptionConfiguration($metadatas) + $typeConfiguration;
103✔
361

362
        if (!empty($typeAnnotation->interfaces)) {
103✔
363
            $typeConfiguration['interfaces'] = $typeAnnotation->interfaces;
98✔
364
        } else {
365
            $interfaces = array_keys(self::$map->searchClassesMapBy(function ($gqlType, $configuration) use ($reflectionClass) {
103✔
366
                ['class' => $interfaceClassName] = $configuration;
101✔
367

368
                $interfaceMetadata = self::getClassReflection($interfaceClassName);
101✔
369
                if ($interfaceMetadata->isInterface() && $reflectionClass->implementsInterface($interfaceMetadata->getName())) {
101✔
370
                    return true;
98✔
371
                }
372

373
                return $reflectionClass->isSubclassOf($interfaceClassName);
101✔
374
            }, self::GQL_INTERFACE));
103✔
375

376
            sort($interfaces);
103✔
377
            $typeConfiguration['interfaces'] = $interfaces;
103✔
378
        }
379

380
        if (isset($typeAnnotation->resolveField)) {
103✔
381
            $typeConfiguration['resolveField'] = self::formatExpression($typeAnnotation->resolveField);
98✔
382
        }
383

384
        $buildersAnnotations = self::getMetadataMatching($metadatas, Metadata\FieldsBuilder::class);
103✔
385
        if (!empty($buildersAnnotations)) {
103✔
386
            $typeConfiguration['builders'] = array_map(fn ($fieldsBuilderAnnotation) => ['builder' => $fieldsBuilderAnnotation->name, 'builderConfig' => $fieldsBuilderAnnotation->config], $buildersAnnotations);
98✔
387
        }
388

389
        if (isset($typeAnnotation->isTypeOf)) {
103✔
390
            $typeConfiguration['isTypeOf'] = $typeAnnotation->isTypeOf;
98✔
391
        }
392

393
        $publicMetadata = self::getFirstMetadataMatching($metadatas, Metadata\IsPublic::class);
103✔
394
        if (null !== $publicMetadata) {
103✔
395
            $typeConfiguration['fieldsDefaultPublic'] = self::formatExpression($publicMetadata->value);
98✔
396
        }
397

398
        $accessMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Access::class);
103✔
399
        if (null !== $accessMetadata) {
103✔
400
            $typeConfiguration['fieldsDefaultAccess'] = self::formatExpression($accessMetadata->value);
98✔
401
        }
402

403
        return ['type' => $typeAnnotation->isRelay ? 'relay-mutation-payload' : 'object', 'config' => $typeConfiguration];
103✔
404
    }
405

406
    /**
407
     * Create a GraphQL Interface type configuration from metadatas on properties.
408
     *
409
     * @return array{type: 'interface', config: array}
410
     */
411
    private static function typeInterfaceMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\TypeInterface $interfaceAnnotation, string $gqlName): array
412
    {
413
        $interfaceConfiguration = [];
101✔
414

415
        $fieldsFromProperties = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, self::getClassProperties($reflectionClass));
101✔
416
        $fieldsFromMethods = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $reflectionClass->getMethods());
101✔
417

418
        $interfaceConfiguration['fields'] = array_merge($fieldsFromProperties, $fieldsFromMethods);
101✔
419
        $interfaceConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $interfaceConfiguration;
101✔
420

421
        if (isset($interfaceAnnotation->resolveType)) {
101✔
422
            $interfaceConfiguration['resolveType'] = self::formatExpression($interfaceAnnotation->resolveType);
98✔
423
        } else {
424
            // Try to use default interface resolver type
425
            $interfaceConfiguration['resolveType'] = self::formatExpression(sprintf("service('overblog_graphql.interface_type_resolver').resolveType('%s', value)", $gqlName));
101✔
426
        }
427

428
        return ['type' => 'interface', 'config' => $interfaceConfiguration];
101✔
429
    }
430

431
    /**
432
     * Create a GraphQL Input type configuration from metadatas on properties.
433
     *
434
     * @return array{type: 'relay-mutation-input'|'input-object', config: array}
435
     */
436
    private static function inputMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Input $inputAnnotation): array
437
    {
438
        $inputConfiguration = array_merge([
98✔
439
            'fields' => self::getGraphQLInputFieldsFromMetadatas($reflectionClass, self::getClassProperties($reflectionClass)),
98✔
440
        ], self::getDescriptionConfiguration(static::getMetadatas($reflectionClass), true));
98✔
441

442
        return ['type' => $inputAnnotation->isRelay ? 'relay-mutation-input' : 'input-object', 'config' => $inputConfiguration];
98✔
443
    }
444

445
    /**
446
     * Get a GraphQL scalar configuration from given scalar metadata.
447
     *
448
     * @return array{type: 'custom-scalar', config: array}
449
     */
450
    private static function scalarMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Scalar $scalarAnnotation): array
451
    {
452
        $scalarConfiguration = [];
98✔
453

454
        if (isset($scalarAnnotation->scalarType)) {
98✔
455
            $scalarConfiguration['scalarType'] = self::formatExpression($scalarAnnotation->scalarType);
98✔
456
        } else {
457
            $scalarConfiguration = [
98✔
458
                'serialize' => [$reflectionClass->getName(), 'serialize'],
98✔
459
                'parseValue' => [$reflectionClass->getName(), 'parseValue'],
98✔
460
                'parseLiteral' => [$reflectionClass->getName(), 'parseLiteral'],
98✔
461
            ];
98✔
462
        }
463

464
        $scalarConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $scalarConfiguration;
98✔
465

466
        return ['type' => 'custom-scalar', 'config' => $scalarConfiguration];
98✔
467
    }
468

469
    /**
470
     * Get a GraphQL Enum configuration from given enum metadata.
471
     *
472
     * @return array{type: 'enum', config: array}
473
     */
474
    private static function enumMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Enum $enumMetadata): array
475
    {
476
        $metadatas = static::getMetadatas($reflectionClass);
98✔
477
        $enumValues = self::getMetadataMatching($metadatas, Metadata\EnumValue::class);
98✔
478
        $isPhpEnum = PHP_VERSION_ID >= 80100 && $reflectionClass->isEnum();
98✔
479
        $values = [];
98✔
480

481
        foreach ($reflectionClass->getConstants() as $name => $value) {
98✔
482
            $reflectionConstant = new ReflectionClassConstant($reflectionClass->getName(), $name);
98✔
483
            $valueConfig = self::getDescriptionConfiguration(static::getMetadatas($reflectionConstant), true);
98✔
484

485
            $enumValueAnnotation = current(array_filter($enumValues, fn ($enumValueAnnotation) => $enumValueAnnotation->name === $name));
98✔
486
            $valueConfig['value'] = $isPhpEnum ? $value->name : $value;
98✔
487

488
            if (false !== $enumValueAnnotation) {
98✔
489
                if (isset($enumValueAnnotation->description)) {
49✔
490
                    $valueConfig['description'] = $enumValueAnnotation->description;
49✔
491
                }
492

493
                if (isset($enumValueAnnotation->deprecationReason)) {
49✔
494
                    $valueConfig['deprecationReason'] = $enumValueAnnotation->deprecationReason;
49✔
495
                }
496
            }
497

498
            $values[$name] = $valueConfig;
98✔
499
        }
500

501
        $enumConfiguration = ['values' => $values];
98✔
502
        $enumConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $enumConfiguration;
98✔
503
        if ($isPhpEnum) {
98✔
504
            $enumConfiguration['enumClass'] = $reflectionClass->getName();
98✔
505
        }
506

507
        return ['type' => 'enum', 'config' => $enumConfiguration];
98✔
508
    }
509

510
    /**
511
     * Get a GraphQL Union configuration from given union metadata.
512
     *
513
     * @return array{type: 'union', config: array}
514
     */
515
    private static function unionMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Union $unionMetadata): array
516
    {
517
        $unionConfiguration = [];
98✔
518
        if (!empty($unionMetadata->types)) {
98✔
519
            $unionConfiguration['types'] = $unionMetadata->types;
98✔
520
        } else {
521
            $types = array_keys(self::$map->searchClassesMapBy(function ($gqlType, $configuration) use ($reflectionClass) {
98✔
522
                $typeClassName = $configuration['class'];
98✔
523
                $typeMetadata = self::getClassReflection($typeClassName);
98✔
524

525
                if ($reflectionClass->isInterface() && $typeMetadata->implementsInterface($reflectionClass->getName())) {
98✔
526
                    return true;
98✔
527
                }
528

529
                return $typeMetadata->isSubclassOf($reflectionClass->getName());
98✔
530
            }, self::GQL_TYPE));
98✔
531
            sort($types);
98✔
532
            $unionConfiguration['types'] = $types;
98✔
533
        }
534

535
        $unionConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $unionConfiguration;
98✔
536

537
        if (isset($unionMetadata->resolveType)) {
98✔
538
            $unionConfiguration['resolveType'] = self::formatExpression($unionMetadata->resolveType);
98✔
539
        } else {
540
            if ($reflectionClass->hasMethod('resolveType')) {
98✔
541
                $method = $reflectionClass->getMethod('resolveType');
98✔
542
                if ($method->isStatic() && $method->isPublic()) {
98✔
543
                    $unionConfiguration['resolveType'] = self::formatExpression(sprintf("@=call('%s::%s', [service('overblog_graphql.type_resolver'), value], true)", self::formatNamespaceForExpression($reflectionClass->getName()), 'resolveType'));
98✔
544
                } else {
545
                    throw new InvalidArgumentException(sprintf('The "resolveType()" method on class must be static and public. Or you must define a "resolveType" attribute on the %s metadata.', self::formatMetadata('Union')));
52✔
546
                }
547
            } else {
548
                throw new InvalidArgumentException(sprintf('The metadata %s has no "resolveType" attribute and the related class has no "resolveType()" public static method. You need to define of them.', self::formatMetadata('Union')));
4✔
549
            }
550
        }
551

552
        return ['type' => 'union', 'config' => $unionConfiguration];
98✔
553
    }
554

555
    /**
556
     * @phpstan-param ReflectionMethod|ReflectionProperty $reflector
557
     * @phpstan-param class-string<Metadata\Field> $fieldMetadataName
558
     *
559
     * @return array<string,array>
560
     *
561
     * @throws AnnotationException
562
     */
563
    private static function getTypeFieldConfigurationFromReflector(ReflectionClass $reflectionClass, Reflector $reflector, string $fieldMetadataName, string $currentValue = 'value'): array
564
    {
565
        /** @var ReflectionProperty|ReflectionMethod $reflector */
566
        $metadatas = static::getMetadatas($reflector);
101✔
567

568
        $fieldMetadata = self::getFirstMetadataMatching($metadatas, $fieldMetadataName);
101✔
569
        $accessMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Access::class);
101✔
570
        $publicMetadata = self::getFirstMetadataMatching($metadatas, Metadata\IsPublic::class);
101✔
571

572
        if (null === $fieldMetadata) {
101✔
573
            if (null !== $accessMetadata || null !== $publicMetadata) {
98✔
574
                throw new InvalidArgumentException(sprintf('The metadatas %s and/or %s defined on "%s" are only usable in addition of metadata %s', self::formatMetadata('Access'), self::formatMetadata('Visible'), $reflector->getName(), self::formatMetadata('Field')));
4✔
575
            }
576

577
            return [];
98✔
578
        }
579

580
        if ($reflector instanceof ReflectionMethod && !$reflector->isPublic()) {
101✔
581
            throw new InvalidArgumentException(sprintf('The metadata %s can only be applied to public method. The method "%s" is not public.', self::formatMetadata('Field'), $reflector->getName()));
4✔
582
        }
583

584
        $fieldName = $reflector->getName();
101✔
585
        $fieldConfiguration = [];
101✔
586

587
        if (isset($fieldMetadata->type)) {
101✔
588
            $fieldConfiguration['type'] = $fieldMetadata->type;
101✔
589
        }
590

591
        $fieldConfiguration = self::getDescriptionConfiguration($metadatas, true) + $fieldConfiguration;
101✔
592

593
        $args = [];
101✔
594

595
        /** @var Metadata\Arg[] $argAnnotations */
596
        $argAnnotations = self::getMetadataMatching($metadatas, Metadata\Arg::class);
101✔
597

598
        $validArgNames = array_map(fn (ReflectionParameter $parameter) => $parameter->getName(), $reflector instanceof ReflectionMethod ? $reflector->getParameters() : []);
101✔
599

600
        foreach ($argAnnotations as $arg) {
101✔
601
            if ($reflector instanceof ReflectionMethod && !in_array($arg->name, $validArgNames, true)) {
98✔
602
                throw new InvalidArgumentException(sprintf('The argument "%s" defined with #[GQL\Arg] attribute/annotation on method "%s" does not match any parameter name in the method.', $arg->name, $reflector->getName()));
4✔
603
            }
604

605
            $args[$arg->name] = ['type' => $arg->type];
98✔
606

607
            if (isset($arg->description)) {
98✔
608
                $args[$arg->name]['description'] = $arg->description;
98✔
609
            }
610

611
            if (isset($arg->defaultValue)) {
98✔
612
                $args[$arg->name]['defaultValue'] = $arg->defaultValue;
98✔
613
            } elseif (isset($arg->default)) {
98✔
614
                @trigger_error(sprintf('%s %s %s', 'overblog/graphql-bundle', '1.3', 'The "default" attribute on @GQL\Arg or #GQL\Arg is deprecated, use "defaultValue" instead.'), E_USER_DEPRECATED);
×
615
                $args[$arg->name]['defaultValue'] = $arg->default;
×
616
            }
617
        }
618

619
        if ($reflector instanceof ReflectionMethod) {
101✔
620
            $args = self::guessArgs($reflectionClass, $reflector, $args);
101✔
621
        }
622

623
        $gqlArgs = array_filter($args, fn ($arg) => !isset($arg['internal']));
101✔
624
        if (!empty($gqlArgs)) {
101✔
625
            $fieldConfiguration['args'] = $gqlArgs;
98✔
626
        }
627

628
        $fieldName = $fieldMetadata->name ?? $fieldName;
101✔
629

630
        if (isset($fieldMetadata->resolve)) {
101✔
631
            $fieldConfiguration['resolve'] = self::formatExpression($fieldMetadata->resolve);
98✔
632
        } else {
633
            if ($reflector instanceof ReflectionMethod) {
101✔
634
                $fieldConfiguration['resolve'] = self::formatExpression(sprintf('call(%s.%s, %s)', $currentValue, $reflector->getName(), self::formatArgsForExpression($args)));
101✔
635
            } else {
636
                if ($fieldName !== $reflector->getName() || 'value' !== $currentValue) {
101✔
637
                    $fieldConfiguration['resolve'] = self::formatExpression(sprintf('%s.%s', $currentValue, $reflector->getName()));
3✔
638
                }
639
            }
640
        }
641

642
        $argsBuilder = self::getFirstMetadataMatching($metadatas, Metadata\ArgsBuilder::class);
101✔
643
        if ($argsBuilder) {
101✔
644
            $fieldConfiguration['argsBuilder'] = ['builder' => $argsBuilder->name, 'config' => $argsBuilder->config];
98✔
645
        }
646
        $fieldBuilder = self::getFirstMetadataMatching($metadatas, Metadata\FieldBuilder::class);
101✔
647
        if ($fieldBuilder) {
101✔
648
            $fieldConfiguration['builder'] = $fieldBuilder->name;
98✔
649
            $fieldConfiguration['builderConfig'] = $fieldBuilder->config;
98✔
650
        } else {
651
            if (!isset($fieldMetadata->type)) {
101✔
652
                try {
653
                    $fieldConfiguration['type'] = self::guessType($reflectionClass, $reflector, self::VALID_OUTPUT_TYPES);
101✔
654
                } catch (TypeGuessingException $e) {
8✔
655
                    $error = sprintf('The attribute "type" on %s is missing on %s "%s" and cannot be auto-guessed from the following type guessers:'."\n%s\n", static::formatMetadata($fieldMetadataName), $reflector instanceof ReflectionProperty ? 'property' : 'method', $reflector->getName(), $e->getMessage());
8✔
656

657
                    throw new InvalidArgumentException($error);
8✔
658
                }
659
            }
660
        }
661

662
        if ($accessMetadata) {
101✔
663
            $fieldConfiguration['access'] = self::formatExpression($accessMetadata->value);
98✔
664
        }
665

666
        if ($publicMetadata) {
101✔
667
            $fieldConfiguration['public'] = self::formatExpression($publicMetadata->value);
98✔
668
        }
669

670
        if (isset($fieldMetadata->complexity)) {
101✔
671
            $fieldConfiguration['complexity'] = self::formatExpression($fieldMetadata->complexity);
98✔
672
        }
673

674
        return [$fieldName => $fieldConfiguration];
101✔
675
    }
676

677
    /**
678
     * Create GraphQL input fields configuration based on metadatas.
679
     *
680
     * @param ReflectionProperty[] $reflectors
681
     *
682
     * @return array<string,array>
683
     *
684
     * @throws AnnotationException
685
     */
686
    private static function getGraphQLInputFieldsFromMetadatas(ReflectionClass $reflectionClass, array $reflectors): array
687
    {
688
        $fields = [];
98✔
689

690
        foreach ($reflectors as $reflector) {
98✔
691
            $metadatas = static::getMetadatas($reflector);
98✔
692

693
            /** @var Metadata\Field|null $fieldMetadata */
694
            $fieldMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Field::class);
98✔
695

696
            // No field metadata found
697
            if (null === $fieldMetadata) {
98✔
698
                continue;
98✔
699
            }
700

701
            // Ignore field with resolver when the type is an Input
702
            if (isset($fieldMetadata->resolve)) {
98✔
703
                continue;
98✔
704
            }
705

706
            $fieldName = $reflector->getName();
98✔
707
            if (isset($fieldMetadata->type)) {
98✔
708
                $fieldType = $fieldMetadata->type;
98✔
709
            } else {
710
                try {
711
                    $fieldType = self::guessType($reflectionClass, $reflector, self::VALID_INPUT_TYPES);
98✔
712
                } catch (TypeGuessingException $e) {
×
713
                    throw new InvalidArgumentException(sprintf('The attribute "type" on %s is missing on property "%s" and cannot be auto-guessed from the following type guessers:'."\n%s\n", self::formatMetadata(Metadata\Field::class), $reflector->getName(), $e->getMessage()));
×
714
                }
715
            }
716
            $fieldConfiguration = [];
98✔
717
            if ($fieldType) {
98✔
718
                // Resolve a PHP class from a GraphQL type
719
                $resolvedType = self::$map->getType($fieldType);
98✔
720
                // We found a type but it is not allowed
721
                if (null !== $resolvedType && !in_array($resolvedType['type'], self::VALID_INPUT_TYPES)) {
98✔
722
                    throw new InvalidArgumentException(sprintf('The type "%s" on "%s" is a "%s" not valid on an Input %s. Only Input, Scalar and Enum are allowed.', $fieldType, $reflector->getName(), $resolvedType['type'], self::formatMetadata('Field')));
×
723
                }
724

725
                $fieldConfiguration['type'] = $fieldType;
98✔
726
            }
727

728
            if ($fieldMetadata instanceof InputField && null !== $fieldMetadata->defaultValue) {
98✔
729
                $fieldConfiguration['defaultValue'] = $fieldMetadata->defaultValue;
98✔
730
            } elseif ($reflector->hasDefaultValue() && null !== $reflector->getDefaultValue()) {
98✔
731
                $fieldConfiguration['defaultValue'] = $reflector->getDefaultValue();
98✔
732
            }
733

734
            $fieldConfiguration = array_merge(self::getDescriptionConfiguration($metadatas, true), $fieldConfiguration);
98✔
735
            $fields[$fieldName] = $fieldConfiguration;
98✔
736
        }
737

738
        return $fields;
98✔
739
    }
740

741
    /**
742
     * Create GraphQL type fields configuration based on metadatas.
743
     *
744
     * @param ReflectionProperty[]|ReflectionMethod[] $reflectors
745
     *
746
     * @phpstan-param class-string<Metadata\Field> $fieldMetadataName
747
     *
748
     * @throws AnnotationException
749
     */
750
    private static function getGraphQLTypeFieldsFromAnnotations(ReflectionClass $reflectionClass, array $reflectors, string $fieldMetadataName = Metadata\Field::class, string $currentValue = 'value'): array
751
    {
752
        $fields = [];
103✔
753

754
        foreach ($reflectors as $reflector) {
103✔
755
            $fields = array_merge($fields, self::getTypeFieldConfigurationFromReflector($reflectionClass, $reflector, $fieldMetadataName, $currentValue));
101✔
756
        }
757

758
        return $fields;
103✔
759
    }
760

761
    /**
762
     * @phpstan-param class-string<Metadata\Query|Metadata\Mutation> $expectedMetadata
763
     *
764
     * Return fields config from Provider methods.
765
     * Loop through configured provider and extract fields targeting the targetType.
766
     *
767
     * @return array<string,array>
768
     */
769
    private static function getGraphQLFieldsFromProviders(ReflectionClass $reflectionClass, string $expectedMetadata, string $targetType, bool $isDefaultTarget = false): array
770
    {
771
        $fields = [];
103✔
772
        foreach (self::$providers as ['reflectionClass' => $providerReflection, 'metadata' => $providerMetadata]) {
103✔
773
            $defaultAccessAnnotation = self::getFirstMetadataMatching(static::getMetadatas($providerReflection), Metadata\Access::class);
101✔
774
            $defaultIsPublicAnnotation = self::getFirstMetadataMatching(static::getMetadatas($providerReflection), Metadata\IsPublic::class);
101✔
775

776
            $defaultAccess = $defaultAccessAnnotation ? self::formatExpression($defaultAccessAnnotation->value) : false;
101✔
777
            $defaultIsPublic = $defaultIsPublicAnnotation ? self::formatExpression($defaultIsPublicAnnotation->value) : false;
101✔
778

779
            $methods = [];
101✔
780
            // First found the methods matching the targeted type
781
            foreach ($providerReflection->getMethods() as $method) {
101✔
782
                $metadatas = static::getMetadatas($method);
101✔
783

784
                $metadata = self::getFirstMetadataMatching($metadatas, [Metadata\Mutation::class, Metadata\Query::class]);
101✔
785
                if (null === $metadata) {
101✔
786
                    continue;
×
787
                }
788

789
                // TODO: Remove old property check in 1.1
790
                $metadataTargets = $metadata->targetTypes ?? null;
101✔
791

792
                if (null === $metadataTargets) {
101✔
793
                    if ($metadata instanceof Metadata\Mutation && isset($providerMetadata->targetMutationTypes)) {
101✔
794
                        $metadataTargets = $providerMetadata->targetMutationTypes;
98✔
795
                    } elseif ($metadata instanceof Metadata\Query && isset($providerMetadata->targetQueryTypes)) {
101✔
796
                        $metadataTargets = $providerMetadata->targetQueryTypes;
98✔
797
                    }
798
                }
799

800
                if (null === $metadataTargets) {
101✔
801
                    if ($isDefaultTarget) {
101✔
802
                        $metadataTargets = [$targetType];
101✔
803
                        if (!$metadata instanceof $expectedMetadata) {
101✔
804
                            continue;
101✔
805
                        }
806
                    } else {
807
                        continue;
101✔
808
                    }
809
                }
810

811
                if (!in_array($targetType, $metadataTargets)) {
101✔
812
                    continue;
98✔
813
                }
814

815
                if (!$metadata instanceof $expectedMetadata) {
101✔
816
                    if (Metadata\Mutation::class === $expectedMetadata) {
8✔
817
                        $message = sprintf('The provider "%s" try to add a query field on type "%s" (through %s on method "%s") but "%s" is a mutation.', $providerReflection->getName(), $targetType, self::formatMetadata('Query'), $method->getName(), $targetType);
4✔
818
                    } else {
819
                        $message = sprintf('The provider "%s" try to add a mutation on type "%s" (through %s on method "%s") but "%s" is not a mutation.', $providerReflection->getName(), $targetType, self::formatMetadata('Mutation'), $method->getName(), $targetType);
4✔
820
                    }
821

822
                    throw new InvalidArgumentException($message);
8✔
823
                }
824
                $methods[$method->getName()] = $method;
101✔
825
            }
826

827
            $currentValue = sprintf("service('%s')", self::formatNamespaceForExpression($providerReflection->getName()));
101✔
828
            $providerFields = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $methods, $expectedMetadata, $currentValue);
101✔
829
            foreach ($providerFields as $fieldName => $fieldConfig) {
101✔
830
                if (isset($providerMetadata->prefix)) {
101✔
831
                    $fieldName = sprintf('%s%s', $providerMetadata->prefix, $fieldName);
98✔
832
                }
833

834
                if ($defaultAccess && !isset($fieldConfig['access'])) {
101✔
835
                    $fieldConfig['access'] = $defaultAccess;
98✔
836
                }
837

838
                if ($defaultIsPublic && !isset($fieldConfig['public'])) {
101✔
839
                    $fieldConfig['public'] = $defaultIsPublic;
98✔
840
                }
841

842
                $fields[$fieldName] = $fieldConfig;
101✔
843
            }
844
        }
845

846
        return $fields;
103✔
847
    }
848

849
    /**
850
     * Get the config for description & deprecation reason.
851
     *
852
     * @return array<'description'|'deprecationReason',string>
853
     */
854
    private static function getDescriptionConfiguration(array $metadatas, bool $withDeprecation = false): array
855
    {
856
        $config = [];
103✔
857
        $descriptionAnnotation = self::getFirstMetadataMatching($metadatas, Metadata\Description::class);
103✔
858
        if (null !== $descriptionAnnotation) {
103✔
859
            $config['description'] = $descriptionAnnotation->value;
98✔
860
        }
861

862
        if ($withDeprecation) {
103✔
863
            $deprecatedAnnotation = self::getFirstMetadataMatching($metadatas, Metadata\Deprecated::class);
101✔
864
            if (null !== $deprecatedAnnotation) {
101✔
865
                $config['deprecationReason'] = $deprecatedAnnotation->value;
98✔
866
            }
867
        }
868

869
        return $config;
103✔
870
    }
871

872
    /**
873
     * Format an array of args to a list of arguments in an expression.
874
     */
875
    private static function formatArgsForExpression(array $args): string
876
    {
877
        $mapping = [];
101✔
878
        foreach ($args as $name => $config) {
101✔
879
            $mapping[] = sprintf('%s: "%s"', $name, $config['type']);
98✔
880
        }
881

882
        return sprintf('arguments({%s}, args)', implode(', ', $mapping));
101✔
883
    }
884

885
    /**
886
     * Format a namespace to be used in an expression (double escape).
887
     */
888
    private static function formatNamespaceForExpression(string $namespace): string
889
    {
890
        return str_replace('\\', '\\\\', $namespace);
101✔
891
    }
892

893
    /**
894
     * Get the first metadata matching given class.
895
     *
896
     * @phpstan-template T of object
897
     *
898
     * @phpstan-param class-string<T>|class-string<T>[] $metadataClasses
899
     *
900
     * @return object|null
901
     *
902
     * @phpstan-return T|null
903
     */
904
    private static function getFirstMetadataMatching(array $metadatas, $metadataClasses)
905
    {
906
        $metas = self::getMetadataMatching($metadatas, $metadataClasses);
103✔
907

908
        return array_shift($metas);
103✔
909
    }
910

911
    /**
912
     * Return the metadata matching given class
913
     *
914
     * @phpstan-template T of object
915
     *
916
     * @phpstan-param class-string<T>|class-string<T>[] $metadataClasses
917
     *
918
     * @return array
919
     */
920
    private static function getMetadataMatching(array $metadatas, $metadataClasses)
921
    {
922
        if (is_string($metadataClasses)) {
103✔
923
            $metadataClasses = [$metadataClasses];
103✔
924
        }
925

926
        return array_values(array_filter($metadatas, function ($metadata) use ($metadataClasses) {
103✔
927
            foreach ($metadataClasses as $metadataClass) {
103✔
928
                if ($metadata instanceof $metadataClass) {
103✔
929
                    return true;
101✔
930
                }
931
            }
932

933
            return false;
103✔
934
        }));
103✔
935
    }
936

937
    /**
938
     * Format an expression (ie. add "@=" if not set).
939
     */
940
    private static function formatExpression(string $expression): string
941
    {
942
        return '@=' === substr($expression, 0, 2) ? $expression : sprintf('@=%s', $expression);
101✔
943
    }
944

945
    /**
946
     * Suffix a name if it is not already.
947
     */
948
    private static function suffixName(string $name, string $suffix): string
949
    {
950
        return substr($name, -strlen($suffix)) === $suffix ? $name : sprintf('%s%s', $name, $suffix);
98✔
951
    }
952

953
    /**
954
     * Try to guess a GraphQL type using configured type guessers
955
     *
956
     * @throws RuntimeException
957
     */
958
    private static function guessType(ReflectionClass $reflectionClass, Reflector $reflector, array $filterGraphQLTypes = []): string
959
    {
960
        $errors = [];
101✔
961
        foreach (self::$typeGuessers as $typeGuesser) {
101✔
962
            if (!$typeGuesser->supports($reflector)) {
101✔
963
                continue;
98✔
964
            }
965
            try {
966
                $type = $typeGuesser->guessType($reflectionClass, $reflector, $filterGraphQLTypes);
101✔
967

968
                return $type;
101✔
969
            } catch (TypeGuessingException $exception) {
101✔
970
                $errors[] = sprintf('[%s] %s', $typeGuesser->getName(), $exception->getMessage());
101✔
971
            }
972
        }
973

974
        throw new TypeGuessingException(implode("\n", $errors));
12✔
975
    }
976

977
    /**
978
     * Transform a method arguments from reflection to a list of GraphQL argument.
979
     */
980
    private static function guessArgs(
981
        ReflectionClass $reflectionClass,
982
        ReflectionMethod $method,
983
        array $currentArguments,
984
    ): array {
985
        $arguments = [];
101✔
986
        foreach ($method->getParameters() as $index => $parameter) {
101✔
987
            if (array_key_exists($parameter->getName(), $currentArguments)) {
98✔
988
                $arguments[$parameter->getName()] = $currentArguments[$parameter->getName()];
98✔
989
                continue;
98✔
990
            }
991

992
            if ($parameter->getType() instanceof ReflectionNamedType) {
98✔
993
                $className = $parameter->getType()->getName();
98✔
994
                if (ResolveInfo::class === $className || is_subclass_of($className, ResolveInfo::class)) {
98✔
995
                    $arguments[$parameter->getName()] = ['type' => ArgumentsTransformer::RESOLVE_INFO_TOKEN, 'internal' => true];
98✔
996
                    continue;
98✔
997
                }
998
            }
999

1000
            try {
1001
                $gqlType = self::guessType($reflectionClass, $parameter, self::VALID_INPUT_TYPES);
98✔
1002
            } catch (TypeGuessingException $exception) {
4✔
1003
                throw new InvalidArgumentException(sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed from the following type guessers:'."\n%s\n", $index + 1, $parameter->getName(), $method->getName(), $exception->getMessage()));
4✔
1004
            }
1005

1006
            $argumentConfig = [];
98✔
1007
            if ($parameter->isDefaultValueAvailable()) {
98✔
1008
                $argumentConfig['defaultValue'] = $parameter->getDefaultValue();
98✔
1009
            }
1010

1011
            $argumentConfig['type'] = $gqlType;
98✔
1012

1013
            $arguments[$parameter->getName()] = $argumentConfig;
98✔
1014
        }
1015

1016
        return $arguments;
101✔
1017
    }
1018

1019
    /**
1020
     * @return ReflectionProperty[]
1021
     */
1022
    private static function getClassProperties(ReflectionClass $reflectionClass): array
1023
    {
1024
        $properties = [];
103✔
1025
        do {
1026
            foreach ($reflectionClass->getProperties() as $property) {
103✔
1027
                if (isset($properties[$property->getName()])) {
101✔
1028
                    continue;
101✔
1029
                }
1030
                $properties[$property->getName()] = $property;
101✔
1031
            }
1032
        } while ($reflectionClass = $reflectionClass->getParentClass());
103✔
1033

1034
        return $properties;
103✔
1035
    }
1036

1037
    protected static function formatMetadata(string $className): string
1038
    {
1039
        return sprintf(static::METADATA_FORMAT, str_replace(self::ANNOTATION_NAMESPACE, '', $className));
28✔
1040
    }
1041

1042
    abstract protected static function getMetadatas(Reflector $reflector): array;
1043
}
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