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 — 0.14 (#841)
by Jérémiah
02:54
created

MetadataParser::typeMetadataToGQLConfiguration()   C

Complexity

Conditions 13
Paths 24

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13.0085

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 26
c 1
b 0
f 0
nc 24
nop 4
dl 0
loc 46
ccs 26
cts 27
cp 0.963
crap 13.0085
rs 6.6166

How to fix   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 ReflectionClassConstant;
19
use ReflectionException;
20
use ReflectionMethod;
21
use ReflectionProperty;
22
use Reflector;
23
use RuntimeException;
24
use SplFileInfo;
25
use Symfony\Component\Config\Resource\FileResource;
26
use Symfony\Component\DependencyInjection\ContainerBuilder;
27
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
28
use function array_filter;
29
use function array_keys;
30
use function array_map;
31
use function array_unshift;
32
use function current;
33
use function file_get_contents;
34
use function implode;
35
use function in_array;
36
use function is_array;
37
use function is_string;
38
use function preg_match;
39
use function sprintf;
40
use function str_replace;
41
use function strlen;
42
use function substr;
43
use function trim;
44
45
abstract class MetadataParser implements PreParserInterface
46
{
47
    public const ANNOTATION_NAMESPACE = 'Overblog\GraphQLBundle\Annotation\\';
48
    public const METADATA_FORMAT = '%s';
49
50
    private static ClassesTypesMap $map;
51
    private static array $typeGuessers = [];
52
    private static array $providers = [];
53
    private static array $reflections = [];
54
55
    private const GQL_SCALAR = 'scalar';
56
    private const GQL_ENUM = 'enum';
57
    private const GQL_TYPE = 'type';
58
    private const GQL_INPUT = 'input';
59
    private const GQL_UNION = 'union';
60
    private const GQL_INTERFACE = 'interface';
61
62
    /**
63
     * @see https://facebook.github.io/graphql/draft/#sec-Input-and-Output-Types
64
     */
65
    private const VALID_INPUT_TYPES = [self::GQL_SCALAR, self::GQL_ENUM, self::GQL_INPUT];
66
    private const VALID_OUTPUT_TYPES = [self::GQL_SCALAR, self::GQL_TYPE, self::GQL_INTERFACE, self::GQL_UNION, self::GQL_ENUM];
67
68
    /**
69
     * {@inheritdoc}
70
     *
71
     * @throws InvalidArgumentException
72
     * @throws ReflectionException
73
     */
74 52
    public static function preParse(SplFileInfo $file, ContainerBuilder $container, array $configs = []): void
75
    {
76 52
        $container->setParameter('overblog_graphql_types.classes_map', self::processFile($file, $container, $configs, true));
77 52
    }
78
79
    /**
80
     * @throws InvalidArgumentException
81
     * @throws ReflectionException
82
     */
83 52
    public static function parse(SplFileInfo $file, ContainerBuilder $container, array $configs = []): array
84
    {
85 52
        return self::processFile($file, $container, $configs, false);
86
    }
87
88
    /**
89
     * @internal
90
     */
91 92
    public static function reset(array $configs): void
92
    {
93 92
        self::$map = new ClassesTypesMap();
94 92
        self::$typeGuessers = [
95 92
            new DocBlockTypeGuesser(self::$map),
96 92
            new TypeHintTypeGuesser(self::$map),
97 92
            new DoctrineTypeGuesser(self::$map, $configs['doctrine']['types_mapping']),
98
        ];
99 92
        self::$providers = [];
100 92
        self::$reflections = [];
101 92
    }
102
103
    /**
104
     * Process a file.
105
     *
106
     * @throws InvalidArgumentException|ReflectionException|AnnotationException
107
     */
108 52
    private static function processFile(SplFileInfo $file, ContainerBuilder $container, array $configs, bool $preProcess): array
109
    {
110 52
        $container->addResource(new FileResource($file->getRealPath()));
111
112
        try {
113 52
            $className = $file->getBasename('.php');
114 52
            if (preg_match('#namespace (.+);#', file_get_contents($file->getRealPath()), $matches)) {
115 52
                $className = trim($matches[1]).'\\'.$className;
116
            }
117
118 52
            $gqlTypes = [];
119
            /** @phpstan-ignore-next-line */
120 52
            $reflectionClass = self::getClassReflection($className);
121
122 52
            foreach (static::getMetadatas($reflectionClass) as $classMetadata) {
123 52
                if ($classMetadata instanceof Meta) {
124 52
                    $gqlTypes = self::classMetadatasToGQLConfiguration(
125 52
                        $reflectionClass,
126
                        $classMetadata,
127
                        $configs,
128
                        $gqlTypes,
129
                        $preProcess
130
                    );
131
                }
132
            }
133
134 52
            return $preProcess ? self::$map->toArray() : $gqlTypes;
135 20
        } catch (\InvalidArgumentException $e) {
136 20
            throw new InvalidArgumentException(sprintf('Failed to parse GraphQL metadata from file "%s".', $file), $e->getCode(), $e);
137
        }
138
    }
139
140
    /**
141
     * @return array<string,array>
142
     */
143 52
    private static function classMetadatasToGQLConfiguration(
144
        ReflectionClass $reflectionClass,
145
        Meta $classMetadata,
146
        array $configs,
147
        array $gqlTypes,
148
        bool $preProcess
149
    ): array {
150 52
        $gqlConfiguration = $gqlType = $gqlName = null;
151
152
        switch (true) {
153 52
            case $classMetadata instanceof Metadata\Type:
154 52
                $gqlType = self::GQL_TYPE;
155 52
                $gqlName = $classMetadata->name ?? $reflectionClass->getShortName();
156 52
                if (!$preProcess) {
157 52
                    $gqlConfiguration = self::typeMetadataToGQLConfiguration($reflectionClass, $classMetadata, $gqlName, $configs);
158
159 52
                    if ($classMetadata instanceof Metadata\Relay\Connection) {
160 51
                        if (!$reflectionClass->implementsInterface(ConnectionInterface::class)) {
161
                            throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" can only be used on class implementing the ConnectionInterface.', self::formatMetadata('Connection'), $reflectionClass->getName()));
162
                        }
163
164 51
                        if (!(isset($classMetadata->edge) xor isset($classMetadata->node))) {
165
                            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()));
166
                        }
167
168 51
                        $edgeType = $classMetadata->edge ?? false;
169 51
                        if (!$edgeType) {
170 51
                            $edgeType = $gqlName.'Edge';
171 51
                            $gqlTypes[$edgeType] = [
172 51
                                'type' => 'object',
173
                                'config' => [
174
                                    'builders' => [
175 51
                                        ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classMetadata->node]],
176
                                    ],
177
                                ],
178
                            ];
179
                        }
180
181 51
                        if (!isset($gqlConfiguration['config']['builders'])) {
182 51
                            $gqlConfiguration['config']['builders'] = [];
183
                        }
184
185 51
                        array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-connection', 'builderConfig' => ['edgeType' => $edgeType]]);
186
                    }
187
                }
188 52
                break;
189
190 51
            case $classMetadata instanceof Metadata\Input:
191 51
                $gqlType = self::GQL_INPUT;
192 51
                $gqlName = $classMetadata->name ?? self::suffixName($reflectionClass->getShortName(), 'Input');
193 51
                if (!$preProcess) {
194 51
                    $gqlConfiguration = self::inputMetadataToGQLConfiguration($reflectionClass, $classMetadata);
195
                }
196 51
                break;
197
198 51
            case $classMetadata instanceof Metadata\Scalar:
199 51
                $gqlType = self::GQL_SCALAR;
200 51
                if (!$preProcess) {
201 51
                    $gqlConfiguration = self::scalarMetadataToGQLConfiguration($reflectionClass, $classMetadata);
202
                }
203 51
                break;
204
205 51
            case $classMetadata instanceof Metadata\Enum:
206 51
                $gqlType = self::GQL_ENUM;
207 51
                if (!$preProcess) {
208 51
                    $gqlConfiguration = self::enumMetadataToGQLConfiguration($reflectionClass, $classMetadata);
209
                }
210 51
                break;
211
212 51
            case $classMetadata instanceof Metadata\Union:
213 51
                $gqlType = self::GQL_UNION;
214 51
                if (!$preProcess) {
215 51
                    $gqlConfiguration = self::unionMetadataToGQLConfiguration($reflectionClass, $classMetadata);
216
                }
217 51
                break;
218
219 51
            case $classMetadata instanceof Metadata\TypeInterface:
220 51
                $gqlType = self::GQL_INTERFACE;
221 51
                if (!$preProcess) {
222 51
                    $gqlConfiguration = self::typeInterfaceMetadataToGQLConfiguration($reflectionClass, $classMetadata);
223
                }
224 51
                break;
225
226 51
            case $classMetadata instanceof Metadata\Provider:
227 51
                if ($preProcess) {
228 51
                    self::$providers[] = ['reflectionClass' => $reflectionClass, 'metadata' => $classMetadata];
229
                }
230
231 51
                return [];
232
        }
233
234 52
        if (null !== $gqlType) {
235 52
            if (!$gqlName) {
236 51
                $gqlName = !empty($classMetadata->name) ? $classMetadata->name : $reflectionClass->getShortName();
237
            }
238
239 52
            if ($preProcess) {
240 52
                if (self::$map->hasType($gqlName)) {
241 2
                    throw new InvalidArgumentException(sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$map->getType($gqlName)['class']));
242
                }
243 52
                self::$map->addClassType($gqlName, $reflectionClass->getName(), $gqlType);
244
            } else {
245 52
                $gqlTypes = [$gqlName => $gqlConfiguration] + $gqlTypes;
246
            }
247
        }
248
249 52
        return $gqlTypes;
250
    }
251
252
    /**
253
     * @throws ReflectionException
254
     * @phpstan-param class-string $className
255
     */
256 52
    private static function getClassReflection(string $className): ReflectionClass
257
    {
258 52
        self::$reflections[$className] ??= new ReflectionClass($className);
259
260 52
        return self::$reflections[$className];
261
    }
262
263 52
    private static function typeMetadataToGQLConfiguration(
264
        ReflectionClass $reflectionClass,
265
        Metadata\Type $classMetadata,
266
        string $gqlName,
267
        array $configs
268
    ): array {
269 52
        $isMutation = $isDefault = $isRoot = false;
270 52
        if (isset($configs['definitions']['schema'])) {
271 51
            $defaultSchemaName = isset($configs['definitions']['schema']['default']) ? 'default' : array_key_first($configs['definitions']['schema']);
272 51
            foreach ($configs['definitions']['schema'] as $schemaName => $schema) {
273 51
                $schemaQuery = $schema['query'] ?? null;
274 51
                $schemaMutation = $schema['mutation'] ?? null;
275
276 51
                if ($gqlName === $schemaQuery) {
277 51
                    $isRoot = true;
278 51
                    if ($defaultSchemaName === $schemaName) {
279 51
                        $isDefault = true;
280
                    }
281 51
                } elseif ($gqlName === $schemaMutation) {
282 51
                    $isMutation = true;
283 51
                    $isRoot = true;
284 51
                    if ($defaultSchemaName === $schemaName) {
285 51
                        $isDefault = true;
286
                    }
287
                }
288
            }
289
        }
290
291 52
        $currentValue = $isRoot ? sprintf("service('%s')", self::formatNamespaceForExpression($reflectionClass->getName())) : 'value';
292
293 52
        $gqlConfiguration = self::graphQLTypeConfigFromAnnotation($reflectionClass, $classMetadata, $currentValue);
294
295 52
        $providerFields = self::getGraphQLFieldsFromProviders($reflectionClass, $isMutation ? Metadata\Mutation::class : Metadata\Query::class, $gqlName, $isDefault);
296 52
        $gqlConfiguration['config']['fields'] = array_merge($gqlConfiguration['config']['fields'], $providerFields);
297
298 52
        if ($classMetadata instanceof Metadata\Relay\Edge) {
299 51
            if (!$reflectionClass->implementsInterface(EdgeInterface::class)) {
300
                throw new InvalidArgumentException(sprintf('The metadata %s on class "%s" can only be used on class implementing the EdgeInterface.', self::formatMetadata('Edge'), $reflectionClass->getName()));
301
            }
302 51
            if (!isset($gqlConfiguration['config']['builders'])) {
303 51
                $gqlConfiguration['config']['builders'] = [];
304
            }
305 51
            array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classMetadata->node]]);
306
        }
307
308 52
        return $gqlConfiguration;
309
    }
310
311
    /**
312
     * @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...
313
     */
314 52
    private static function graphQLTypeConfigFromAnnotation(ReflectionClass $reflectionClass, Metadata\Type $typeAnnotation, string $currentValue): array
315
    {
316 52
        $typeConfiguration = [];
317 52
        $metadatas = static::getMetadatas($reflectionClass);
318
319 52
        $fieldsFromProperties = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, self::getClassProperties($reflectionClass), Metadata\Field::class, $currentValue);
320 52
        $fieldsFromMethods = self::getGraphQLTypeFieldsFromAnnotations($reflectionClass, $reflectionClass->getMethods(), Metadata\Field::class, $currentValue);
321
322 52
        $typeConfiguration['fields'] = array_merge($fieldsFromProperties, $fieldsFromMethods);
323 52
        $typeConfiguration = self::getDescriptionConfiguration($metadatas) + $typeConfiguration;
324
325 52
        if (!empty($typeAnnotation->interfaces)) {
326 51
            $typeConfiguration['interfaces'] = $typeAnnotation->interfaces;
327
        } else {
328 52
            $interfaces = array_keys(self::$map->searchClassesMapBy(function ($gqlType, $configuration) use ($reflectionClass) {
329 51
                ['class' => $interfaceClassName] = $configuration;
330
331 51
                $interfaceMetadata = self::getClassReflection($interfaceClassName);
332 51
                if ($interfaceMetadata->isInterface() && $reflectionClass->implementsInterface($interfaceMetadata->getName())) {
333 51
                    return true;
334
                }
335
336 51
                return $reflectionClass->isSubclassOf($interfaceClassName);
337 52
            }, self::GQL_INTERFACE));
338
339 52
            sort($interfaces);
340 52
            $typeConfiguration['interfaces'] = $interfaces;
341
        }
342
343 52
        if (isset($typeAnnotation->resolveField)) {
344 51
            $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

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

413
            $scalarConfiguration['scalarType'] = self::formatExpression(/** @scrutinizer ignore-type */ $scalarAnnotation->scalarType);
Loading history...
414
        } else {
415 51
            $scalarConfiguration = [
416 51
                'serialize' => [$reflectionClass->getName(), 'serialize'],
417 51
                'parseValue' => [$reflectionClass->getName(), 'parseValue'],
418 51
                'parseLiteral' => [$reflectionClass->getName(), 'parseLiteral'],
419
            ];
420
        }
421
422 51
        $scalarConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $scalarConfiguration;
423
424 51
        return ['type' => 'custom-scalar', 'config' => $scalarConfiguration];
425
    }
426
427
    /**
428
     * Get a GraphQL Enum configuration from given enum metadata.
429
     *
430
     * @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...
431
     */
432 51
    private static function enumMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Enum $enumMetadata): array
433
    {
434 51
        $metadatas = static::getMetadatas($reflectionClass);
435 51
        $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

435
        $enumValues = array_merge(self::getMetadataMatching($metadatas, Metadata\EnumValue::class), /** @scrutinizer ignore-deprecated */ $enumMetadata->values);
Loading history...
436
437 51
        $values = [];
438
439 51
        foreach ($reflectionClass->getConstants() as $name => $value) {
440 51
            $reflectionConstant = new ReflectionClassConstant($reflectionClass->getName(), $name);
441 51
            $valueConfig = self::getDescriptionConfiguration(static::getMetadatas($reflectionConstant), true);
442
443 51
            $enumValueAnnotation = current(array_filter($enumValues, fn ($enumValueAnnotation) => $enumValueAnnotation->name === $name));
444 51
            $valueConfig['value'] = $value;
445
446 51
            if (false !== $enumValueAnnotation) {
447 27
                if (isset($enumValueAnnotation->description)) {
448 27
                    $valueConfig['description'] = $enumValueAnnotation->description;
449
                }
450
451 27
                if (isset($enumValueAnnotation->deprecationReason)) {
452 27
                    $valueConfig['deprecationReason'] = $enumValueAnnotation->deprecationReason;
453
                }
454
            }
455
456 51
            $values[$name] = $valueConfig;
457
        }
458
459 51
        $enumConfiguration = ['values' => $values];
460 51
        $enumConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $enumConfiguration;
461
462 51
        return ['type' => 'enum', 'config' => $enumConfiguration];
463
    }
464
465
    /**
466
     * Get a GraphQL Union configuration from given union metadata.
467
     *
468
     * @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...
469
     */
470 51
    private static function unionMetadataToGQLConfiguration(ReflectionClass $reflectionClass, Metadata\Union $unionMetadata): array
471
    {
472 51
        $unionConfiguration = [];
473 51
        if (!empty($unionMetadata->types)) {
474 51
            $unionConfiguration['types'] = $unionMetadata->types;
475
        } else {
476 51
            $types = array_keys(self::$map->searchClassesMapBy(function ($gqlType, $configuration) use ($reflectionClass) {
477 51
                $typeClassName = $configuration['class'];
478 51
                $typeMetadata = self::getClassReflection($typeClassName);
479
480 51
                if ($reflectionClass->isInterface() && $typeMetadata->implementsInterface($reflectionClass->getName())) {
481 51
                    return true;
482
                }
483
484 51
                return $typeMetadata->isSubclassOf($reflectionClass->getName());
485 51
            }, self::GQL_TYPE));
486 51
            sort($types);
487 51
            $unionConfiguration['types'] = $types;
488
        }
489
490 51
        $unionConfiguration = self::getDescriptionConfiguration(static::getMetadatas($reflectionClass)) + $unionConfiguration;
491
492 51
        if (isset($unionMetadata->resolveType)) {
493 51
            $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

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