Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Pull Request — master (#801)
by Vincent
21:27
created

MetadataParser::graphQLTypeConfigFromAnnotation()   B

Complexity

Conditions 10
Paths 128

Size

Total Lines 55
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 34
c 1
b 0
f 0
nc 128
nop 3
dl 0
loc 55
rs 7.4333

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Config\Parser\MetadataParser;
6
7
use Doctrine\Common\Annotations\AnnotationException;
8
use Overblog\GraphQLBundle\Annotation\Annotation as Meta;
9
use Overblog\GraphQLBundle\Annotation as Metadata;
10
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\DocBlockTypeGuesser;
11
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\DoctrineTypeGuesser;
12
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\TypeGuessingException;
13
use Overblog\GraphQLBundle\Config\Parser\MetadataParser\TypeGuesser\TypeHintTypeGuesser;
14
use Overblog\GraphQLBundle\Config\Parser\PreParserInterface;
15
use Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface;
16
use Overblog\GraphQLBundle\Relay\Connection\EdgeInterface;
17
use ReflectionClass;
18
use ReflectionException;
19
use ReflectionMethod;
20
use ReflectionProperty;
21
use Reflector;
22
use RuntimeException;
23
use SplFileInfo;
24
use Symfony\Component\Config\Resource\FileResource;
25
use Symfony\Component\DependencyInjection\ContainerBuilder;
26
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
27
use function array_filter;
28
use function array_keys;
29
use function array_map;
30
use function array_unshift;
31
use function current;
32
use function file_get_contents;
33
use function implode;
34
use function in_array;
35
use function is_array;
36
use function is_string;
37
use function preg_match;
38
use function sprintf;
39
use function str_replace;
40
use function strlen;
41
use function substr;
42
use function trim;
43
44
abstract class MetadataParser implements PreParserInterface
45
{
46
    const ANNOTATION_NAMESPACE = 'Overblog\GraphQLBundle\Annotation\\';
47
    const METADATA_FORMAT = '%s';
48
49
    private static ClassesTypesMap $map;
50
    private static array $typeGuessers = [];
51
    private static array $providers = [];
52
    private static array $reflections = [];
53
54
    private const GQL_SCALAR = 'scalar';
55
    private const GQL_ENUM = 'enum';
56
    private const GQL_TYPE = 'type';
57
    private const GQL_INPUT = 'input';
58
    private const GQL_UNION = 'union';
59
    private const GQL_INTERFACE = 'interface';
60
61
    /**
62
     * @see https://facebook.github.io/graphql/draft/#sec-Input-and-Output-Types
63
     */
64
    private const VALID_INPUT_TYPES = [self::GQL_SCALAR, self::GQL_ENUM, self::GQL_INPUT];
65
    private const VALID_OUTPUT_TYPES = [self::GQL_SCALAR, self::GQL_TYPE, self::GQL_INTERFACE, self::GQL_UNION, self::GQL_ENUM];
66
67
    /**
68
     * {@inheritdoc}
69
     *
70
     * @throws InvalidArgumentException
71
     * @throws ReflectionException
72
     */
73
    public static function preParse(SplFileInfo $file, ContainerBuilder $container, array $configs = []): void
74
    {
75
        $container->setParameter('overblog_graphql_types.classes_map', self::processFile($file, $container, $configs, true));
76
    }
77
78
    /**
79
     * @throws InvalidArgumentException
80
     * @throws ReflectionException
81
     */
82
    public static function parse(SplFileInfo $file, ContainerBuilder $container, array $configs = []): array
83
    {
84
        return self::processFile($file, $container, $configs, false);
85
    }
86
87
    /**
88
     * @internal
89
     */
90
    public static function reset(array $configs): void
91
    {
92
        self::$map = new ClassesTypesMap();
93
        self::$typeGuessers = [
94
            new DocBlockTypeGuesser(self::$map),
95
            new TypeHintTypeGuesser(self::$map),
96
            new DoctrineTypeGuesser(self::$map, $configs['doctrine']['types_mapping']),
97
        ];
98
        self::$providers = [];
99
        self::$reflections = [];
100
    }
101
102
    /**
103
     * Process a file.
104
     *
105
     * @throws InvalidArgumentException|ReflectionException|AnnotationException
106
     */
107
    private static function processFile(SplFileInfo $file, ContainerBuilder $container, array $configs, bool $preProcess): array
108
    {
109
        $container->addResource(new FileResource($file->getRealPath()));
110
111
        try {
112
            $className = $file->getBasename('.php');
113
            if (preg_match('#namespace (.+);#', file_get_contents($file->getRealPath()), $matches)) {
114
                $className = trim($matches[1]).'\\'.$className;
115
            }
116
117
            $gqlTypes = [];
118
            /** @phpstan-ignore-next-line */
119
            $reflectionClass = self::getClassReflection($className);
120
121
            foreach (static::getMetadatas($reflectionClass) as $classMetadata) {
122
                if ($classMetadata instanceof Meta) {
123
                    $gqlTypes = self::classMetadatasToGQLConfiguration(
124
                        $reflectionClass,
125
                        $classMetadata,
126
                        $configs,
127
                        $gqlTypes,
128
                        $preProcess
129
                    );
130
                }
131
            }
132
133
            return $preProcess ? self::$map->toArray() : $gqlTypes;
134
        } catch (\InvalidArgumentException $e) {
135
            throw new InvalidArgumentException(sprintf('Failed to parse GraphQL metadata from file "%s".', $file), $e->getCode(), $e);
136
        }
137
    }
138
139
    /**
140
     * @return array<string,array>
141
     */
142
    private static function classMetadatasToGQLConfiguration(
143
        ReflectionClass $reflectionClass,
144
        Meta $classMetadata,
145
        array $configs,
146
        array $gqlTypes,
147
        bool $preProcess
148
    ): array {
149
        $gqlConfiguration = $gqlType = $gqlName = null;
150
151
        switch (true) {
152
            case $classMetadata instanceof Metadata\Type:
153
                $gqlType = self::GQL_TYPE;
154
                $gqlName = $classMetadata->name ?? $reflectionClass->getShortName();
155
                if (!$preProcess) {
156
                    $gqlConfiguration = self::typeMetadataToGQLConfiguration($reflectionClass, $classMetadata, $gqlName, $configs);
157
158
                    if ($classMetadata instanceof Metadata\Relay\Connection) {
159
                        if (!$reflectionClass->implementsInterface(ConnectionInterface::class)) {
160
                            throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" can only be used on class implementing the ConnectionInterface.', self::formatMetadata('Connection'), $reflectionClass->getName()));
161
                        }
162
163
                        if (!(isset($classMetadata->edge) xor isset($classMetadata->node))) {
164
                            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()));
165
                        }
166
167
                        $edgeType = $classMetadata->edge ?? false;
168
                        if (!$edgeType) {
169
                            $edgeType = $gqlName.'Edge';
170
                            $gqlTypes[$edgeType] = [
171
                                'type' => 'object',
172
                                'config' => [
173
                                    'builders' => [
174
                                        ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classMetadata->node]],
175
                                    ],
176
                                ],
177
                            ];
178
                        }
179
180
                        if (!isset($gqlConfiguration['config']['builders'])) {
181
                            $gqlConfiguration['config']['builders'] = [];
182
                        }
183
184
                        array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-connection', 'builderConfig' => ['edgeType' => $edgeType]]);
185
                    }
186
                }
187
                break;
188
189
            case $classMetadata instanceof Metadata\Input:
190
                $gqlType = self::GQL_INPUT;
191
                $gqlName = $classMetadata->name ?? self::suffixName($reflectionClass->getShortName(), 'Input');
192
                if (!$preProcess) {
193
                    $gqlConfiguration = self::inputMetadataToGQLConfiguration($reflectionClass, $classMetadata);
194
                }
195
                break;
196
197
            case $classMetadata instanceof Metadata\Scalar:
198
                $gqlType = self::GQL_SCALAR;
199
                if (!$preProcess) {
200
                    $gqlConfiguration = self::scalarMetadataToGQLConfiguration($reflectionClass, $classMetadata);
201
                }
202
                break;
203
204
            case $classMetadata instanceof Metadata\Enum:
205
                $gqlType = self::GQL_ENUM;
206
                if (!$preProcess) {
207
                    $gqlConfiguration = self::enumMetadataToGQLConfiguration($reflectionClass, $classMetadata);
208
                }
209
                break;
210
211
            case $classMetadata instanceof Metadata\Union:
212
                $gqlType = self::GQL_UNION;
213
                if (!$preProcess) {
214
                    $gqlConfiguration = self::unionMetadataToGQLConfiguration($reflectionClass, $classMetadata);
215
                }
216
                break;
217
218
            case $classMetadata instanceof Metadata\TypeInterface:
219
                $gqlType = self::GQL_INTERFACE;
220
                if (!$preProcess) {
221
                    $gqlConfiguration = self::typeInterfaceMetadataToGQLConfiguration($reflectionClass, $classMetadata);
222
                }
223
                break;
224
225
            case $classMetadata instanceof Metadata\Provider:
226
                if ($preProcess) {
227
                    self::$providers[] = ['reflectionClass' => $reflectionClass, 'metadata' => $classMetadata];
228
                }
229
230
                return [];
231
        }
232
233
        if (null !== $gqlType) {
234
            if (!$gqlName) {
235
                $gqlName = isset($classMetadata->name) ? $classMetadata->name : $reflectionClass->getShortName();
236
            }
237
238
            if ($preProcess) {
239
                if (self::$map->hasType($gqlName)) {
0 ignored issues
show
Bug introduced by
It seems like $gqlName can also be of type null; however, parameter $gqlType of Overblog\GraphQLBundle\C...ssesTypesMap::hasType() 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

239
                if (self::$map->hasType(/** @scrutinizer ignore-type */ $gqlName)) {
Loading history...
240
                    throw new InvalidArgumentException(sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$map->getType($gqlName)['class']));
0 ignored issues
show
Bug introduced by
It seems like $gqlName can also be of type null; however, parameter $gqlType of Overblog\GraphQLBundle\C...ssesTypesMap::getType() 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

240
                    throw new InvalidArgumentException(sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$map->getType(/** @scrutinizer ignore-type */ $gqlName)['class']));
Loading history...
241
                }
242
                self::$map->addClassType($gqlName, $reflectionClass->getName(), $gqlType);
0 ignored issues
show
Bug introduced by
It seems like $gqlName can also be of type null; however, parameter $typeName of Overblog\GraphQLBundle\C...ypesMap::addClassType() 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

242
                self::$map->addClassType(/** @scrutinizer ignore-type */ $gqlName, $reflectionClass->getName(), $gqlType);
Loading history...
243
            } else {
244
                $gqlTypes = [$gqlName => $gqlConfiguration] + $gqlTypes;
245
            }
246
        }
247
248
        return $gqlTypes;
249
    }
250
251
    /**
252
     * @throws ReflectionException
253
     * @phpstan-param class-string $className
254
     */
255
    private static function getClassReflection(string $className): ReflectionClass
256
    {
257
        self::$reflections[$className] ??= new ReflectionClass($className);
258
259
        return self::$reflections[$className];
260
    }
261
262
    private static function typeMetadataToGQLConfiguration(
263
        ReflectionClass $reflectionClass,
264
        Metadata\Type $classMetadata,
265
        string $gqlName,
266
        array $configs
267
    ): array {
268
        $isMutation = $isDefault = $isRoot = false;
269
        if (isset($configs['definitions']['schema'])) {
270
            $defaultSchemaName = isset($configs['definitions']['schema']['default']) ? 'default' : array_key_first($configs['definitions']['schema']);
271
            foreach ($configs['definitions']['schema'] as $schemaName => $schema) {
272
                $schemaQuery = $schema['query'] ?? null;
273
                $schemaMutation = $schema['mutation'] ?? null;
274
275
                if ($gqlName === $schemaQuery) {
276
                    $isRoot = true;
277
                    if ($defaultSchemaName === $schemaName) {
278
                        $isDefault = true;
279
                    }
280
                } elseif ($gqlName === $schemaMutation) {
281
                    $isMutation = true;
282
                    $isRoot = true;
283
                    if ($defaultSchemaName === $schemaName) {
284
                        $isDefault = true;
285
                    }
286
                }
287
            }
288
        }
289
290
        $currentValue = $isRoot ? sprintf("service('%s')", self::formatNamespaceForExpression($reflectionClass->getName())) : 'value';
291
292
        $gqlConfiguration = self::graphQLTypeConfigFromAnnotation($reflectionClass, $classMetadata, $currentValue);
293
294
        $providerFields = self::getGraphQLFieldsFromProviders($reflectionClass, $isMutation ? Metadata\Mutation::class : Metadata\Query::class, $gqlName, $isDefault);
295
        $gqlConfiguration['config']['fields'] = array_merge($gqlConfiguration['config']['fields'], $providerFields);
296
297
        if ($classMetadata instanceof Metadata\Relay\Edge) {
298
            if (!$reflectionClass->implementsInterface(EdgeInterface::class)) {
299
                throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" can only be used on class implementing the EdgeInterface.', self::formatMetadata('Edge'), $reflectionClass->getName()));
300
            }
301
            if (!isset($gqlConfiguration['config']['builders'])) {
302
                $gqlConfiguration['config']['builders'] = [];
303
            }
304
            array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classMetadata->node]]);
305
        }
306
307
        return $gqlConfiguration;
308
    }
309
310
    /**
311
     * @return array{type: 'relay-mutation-payload'|'object', config: array}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type: 'relay-mutat...object', config: array} at position 4 could not be parsed: Unknown type name ''relay-mutation-payload'' at position 4 in array{type: 'relay-mutation-payload'|'object', config: array}.
Loading history...
312
     */
313
    private static function graphQLTypeConfigFromAnnotation(ReflectionClass $reflectionClass, Metadata\Type $typeAnnotation, string $currentValue): array
314
    {
315
        $typeConfiguration = [];
316
        $metadatas = static::getMetadatas($reflectionClass);
317
318
        $fieldsFromProperties = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, self::getClassProperties($reflectionClass), Metadata\Field::class, $currentValue);
319
        $fieldsFromMethods = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $reflectionClass->getMethods(), Metadata\Field::class, $currentValue);
320
321
        $typeConfiguration['fields'] = array_merge($fieldsFromProperties, $fieldsFromMethods);
322
        $typeConfiguration = self::getDescriptionConfiguration($metadatas) + $typeConfiguration;
323
324
        if (!empty($typeAnnotation->interfaces)) {
325
            $typeConfiguration['interfaces'] = $typeAnnotation->interfaces;
326
        } else {
327
            $interfaces = array_keys(self::$map->searchClassesMapBy(function ($gqlType, $configuration) use ($reflectionClass) {
328
                ['class' => $interfaceClassName] = $configuration;
329
330
                $interfaceMetadata = self::getClassReflection($interfaceClassName);
331
                if ($interfaceMetadata->isInterface() && $reflectionClass->implementsInterface($interfaceMetadata->getName())) {
332
                    return true;
333
                }
334
335
                return $reflectionClass->isSubclassOf($interfaceClassName);
336
            }, self::GQL_INTERFACE));
337
338
            sort($interfaces);
339
            $typeConfiguration['interfaces'] = $interfaces;
340
        }
341
342
        if (isset($typeAnnotation->resolveField)) {
343
            $typeConfiguration['resolveField'] = self::formatExpression($typeAnnotation->resolveField);
0 ignored issues
show
Bug introduced by
It seems like $typeAnnotation->resolveField can also be of type null; however, parameter $expression of Overblog\GraphQLBundle\C...ser::formatExpression() 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

343
            $typeConfiguration['resolveField'] = self::formatExpression(/** @scrutinizer ignore-type */ $typeAnnotation->resolveField);
Loading history...
344
        }
345
346
        $buildersAnnotations = array_merge(self::getMetadataMatching($metadatas, Metadata\FieldsBuilder::class), $typeAnnotation->builders);
347
        if (!empty($buildersAnnotations)) {
348
            $typeConfiguration['builders'] = array_map(function ($fieldsBuilderAnnotation) {
349
                return ['builder' => $fieldsBuilderAnnotation->value, 'builderConfig' => $fieldsBuilderAnnotation->config];
350
            }, $buildersAnnotations);
351
        }
352
353
        if (isset($typeAnnotation->isTypeOf)) {
354
            $typeConfiguration['isTypeOf'] = $typeAnnotation->isTypeOf;
355
        }
356
357
        $publicMetadata = self::getFirstMetadataMatching($metadatas, Metadata\IsPublic::class);
358
        if (null !== $publicMetadata) {
359
            $typeConfiguration['fieldsDefaultPublic'] = self::formatExpression($publicMetadata->value);
360
        }
361
362
        $accessMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Access::class);
363
        if (null !== $accessMetadata) {
364
            $typeConfiguration['fieldsDefaultAccess'] = self::formatExpression($accessMetadata->value);
365
        }
366
367
        return ['type' => $typeAnnotation->isRelay ? 'relay-mutation-payload' : 'object', 'config' => $typeConfiguration];
368
    }
369
370
    /**
371
     * Create a GraphQL Interface type configuration from metadatas on properties.
372
     *
373
     * @return array{type: 'interface', config: array}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type: 'interface', config: array} at position 4 could not be parsed: Unknown type name ''interface'' at position 4 in array{type: 'interface', config: array}.
Loading history...
374
     */
375
    private static function typeInterfaceMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\TypeInterface $interfaceAnnotation): array
376
    {
377
        $interfaceConfiguration = [];
378
379
        $fieldsFromProperties = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, self::getClassProperties($reflectionClass));
380
        $fieldsFromMethods = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $reflectionClass->getMethods());
381
382
        $interfaceConfiguration['fields'] = array_merge($fieldsFromProperties, $fieldsFromMethods);
383
        $interfaceConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $interfaceConfiguration;
384
385
        $interfaceConfiguration['resolveType'] = self::formatExpression($interfaceAnnotation->resolveType);
386
387
        return ['type' => 'interface', 'config' => $interfaceConfiguration];
388
    }
389
390
    /**
391
     * Create a GraphQL Input type configuration from metadatas on properties.
392
     *
393
     * @return array{type: 'relay-mutation-input'|'input-object', config: array}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type: 'relay-mutat...object', config: array} at position 4 could not be parsed: Unknown type name ''relay-mutation-input'' at position 4 in array{type: 'relay-mutation-input'|'input-object', config: array}.
Loading history...
394
     */
395
    private static function inputMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Input $inputAnnotation): array
396
    {
397
        $inputConfiguration = array_merge([
398
            'fields' => self::getGraphQLInputFieldsFromMetadatas($reflectionClass, self::getClassProperties($reflectionClass)),
399
        ], self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)));
400
401
        return ['type' => $inputAnnotation->isRelay ? 'relay-mutation-input' : 'input-object', 'config' => $inputConfiguration];
402
    }
403
404
    /**
405
     * Get a GraphQL scalar configuration from given scalar metadata.
406
     *
407
     * @return array{type: 'custom-scalar', config: array}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type: 'custom-scalar', config: array} at position 4 could not be parsed: Unknown type name ''custom-scalar'' at position 4 in array{type: 'custom-scalar', config: array}.
Loading history...
408
     */
409
    private static function scalarMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Scalar $scalarAnnotation): array
410
    {
411
        $scalarConfiguration = [];
412
413
        if (isset($scalarAnnotation->scalarType)) {
414
            $scalarConfiguration['scalarType'] = self::formatExpression($scalarAnnotation->scalarType);
0 ignored issues
show
Bug introduced by
It seems like $scalarAnnotation->scalarType can also be of type null; however, parameter $expression of Overblog\GraphQLBundle\C...ser::formatExpression() 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

414
            $scalarConfiguration['scalarType'] = self::formatExpression(/** @scrutinizer ignore-type */ $scalarAnnotation->scalarType);
Loading history...
415
        } else {
416
            $scalarConfiguration = [
417
                'serialize' => [$reflectionClass->getName(), 'serialize'],
418
                'parseValue' => [$reflectionClass->getName(), 'parseValue'],
419
                'parseLiteral' => [$reflectionClass->getName(), 'parseLiteral'],
420
            ];
421
        }
422
423
        $scalarConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $scalarConfiguration;
424
425
        return ['type' => 'custom-scalar', 'config' => $scalarConfiguration];
426
    }
427
428
    /**
429
     * Get a GraphQL Enum configuration from given enum metadata.
430
     *
431
     * @return array{type: 'enum', config: array}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type: 'enum', config: array} at position 4 could not be parsed: Unknown type name ''enum'' at position 4 in array{type: 'enum', config: array}.
Loading history...
432
     */
433
    private static function enumMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Enum $enumMetadata): array
434
    {
435
        $metadatas = static::getMetadatas($reflectionClass);
436
        $enumValues = array_merge(self::getMetadataMatching($metadatas, Metadata\EnumValue::class), $enumMetadata->values);
0 ignored issues
show
Deprecated Code introduced by
The property Overblog\GraphQLBundle\Annotation\Enum::$values has been deprecated. ( Ignorable by Annotation )

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

436
        $enumValues = array_merge(self::getMetadataMatching($metadatas, Metadata\EnumValue::class), /** @scrutinizer ignore-deprecated */ $enumMetadata->values);
Loading history...
437
438
        $values = [];
439
440
        foreach ($reflectionClass->getConstants() as $name => $value) {
441
            $enumValueAnnotation = current(array_filter($enumValues, fn ($enumValueAnnotation) => $enumValueAnnotation->name === $name));
442
            $valueConfig = [];
443
            $valueConfig['value'] = $value;
444
445
            if (false !== $enumValueAnnotation) {
446
                if (isset($enumValueAnnotation->description)) {
447
                    $valueConfig['description'] = $enumValueAnnotation->description;
448
                }
449
450
                if (isset($enumValueAnnotation->deprecationReason)) {
451
                    $valueConfig['deprecationReason'] = $enumValueAnnotation->deprecationReason;
452
                }
453
            }
454
455
            $values[$name] = $valueConfig;
456
        }
457
458
        $enumConfiguration = ['values' => $values];
459
        $enumConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $enumConfiguration;
460
461
        return ['type' => 'enum', 'config' => $enumConfiguration];
462
    }
463
464
    /**
465
     * Get a GraphQL Union configuration from given union metadata.
466
     *
467
     * @return array{type: 'union', config: array}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type: 'union', config: array} at position 4 could not be parsed: Unknown type name ''union'' at position 4 in array{type: 'union', config: array}.
Loading history...
468
     */
469
    private static function unionMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Union $unionMetadata): array
470
    {
471
        $unionConfiguration = [];
472
        if (!empty($unionMetadata->types)) {
473
            $unionConfiguration['types'] = $unionMetadata->types;
474
        } else {
475
            $types = array_keys(self::$map->searchClassesMapBy(function ($gqlType, $configuration) use ($reflectionClass) {
476
                $typeClassName = $configuration['class'];
477
                $typeMetadata = self::getClassReflection($typeClassName);
478
479
                if ($reflectionClass->isInterface() && $typeMetadata->implementsInterface($reflectionClass->getName())) {
480
                    return true;
481
                }
482
483
                return $typeMetadata->isSubclassOf($reflectionClass->getName());
484
            }, self::GQL_TYPE));
485
            sort($types);
486
            $unionConfiguration['types'] = $types;
487
        }
488
489
        $unionConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $unionConfiguration;
490
491
        if (isset($unionMetadata->resolveType)) {
492
            $unionConfiguration['resolveType'] = self::formatExpression($unionMetadata->resolveType);
0 ignored issues
show
Bug introduced by
It seems like $unionMetadata->resolveType can also be of type null; however, parameter $expression of Overblog\GraphQLBundle\C...ser::formatExpression() 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

492
            $unionConfiguration['resolveType'] = self::formatExpression(/** @scrutinizer ignore-type */ $unionMetadata->resolveType);
Loading history...
493
        } else {
494
            if ($reflectionClass->hasMethod('resolveType')) {
495
                $method = $reflectionClass->getMethod('resolveType');
496
                if ($method->isStatic() && $method->isPublic()) {
497
                    $unionConfiguration['resolveType'] = self::formatExpression(sprintf("@=call('%s::%s', [service('overblog_graphql.type_resolver'), value], true)", self::formatNamespaceForExpression($reflectionClass->getName()), 'resolveType'));
498
                } else {
499
                    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')));
500
                }
501
            } else {
502
                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')));
503
            }
504
        }
505
506
        return ['type' => 'union', 'config' => $unionConfiguration];
507
    }
508
509
    /**
510
     * @phpstan-param ReflectionMethod|ReflectionProperty $reflector
511
     * @phpstan-param class-string<Metadata\Field> $fieldMetadataName
512
     *
513
     * @throws AnnotationException
514
     *
515
     * @return array<string,array>
516
     */
517
    private static function getTypeFieldConfigurationFromReflector(ReflectionClass $reflectionClass, Reflector $reflector, string $fieldMetadataName, string $currentValue = 'value'): array
518
    {
519
        /** @var ReflectionProperty|ReflectionMethod $reflector */
520
        $metadatas = static::getMetadatas($reflector);
521
522
        $fieldMetadata = self::getFirstMetadataMatching($metadatas, $fieldMetadataName);
523
        $accessMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Access::class);
524
        $publicMetadata = self::getFirstMetadataMatching($metadatas, Metadata\IsPublic::class);
525
526
        if (null === $fieldMetadata) {
527
            if (null !== $accessMetadata || null !== $publicMetadata) {
528
                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')));
529
            }
530
531
            return [];
532
        }
533
534
        if ($reflector instanceof ReflectionMethod && !$reflector->isPublic()) {
535
            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()));
536
        }
537
538
        $fieldName = $reflector->getName();
539
        $fieldConfiguration = [];
540
541
        if (isset($fieldMetadata->type)) {
542
            $fieldConfiguration['type'] = $fieldMetadata->type;
543
        }
544
545
        $fieldConfiguration = self::getDescriptionConfiguration($metadatas, true) + $fieldConfiguration;
546
547
        $args = [];
548
549
        /** @var Metadata\Arg[] $argAnnotations */
550
        $argAnnotations = array_merge(self::getMetadataMatching($metadatas, Metadata\Arg::class), $fieldMetadata->args);
551
552
        foreach ($argAnnotations as $arg) {
553
            $args[$arg->name] = ['type' => $arg->type];
554
555
            if (isset($arg->description)) {
556
                $args[$arg->name]['description'] = $arg->description;
557
            }
558
559
            if (isset($arg->default)) {
560
                $args[$arg->name]['defaultValue'] = $arg->default;
561
            }
562
        }
563
564
        if (empty($argAnnotations) && $reflector instanceof ReflectionMethod) {
565
            $args = self::guessArgs($reflectionClass, $reflector);
566
        }
567
568
        if (!empty($args)) {
569
            $fieldConfiguration['args'] = $args;
570
        }
571
572
        $fieldName = $fieldMetadata->name ?? $fieldName;
573
574
        if (isset($fieldMetadata->resolve)) {
575
            $fieldConfiguration['resolve'] = self::formatExpression($fieldMetadata->resolve);
576
        } else {
577
            if ($reflector instanceof ReflectionMethod) {
578
                $fieldConfiguration['resolve'] = self::formatExpression(sprintf('call(%s.%s, %s)', $currentValue, $reflector->getName(), self::formatArgsForExpression($args)));
579
            } else {
580
                if ($fieldName !== $reflector->getName() || 'value' !== $currentValue) {
581
                    $fieldConfiguration['resolve'] = self::formatExpression(sprintf('%s.%s', $currentValue, $reflector->getName()));
582
                }
583
            }
584
        }
585
586
        $argsBuilder = self::getFirstMetadataMatching($metadatas, Metadata\ArgsBuilder::class);
587
        if ($argsBuilder) {
588
            $fieldConfiguration['argsBuilder'] = ['builder' => $argsBuilder->value, 'config' => $argsBuilder->config];
589
        } elseif ($fieldMetadata->argsBuilder) {
590
            if (is_string($fieldMetadata->argsBuilder)) {
591
                $fieldConfiguration['argsBuilder'] = ['builder' => $fieldMetadata->argsBuilder, 'config' => []];
592
            } elseif (is_array($fieldMetadata->argsBuilder)) {
593
                list($builder, $builderConfig) = $fieldMetadata->argsBuilder;
594
                $fieldConfiguration['argsBuilder'] = ['builder' => $builder, 'config' => $builderConfig];
595
            } else {
596
                throw new InvalidArgumentException(sprintf('The attribute "argsBuilder" on metadata %s defined on "%s" must be a string or an array where first index is the builder name and the second is the config.', static::formatMetadata($fieldMetadataName), $reflector->getName()));
597
            }
598
        }
599
        $fieldBuilder = self::getFirstMetadataMatching($metadatas, Metadata\FieldBuilder::class);
600
        if ($fieldBuilder) {
601
            $fieldConfiguration['builder'] = $fieldBuilder->value;
602
            $fieldConfiguration['builderConfig'] = $fieldBuilder->config;
603
        } elseif ($fieldMetadata->fieldBuilder) {
604
            if (is_string($fieldMetadata->fieldBuilder)) {
605
                $fieldConfiguration['builder'] = $fieldMetadata->fieldBuilder;
606
                $fieldConfiguration['builderConfig'] = [];
607
            } elseif (is_array($fieldMetadata->fieldBuilder)) {
608
                list($builder, $builderConfig) = $fieldMetadata->fieldBuilder;
609
                $fieldConfiguration['builder'] = $builder;
610
                $fieldConfiguration['builderConfig'] = $builderConfig ?: [];
611
            } else {
612
                throw new InvalidArgumentException(sprintf('The attribute "fieldBuilder" on metadata %s defined on "%s" must be a string or an array where first index is the builder name and the second is the config.', static::formatMetadata($fieldMetadataName), $reflector->getName()));
613
            }
614
        } else {
615
            if (!isset($fieldMetadata->type)) {
616
                try {
617
                    $fieldConfiguration['type'] = self::guessType($reflectionClass, $reflector, self::VALID_OUTPUT_TYPES);
618
                } catch (TypeGuessingException $e) {
619
                    $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());
620
621
                    throw new InvalidArgumentException($error);
622
                }
623
            }
624
        }
625
626
        if ($accessMetadata) {
627
            $fieldConfiguration['access'] = self::formatExpression($accessMetadata->value);
628
        }
629
630
        if ($publicMetadata) {
631
            $fieldConfiguration['public'] = self::formatExpression($publicMetadata->value);
632
        }
633
634
        if (isset($fieldMetadata->complexity)) {
635
            $fieldConfiguration['complexity'] = self::formatExpression($fieldMetadata->complexity);
636
        }
637
638
        return [$fieldName => $fieldConfiguration];
639
    }
640
641
    /**
642
     * Create GraphQL input fields configuration based on metadatas.
643
     *
644
     * @param ReflectionProperty[] $reflectors
645
     *
646
     * @throws AnnotationException
647
     *
648
     * @return array<string,array>
649
     */
650
    private static function getGraphQLInputFieldsFromMetadatas(ReflectionClass $reflectionClass, array $reflectors): array
651
    {
652
        $fields = [];
653
654
        foreach ($reflectors as $reflector) {
655
            $metadatas = static::getMetadatas($reflector);
656
657
            /** @var Metadata\Field|null $fieldMetadata */
658
            $fieldMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Field::class);
659
660
            // No field metadata found
661
            if (null === $fieldMetadata) {
662
                continue;
663
            }
664
665
            // Ignore field with resolver when the type is an Input
666
            if (isset($fieldMetadata->resolve)) {
667
                continue;
668
            }
669
670
            $fieldName = $reflector->getName();
671
            if (isset($fieldMetadata->type)) {
672
                $fieldType = $fieldMetadata->type;
673
            } else {
674
                try {
675
                    $fieldType = self::guessType($reflectionClass, $reflector, self::VALID_INPUT_TYPES);
676
                } catch (TypeGuessingException $e) {
677
                    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()));
678
                }
679
            }
680
            $fieldConfiguration = [];
681
            if ($fieldType) {
682
                // Resolve a PHP class from a GraphQL type
683
                $resolvedType = self::$map->getType($fieldType);
684
                // We found a type but it is not allowed
685
                if (null !== $resolvedType && !in_array($resolvedType['type'], self::VALID_INPUT_TYPES)) {
686
                    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')));
687
                }
688
689
                $fieldConfiguration['type'] = $fieldType;
690
            }
691
692
            $fieldConfiguration = array_merge(self::getDescriptionConfiguration($metadatas, true), $fieldConfiguration);
693
            $fields[$fieldName] = $fieldConfiguration;
694
        }
695
696
        return $fields;
697
    }
698
699
    /**
700
     * Create GraphQL type fields configuration based on metadatas.
701
     *
702
     * @phpstan-param class-string<Metadata\Field> $fieldMetadataName
703
     *
704
     * @param ReflectionProperty[]|ReflectionMethod[] $reflectors
705
     *
706
     * @throws AnnotationException
707
     */
708
    private static function getGraphQLTypeFieldsFromAnnotations(ReflectionClass $reflectionClass, array $reflectors, string $fieldMetadataName = Metadata\Field::class, string $currentValue = 'value'): array
709
    {
710
        $fields = [];
711
712
        foreach ($reflectors as $reflector) {
713
            $fields = array_merge($fields, self::getTypeFieldConfigurationFromReflector($reflectionClass, $reflector, $fieldMetadataName, $currentValue));
714
        }
715
716
        return $fields;
717
    }
718
719
    /**
720
     * @phpstan-param class-string<Metadata\Query|Metadata\Mutation> $expectedMetadata
721
     *
722
     * Return fields config from Provider methods.
723
     * Loop through configured provider and extract fields targeting the targetType.
724
     *
725
     * @return array<string,array>
726
     */
727
    private static function getGraphQLFieldsFromProviders(ReflectionClass $reflectionClass, string $expectedMetadata, string $targetType, bool $isDefaultTarget = false): array
728
    {
729
        $fields = [];
730
        foreach (self::$providers as ['reflectionClass' => $providerReflection, 'metadata' => $providerMetadata]) {
731
            $defaultAccessAnnotation = self::getFirstMetadataMatching(static::getMetadatas($providerReflection), Metadata\Access::class);
732
            $defaultIsPublicAnnotation = self::getFirstMetadataMatching(static::getMetadatas($providerReflection), Metadata\IsPublic::class);
733
734
            $defaultAccess = $defaultAccessAnnotation ? self::formatExpression($defaultAccessAnnotation->value) : false;
735
            $defaultIsPublic = $defaultIsPublicAnnotation ? self::formatExpression($defaultIsPublicAnnotation->value) : false;
736
737
            $methods = [];
738
            // First found the methods matching the targeted type
739
            foreach ($providerReflection->getMethods() as $method) {
740
                $metadatas = static::getMetadatas($method);
741
742
                $metadata = self::getFirstMetadataMatching($metadatas, [Metadata\Mutation::class, Metadata\Query::class]);
743
                if (null === $metadata) {
744
                    continue;
745
                }
746
747
                // TODO: Remove old property check in 1.1
748
                $metadataTargets = $metadata->targetTypes ?? null;
749
750
                if (null === $metadataTargets) {
751
                    if ($metadata instanceof Metadata\Mutation && isset($providerMetadata->targetMutationTypes)) {
752
                        $metadataTargets = $providerMetadata->targetMutationTypes;
753
                    } elseif ($metadata instanceof Metadata\Query && isset($providerMetadata->targetQueryTypes)) {
754
                        $metadataTargets = $providerMetadata->targetQueryTypes;
755
                    }
756
                }
757
758
                if (null === $metadataTargets) {
759
                    if ($isDefaultTarget) {
760
                        $metadataTargets = [$targetType];
761
                        if (!$metadata instanceof $expectedMetadata) {
762
                            continue;
763
                        }
764
                    } else {
765
                        continue;
766
                    }
767
                }
768
769
                if (!in_array($targetType, $metadataTargets)) {
770
                    continue;
771
                }
772
773
                if (!$metadata instanceof $expectedMetadata) {
774
                    if (Metadata\Mutation::class === $expectedMetadata) {
775
                        $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);
776
                    } else {
777
                        $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);
778
                    }
779
780
                    throw new InvalidArgumentException($message);
781
                }
782
                $methods[$method->getName()] = $method;
783
            }
784
785
            $currentValue = sprintf("service('%s')", self::formatNamespaceForExpression($providerReflection->getName()));
786
            $providerFields = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $methods, $expectedMetadata, $currentValue);
787
            foreach ($providerFields as $fieldName => $fieldConfig) {
788
                if (isset($providerMetadata->prefix)) {
789
                    $fieldName = sprintf('%s%s', $providerMetadata->prefix, $fieldName);
790
                }
791
792
                if ($defaultAccess && !isset($fieldConfig['access'])) {
793
                    $fieldConfig['access'] = $defaultAccess;
794
                }
795
796
                if ($defaultIsPublic && !isset($fieldConfig['public'])) {
797
                    $fieldConfig['public'] = $defaultIsPublic;
798
                }
799
800
                $fields[$fieldName] = $fieldConfig;
801
            }
802
        }
803
804
        return $fields;
805
    }
806
807
    /**
808
     * Get the config for description & deprecation reason.
809
     *
810
     * @return array<'description'|'deprecationReason',string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<'description'|'deprecationReason',string> at position 2 could not be parsed: Unknown type name ''description'' at position 2 in array<'description'|'deprecationReason',string>.
Loading history...
811
     */
812
    private static function getDescriptionConfiguration(array $metadatas, bool $withDeprecation = false): array
813
    {
814
        $config = [];
815
        $descriptionAnnotation = self::getFirstMetadataMatching($metadatas, Metadata\Description::class);
816
        if (null !== $descriptionAnnotation) {
817
            $config['description'] = $descriptionAnnotation->value;
818
        }
819
820
        if ($withDeprecation) {
821
            $deprecatedAnnotation = self::getFirstMetadataMatching($metadatas, Metadata\Deprecated::class);
822
            if (null !== $deprecatedAnnotation) {
823
                $config['deprecationReason'] = $deprecatedAnnotation->value;
824
            }
825
        }
826
827
        return $config;
828
    }
829
830
    /**
831
     * Format an array of args to a list of arguments in an expression.
832
     */
833
    private static function formatArgsForExpression(array $args): string
834
    {
835
        $mapping = [];
836
        foreach ($args as $name => $config) {
837
            $mapping[] = sprintf('%s: "%s"', $name, $config['type']);
838
        }
839
840
        return sprintf('arguments({%s}, args)', implode(', ', $mapping));
841
    }
842
843
    /**
844
     * Format a namespace to be used in an expression (double escape).
845
     */
846
    private static function formatNamespaceForExpression(string $namespace): string
847
    {
848
        return str_replace('\\', '\\\\', $namespace);
849
    }
850
851
    /**
852
     * Get the first metadata matching given class.
853
     *
854
     * @phpstan-template T of object
855
     * @phpstan-param class-string<T>|class-string<T>[] $metadataClasses
856
     * @phpstan-return T|null
857
     *
858
     * @return object|null
859
     */
860
    private static function getFirstMetadataMatching(array $metadatas, $metadataClasses)
861
    {
862
        $metas = self::getMetadataMatching($metadatas, $metadataClasses);
863
864
        return array_shift($metas);
865
    }
866
867
    /**
868
     * Return the metadata matching given class
869
     *
870
     * @phpstan-template T of object
871
     * @phpstan-param class-string<T>|class-string<T>[] $metadataClasses
872
     *
873
     * @return array
874
     */
875
    private static function getMetadataMatching(array $metadatas, $metadataClasses)
876
    {
877
        if (is_string($metadataClasses)) {
878
            $metadataClasses = [$metadataClasses];
879
        }
880
881
        return array_filter($metadatas, function ($metadata) use ($metadataClasses) {
882
            foreach ($metadataClasses as $metadataClass) {
883
                if ($metadata instanceof $metadataClass) {
884
                    return true;
885
                }
886
            }
887
888
            return false;
889
        });
890
    }
891
892
    /**
893
     * Format an expression (ie. add "@=" if not set).
894
     */
895
    private static function formatExpression(string $expression): string
896
    {
897
        return '@=' === substr($expression, 0, 2) ? $expression : sprintf('@=%s', $expression);
898
    }
899
900
    /**
901
     * Suffix a name if it is not already.
902
     */
903
    private static function suffixName(string $name, string $suffix): string
904
    {
905
        return substr($name, -strlen($suffix)) === $suffix ? $name : sprintf('%s%s', $name, $suffix);
906
    }
907
908
    /**
909
     * Try to guess a GraphQL type using configured type guessers
910
     *
911
     * @throws RuntimeException
912
     */
913
    private static function guessType(ReflectionClass $reflectionClass, Reflector $reflector, array $filterGraphQLTypes = []): string
914
    {
915
        $errors = [];
916
        foreach (self::$typeGuessers as $typeGuesser) {
917
            if (!$typeGuesser->supports($reflector)) {
918
                continue;
919
            }
920
            try {
921
                $type = $typeGuesser->guessType($reflectionClass, $reflector, $filterGraphQLTypes);
922
923
                return $type;
924
            } catch (TypeGuessingException $exception) {
925
                $errors[] = sprintf('[%s] %s', $typeGuesser->getName(), $exception->getMessage());
926
            }
927
        }
928
929
        throw new TypeGuessingException(join("\n", $errors));
930
    }
931
932
    /**
933
     * Transform a method arguments from reflection to a list of GraphQL argument.
934
     */
935
    private static function guessArgs(ReflectionClass $reflectionClass, ReflectionMethod $method): array
936
    {
937
        $arguments = [];
938
        foreach ($method->getParameters() as $index => $parameter) {
939
            try {
940
                $gqlType = self::guessType($reflectionClass, $parameter, self::VALID_INPUT_TYPES);
941
            } catch (TypeGuessingException $exception) {
942
                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()));
943
            }
944
945
            $argumentConfig = [];
946
            if ($parameter->isDefaultValueAvailable()) {
947
                $argumentConfig['defaultValue'] = $parameter->getDefaultValue();
948
            }
949
950
            $argumentConfig['type'] = $gqlType;
951
952
            $arguments[$parameter->getName()] = $argumentConfig;
953
        }
954
955
        return $arguments;
956
    }
957
958
    /**
959
     * @return ReflectionProperty[]
960
     */
961
    private static function getClassProperties(ReflectionClass $reflectionClass): array
962
    {
963
        $properties = [];
964
        do {
965
            foreach ($reflectionClass->getProperties() as $property) {
966
                if (isset($properties[$property->getName()])) {
967
                    continue;
968
                }
969
                $properties[$property->getName()] = $property;
970
            }
971
        } while ($reflectionClass = $reflectionClass->getParentClass());
972
973
        return $properties;
974
    }
975
976
    protected static function formatMetadata(string $className): string
977
    {
978
        return sprintf(static::METADATA_FORMAT, str_replace(self::ANNOTATION_NAMESPACE, '', $className));
979
    }
980
981
    abstract protected static function getMetadatas(Reflector $reflector): array;
982
}
983