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

Completed
Pull Request — 0.13 (#675)
by Moritz
11:06
created

AnnotationParser   F

Complexity

Total Complexity 206

Size/Duplication

Total Lines 1034
Duplicated Lines 0 %

Test Coverage

Coverage 95.95%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 206
eloc 457
c 7
b 0
f 0
dl 0
loc 1034
ccs 426
cts 444
cp 0.9595
rs 2

31 Methods

Rating   Name   Duplication   Size   Complexity  
A preParse() 0 3 1
A parse() 0 3 1
F classAnnotationsToGQLConfiguration() 0 119 27
A processFile() 0 29 5
A reset() 0 6 1
A unionAnnotationToGQLConfiguration() 0 21 5
F getGraphQLFieldsFromAnnotations() 0 122 38
A fullyQualifiedClassName() 0 7 3
F getGraphQLFieldsFromProviders() 0 55 16
B graphQLTypeConfigFromAnnotation() 0 35 8
B typeAnnotationToGQLConfiguration() 0 30 10
B resolveTypeFromPhpType() 0 16 8
B getArgs() 0 14 7
A guessArgs() 0 25 5
A formatArgsForExpression() 0 8 2
B enumAnnotationToGQLConfiguration() 0 28 7
A getAnnotationReader() 0 13 4
A formatNamespaceForExpression() 0 3 1
A extractClassAnnotations() 0 27 6
B resolveTypeFromDoctrineType() 0 22 11
B resolveGraphQLTypeFromReflectionType() 0 16 7
A suffixName() 0 3 2
A resolveClassFromType() 0 3 1
A typeInterfaceAnnotationToGQLConfiguration() 0 13 1
A getFirstAnnotationMatching() 0 15 5
A inputAnnotationToGQLConfiguration() 0 9 2
A scalarAnnotationToGQLConfiguration() 0 17 2
A formatExpression() 0 3 2
A getDescriptionConfiguration() 0 16 4
A resolveTypeFromClass() 0 11 5
B guessType() 0 44 9

How to fix   Complexity   

Complex Class

Complex classes like AnnotationParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\Config\Parser;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\AnnotationRegistry;
9
use Doctrine\ORM\Mapping\Column;
10
use Doctrine\ORM\Mapping\JoinColumn;
11
use Doctrine\ORM\Mapping\ManyToMany;
12
use Doctrine\ORM\Mapping\ManyToOne;
13
use Doctrine\ORM\Mapping\OneToMany;
14
use Doctrine\ORM\Mapping\OneToOne;
15
use Overblog\GraphQLBundle\Annotation as GQL;
16
use Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface;
17
use Overblog\GraphQLBundle\Relay\Connection\EdgeInterface;
18
use Symfony\Component\Config\Resource\FileResource;
19
use Symfony\Component\DependencyInjection\ContainerBuilder;
20
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
21
22
class AnnotationParser implements PreParserInterface
23
{
24
    private static $annotationReader = null;
25
    private static $classesMap = [];
26
    private static $providers = [];
27
    private static $doctrineMapping = [];
28
    private static $classAnnotationsCache = [];
29
30
    private const GQL_SCALAR = 'scalar';
31
    private const GQL_ENUM = 'enum';
32
    private const GQL_TYPE = 'type';
33
    private const GQL_INPUT = 'input';
34
    private const GQL_UNION = 'union';
35
    private const GQL_INTERFACE = 'interface';
36
37
    /**
38
     * @see https://facebook.github.io/graphql/draft/#sec-Input-and-Output-Types
39
     */
40
    private const VALID_INPUT_TYPES = [self::GQL_SCALAR, self::GQL_ENUM, self::GQL_INPUT];
41
    private const VALID_OUTPUT_TYPES = [self::GQL_SCALAR, self::GQL_TYPE, self::GQL_INTERFACE, self::GQL_UNION, self::GQL_ENUM];
42
43
    /**
44
     * {@inheritdoc}
45
     *
46
     * @throws \ReflectionException
47
     * @throws InvalidArgumentException
48
     */
49 20
    public static function preParse(\SplFileInfo $file, ContainerBuilder $container, array $configs = []): void
50
    {
51 20
        $container->setParameter('overblog_graphql_types.classes_map', self::processFile($file, $container, $configs, true));
52 20
    }
53
54
    /**
55
     * {@inheritdoc}
56
     *
57
     * @throws \ReflectionException
58
     * @throws InvalidArgumentException
59
     */
60 20
    public static function parse(\SplFileInfo $file, ContainerBuilder $container, array $configs = []): array
61
    {
62 20
        return self::processFile($file, $container, $configs, false);
63
    }
64
65
    /**
66
     * @internal
67
     */
68 58
    public static function reset(): void
69
    {
70 58
        self::$classesMap = [];
71 58
        self::$providers = [];
72 58
        self::$classAnnotationsCache = [];
73 58
        self::$annotationReader = null;
74 58
    }
75
76
    /**
77
     * Process a file.
78
     *
79
     * @param \SplFileInfo     $file
80
     * @param ContainerBuilder $container
81
     * @param array            $configs
82
     * @param bool             $preProcess
83
     *
84
     * @return array
85
     *
86
     * @throws \ReflectionException
87
     * @throws InvalidArgumentException
88
     */
89 20
    private static function processFile(\SplFileInfo $file, ContainerBuilder $container, array $configs, bool $preProcess): array
90
    {
91 20
        self::$doctrineMapping = $configs['doctrine']['types_mapping'];
92 20
        $container->addResource(new FileResource($file->getRealPath()));
93
94
        try {
95 20
            $className = $file->getBasename('.php');
96 20
            if (\preg_match('#namespace (.+);#', \file_get_contents($file->getRealPath()), $matches)) {
97 20
                $className = \trim($matches[1]).'\\'.$className;
98
            }
99 20
            [$reflectionEntity, $classAnnotations, $properties, $methods] = self::extractClassAnnotations($className);
100 20
            $gqlTypes = [];
101
102 20
            foreach ($classAnnotations as $classAnnotation) {
103 20
                $gqlTypes = self::classAnnotationsToGQLConfiguration(
104 20
                    $reflectionEntity,
105 20
                    $classAnnotation,
106 20
                    $configs,
107 20
                    $classAnnotations,
108 20
                    $properties,
109 20
                    $methods,
110 20
                    $gqlTypes,
111 20
                    $preProcess
112
                );
113
            }
114
115 20
            return $preProcess ? self::$classesMap : $gqlTypes;
116 8
        } catch (\InvalidArgumentException $e) {
117 8
            throw new InvalidArgumentException(\sprintf('Failed to parse GraphQL annotations from file "%s".', $file), $e->getCode(), $e);
118
        }
119
    }
120
121
    /**
122
     * @param \ReflectionClass $reflectionEntity
123
     * @param array            $configs
124
     * @param object           $classAnnotation
125
     * @param array            $classAnnotations
126
     * @param array            $properties
127
     * @param array            $methods
128
     * @param array            $gqlTypes
129
     * @param bool             $preProcess
130
     *
131
     * @return array
132
     */
133 20
    private static function classAnnotationsToGQLConfiguration(
134
        \ReflectionClass $reflectionEntity,
135
        $classAnnotation,
136
        array $configs,
137
        array $classAnnotations,
138
        array $properties,
139
        array $methods,
140
        array $gqlTypes,
141
        bool $preProcess
142
    ): array {
143 20
        $gqlConfiguration = $gqlType = $gqlName = null;
144
145
        switch (true) {
146 20
            case $classAnnotation instanceof GQL\Type:
147 20
                $gqlType = self::GQL_TYPE;
148 20
                $gqlName = $classAnnotation->name ?: $reflectionEntity->getShortName();
149 20
                if (!$preProcess) {
150 20
                    $gqlConfiguration = self::typeAnnotationToGQLConfiguration(
151 20
                        $reflectionEntity, $classAnnotation, $gqlName, $classAnnotations, $properties, $methods, $configs
152
                    );
153
154 20
                    if ($classAnnotation instanceof GQL\Relay\Connection) {
155 19
                        if (!$reflectionEntity->implementsInterface(ConnectionInterface::class)) {
156
                            throw new InvalidArgumentException(\sprintf('The annotation @Connection on class "%s" can only be used on class implementing the ConnectionInterface.', $reflectionEntity->getName()));
157
                        }
158
159 19
                        if (!($classAnnotation->edge xor $classAnnotation->node)) {
160
                            throw new InvalidArgumentException(\sprintf('The annotation @Connection on class "%s" is invalid. You must define the "edge" OR the "node" attribute.', $reflectionEntity->getName()));
161
                        }
162
163 19
                        $edgeType = $classAnnotation->edge;
164 19
                        if (!$edgeType) {
165 19
                            $edgeType = \sprintf('%sEdge', $gqlName);
166 19
                            $gqlTypes[$edgeType] = [
167 19
                                'type' => 'object',
168
                                'config' => [
169
                                    'builders' => [
170 19
                                        ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classAnnotation->node]],
171
                                    ],
172
                                ],
173
                            ];
174
                        }
175 19
                        if (!isset($gqlConfiguration['config']['builders'])) {
176 19
                            $gqlConfiguration['config']['builders'] = [];
177
                        }
178 19
                        \array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-connection', 'builderConfig' => ['edgeType' => $edgeType]]);
179
                    }
180
                }
181 20
                break;
182
183 19
            case $classAnnotation instanceof GQL\Input:
184 19
                $gqlType = self::GQL_INPUT;
185 19
                $gqlName = $classAnnotation->name ?: self::suffixName($reflectionEntity->getShortName(), 'Input');
186 19
                if (!$preProcess) {
187 19
                    $gqlConfiguration = self::inputAnnotationToGQLConfiguration(
188 19
                        $classAnnotation, $classAnnotations, $properties, $reflectionEntity->getNamespaceName()
189
                    );
190
                }
191 19
                break;
192
193 19
            case $classAnnotation instanceof GQL\Scalar:
194 19
                $gqlType = self::GQL_SCALAR;
195 19
                if (!$preProcess) {
196 19
                    $gqlConfiguration = self::scalarAnnotationToGQLConfiguration(
197 19
                        $reflectionEntity->getName(), $classAnnotation, $classAnnotations
198
                    );
199
                }
200 19
                break;
201
202 19
            case $classAnnotation instanceof GQL\Enum:
203 19
                $gqlType = self::GQL_ENUM;
204 19
                if (!$preProcess) {
205 19
                    $gqlConfiguration = self::enumAnnotationToGQLConfiguration(
206 19
                        $classAnnotation, $classAnnotations, $reflectionEntity->getConstants()
207
                    );
208
                }
209 19
                break;
210
211 19
            case $classAnnotation instanceof GQL\Union:
212 19
                $gqlType = self::GQL_UNION;
213 19
                if (!$preProcess) {
214 19
                    $gqlConfiguration = self::unionAnnotationToGQLConfiguration(
215 19
                        $reflectionEntity->getName(), $classAnnotation, $classAnnotations, $methods
216
                    );
217
                }
218 19
                break;
219
220 19
            case $classAnnotation instanceof GQL\TypeInterface:
221 19
                $gqlType = self::GQL_INTERFACE;
222 19
                if (!$preProcess) {
223 19
                    $gqlConfiguration = self::typeInterfaceAnnotationToGQLConfiguration(
224 19
                        $classAnnotation, $classAnnotations, $properties, $methods, $reflectionEntity->getNamespaceName()
225
                    );
226
                }
227 19
                break;
228
229 19
            case $classAnnotation instanceof GQL\Provider:
230 19
                if ($preProcess) {
231 19
                    self::$providers[$reflectionEntity->getName()] = ['annotation' => $classAnnotation, 'methods' => $methods, 'annotations' => $classAnnotations];
232
                }
233 19
                break;
234
        }
235
236 20
        if (null !== $gqlType) {
237 20
            if (!$gqlName) {
238 19
                $gqlName = $classAnnotation->name ?: $reflectionEntity->getShortName();
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on Overblog\GraphQLBundle\Annotation\Provider.
Loading history...
239
            }
240
241 20
            if ($preProcess) {
242 20
                if (isset(self::$classesMap[$gqlName])) {
243 1
                    throw new InvalidArgumentException(\sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$classesMap[$gqlName]['class']));
244
                }
245 20
                self::$classesMap[$gqlName] = ['type' => $gqlType, 'class' => $reflectionEntity->getName()];
246
            } else {
247 20
                $gqlTypes = [$gqlName => $gqlConfiguration] + $gqlTypes;
248
            }
249
        }
250
251 20
        return $gqlTypes;
252
    }
253
254 20
    private static function extractClassAnnotations(string $className): array
255
    {
256 20
        if (!isset(self::$classAnnotationsCache[$className])) {
257 20
            $annotationReader = self::getAnnotationReader();
258 20
            $reflectionEntity = new \ReflectionClass($className);
259 20
            $classAnnotations = $annotationReader->getClassAnnotations($reflectionEntity);
260
261 20
            $properties = [];
262 20
            $reflectionClass = new \ReflectionClass($className);
263
            do {
264 20
                foreach ($reflectionClass->getProperties() as $property) {
265 19
                    if (isset($properties[$property->getName()])) {
266 19
                        continue;
267
                    }
268 19
                    $properties[$property->getName()] = ['property' => $property, 'annotations' => $annotationReader->getPropertyAnnotations($property)];
269
                }
270 20
            } while ($reflectionClass = $reflectionClass->getParentClass());
271
272 20
            $methods = [];
273 20
            foreach ($reflectionEntity->getMethods() as $method) {
274 19
                $methods[$method->getName()] = ['method' => $method, 'annotations' => $annotationReader->getMethodAnnotations($method)];
275
            }
276
277 20
            self::$classAnnotationsCache[$className] = [$reflectionEntity, $classAnnotations, $properties, $methods];
278
        }
279
280 20
        return self::$classAnnotationsCache[$className];
281
    }
282
283 20
    private static function typeAnnotationToGQLConfiguration(
284
        \ReflectionClass $reflectionEntity,
285
        GQL\Type $classAnnotation,
286
        string $gqlName,
287
        array $classAnnotations,
288
        array $properties,
289
        array $methods,
290
        array $configs
291
    ): array {
292 20
        $rootQueryType = $configs['definitions']['schema']['default']['query'] ?? null;
293 20
        $rootMutationType = $configs['definitions']['schema']['default']['mutation'] ?? null;
294 20
        $isRootQuery = ($rootQueryType && $gqlName === $rootQueryType);
295 20
        $isRootMutation = ($rootMutationType && $gqlName === $rootMutationType);
296 20
        $currentValue = ($isRootQuery || $isRootMutation) ? \sprintf("service('%s')", self::formatNamespaceForExpression($reflectionEntity->getName())) : 'value';
297
298 20
        $gqlConfiguration = self::graphQLTypeConfigFromAnnotation($classAnnotation, $classAnnotations, $properties, $methods, $reflectionEntity->getNamespaceName(), $currentValue);
299 20
        $providerFields = self::getGraphQLFieldsFromProviders($reflectionEntity->getNamespaceName(), $isRootMutation ? 'Mutation' : 'Query', $gqlName, ($isRootQuery || $isRootMutation));
300 20
        $gqlConfiguration['config']['fields'] = $providerFields + $gqlConfiguration['config']['fields'];
301
302 20
        if ($classAnnotation instanceof GQL\Relay\Edge) {
303 19
            if (!$reflectionEntity->implementsInterface(EdgeInterface::class)) {
304
                throw new InvalidArgumentException(\sprintf('The annotation @Edge on class "%s" can only be used on class implementing the EdgeInterface.', $reflectionEntity->getName()));
305
            }
306 19
            if (!isset($gqlConfiguration['config']['builders'])) {
307 19
                $gqlConfiguration['config']['builders'] = [];
308
            }
309 19
            \array_unshift($gqlConfiguration['config']['builders'], ['builder' => 'relay-edge', 'builderConfig' => ['nodeType' => $classAnnotation->node]]);
310
        }
311
312 20
        return $gqlConfiguration;
313
    }
314
315 20
    private static function getAnnotationReader()
316
    {
317 20
        if (null === self::$annotationReader) {
318 20
            if (!\class_exists('\\Doctrine\\Common\\Annotations\\AnnotationReader') ||
319 20
                !\class_exists('\\Doctrine\\Common\\Annotations\\AnnotationRegistry')) {
320
                throw new \RuntimeException('In order to use graphql annotation, you need to require doctrine annotations');
321
            }
322
323 20
            AnnotationRegistry::registerLoader('class_exists');
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Common\Annotati...istry::registerLoader() has been deprecated: This method is deprecated and will be removed in doctrine/annotations 2.0. Annotations will be autoloaded in 2.0. ( Ignorable by Annotation )

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

323
            /** @scrutinizer ignore-deprecated */ AnnotationRegistry::registerLoader('class_exists');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
324 20
            self::$annotationReader = new AnnotationReader();
325
        }
326
327 20
        return self::$annotationReader;
328
    }
329
330 20
    private static function graphQLTypeConfigFromAnnotation(GQL\Type $typeAnnotation, array $classAnnotations, array $properties, array $methods, string $namespace, string $currentValue): array
331
    {
332 20
        $typeConfiguration = [];
333
334 20
        $fields = self::getGraphQLFieldsFromAnnotations($namespace, $properties, false, false, $currentValue);
335 20
        $fields = self::getGraphQLFieldsFromAnnotations($namespace, $methods, false, true, $currentValue) + $fields;
336
337 20
        $typeConfiguration['fields'] = $fields;
338 20
        $typeConfiguration = self::getDescriptionConfiguration($classAnnotations) + $typeConfiguration;
339
340 20
        if ($typeAnnotation->interfaces) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $typeAnnotation->interfaces of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
341 19
            $typeConfiguration['interfaces'] = $typeAnnotation->interfaces;
342
        }
343
344 20
        if ($typeAnnotation->resolveField) {
345 19
            $typeConfiguration['resolveField'] = self::formatExpression($typeAnnotation->resolveField);
346
        }
347
348 20
        if ($typeAnnotation->builders && !empty($typeAnnotation->builders)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $typeAnnotation->builders of type Overblog\GraphQLBundle\Annotation\FieldsBuilder[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
349
            $typeConfiguration['builders'] = \array_map(function ($fieldsBuilderAnnotation) {
350 19
                return ['builder' => $fieldsBuilderAnnotation->builder, 'builderConfig' => $fieldsBuilderAnnotation->builderConfig];
351 19
            }, $typeAnnotation->builders);
352
        }
353
354 20
        $publicAnnotation = self::getFirstAnnotationMatching($classAnnotations, GQL\IsPublic::class);
355 20
        if ($publicAnnotation) {
356 19
            $typeConfiguration['fieldsDefaultPublic'] = self::formatExpression($publicAnnotation->value);
357
        }
358
359 20
        $accessAnnotation = self::getFirstAnnotationMatching($classAnnotations, GQL\Access::class);
360 20
        if ($accessAnnotation) {
361 19
            $typeConfiguration['fieldsDefaultAccess'] = self::formatExpression($accessAnnotation->value);
362
        }
363
364 20
        return ['type' => $typeAnnotation->isRelay ? 'relay-mutation-payload' : 'object', 'config' => $typeConfiguration];
365
    }
366
367
    /**
368
     * Create a GraphQL Interface type configuration from annotations on properties.
369
     *
370
     * @param GQL\TypeInterface $interfaceAnnotation
371
     * @param array             $classAnnotations
372
     * @param array             $properties
373
     * @param array             $methods
374
     * @param string            $namespace
375
     *
376
     * @return array
377
     */
378 19
    private static function typeInterfaceAnnotationToGQLConfiguration(GQL\TypeInterface $interfaceAnnotation, array $classAnnotations, array $properties, array $methods, string $namespace)
379
    {
380 19
        $interfaceConfiguration = [];
381
382 19
        $fields = self::getGraphQLFieldsFromAnnotations($namespace, $properties);
383 19
        $fields = self::getGraphQLFieldsFromAnnotations($namespace, $methods, false, true) + $fields;
384
385 19
        $interfaceConfiguration['fields'] = $fields;
386 19
        $interfaceConfiguration = self::getDescriptionConfiguration($classAnnotations) + $interfaceConfiguration;
387
388 19
        $interfaceConfiguration['resolveType'] = self::formatExpression($interfaceAnnotation->resolveType);
389
390 19
        return ['type' => 'interface', 'config' => $interfaceConfiguration];
391
    }
392
393
    /**
394
     * Create a GraphQL Input type configuration from annotations on properties.
395
     *
396
     * @param GQL\Input $inputAnnotation
397
     * @param array     $classAnnotations
398
     * @param array     $properties
399
     * @param string    $namespace
400
     *
401
     * @return array
402
     */
403 19
    private static function inputAnnotationToGQLConfiguration(GQL\Input $inputAnnotation, array $classAnnotations, array $properties, string $namespace): array
404
    {
405 19
        $inputConfiguration = [];
406 19
        $fields = self::getGraphQLFieldsFromAnnotations($namespace, $properties, true);
407
408 19
        $inputConfiguration['fields'] = $fields;
409 19
        $inputConfiguration = self::getDescriptionConfiguration($classAnnotations) + $inputConfiguration;
410
411 19
        return ['type' => $inputAnnotation->isRelay ? 'relay-mutation-input' : 'input-object', 'config' => $inputConfiguration];
412
    }
413
414
    /**
415
     * Get a GraphQL scalar configuration from given scalar annotation.
416
     *
417
     * @param string     $className
418
     * @param GQL\Scalar $scalarAnnotation
419
     * @param array      $classAnnotations
420
     *
421
     * @return array
422
     */
423 19
    private static function scalarAnnotationToGQLConfiguration(string $className, GQL\Scalar $scalarAnnotation, array $classAnnotations): array
424
    {
425 19
        $scalarConfiguration = [];
426
427 19
        if ($scalarAnnotation->scalarType) {
428 19
            $scalarConfiguration['scalarType'] = self::formatExpression($scalarAnnotation->scalarType);
429
        } else {
430
            $scalarConfiguration = [
431 19
                'serialize' => [$className, 'serialize'],
432 19
                'parseValue' => [$className, 'parseValue'],
433 19
                'parseLiteral' => [$className, 'parseLiteral'],
434
            ];
435
        }
436
437 19
        $scalarConfiguration = self::getDescriptionConfiguration($classAnnotations) + $scalarConfiguration;
438
439 19
        return ['type' => 'custom-scalar', 'config' => $scalarConfiguration];
440
    }
441
442
    /**
443
     * Get a GraphQL Enum configuration from given enum annotation.
444
     *
445
     * @param GQL\Enum $enumAnnotation
446
     * @param array    $classAnnotations
447
     * @param array    $constants
448
     *
449
     * @return array
450
     */
451 19
    private static function enumAnnotationToGQLConfiguration(GQL\Enum $enumAnnotation, array $classAnnotations, array $constants): array
452
    {
453 19
        $enumValues = $enumAnnotation->values ? $enumAnnotation->values : [];
454
455 19
        $values = [];
456
457 19
        foreach ($constants as $name => $value) {
458
            $valueAnnotation = \current(\array_filter($enumValues, function ($enumValueAnnotation) use ($name) {
459 19
                return $enumValueAnnotation->name == $name;
460 19
            }));
461 19
            $valueConfig = [];
462 19
            $valueConfig['value'] = $value;
463
464 19
            if ($valueAnnotation && $valueAnnotation->description) {
465 19
                $valueConfig['description'] = $valueAnnotation->description;
466
            }
467
468 19
            if ($valueAnnotation && $valueAnnotation->deprecationReason) {
469 19
                $valueConfig['deprecationReason'] = $valueAnnotation->deprecationReason;
470
            }
471
472 19
            $values[$name] = $valueConfig;
473
        }
474
475 19
        $enumConfiguration = ['values' => $values];
476 19
        $enumConfiguration = self::getDescriptionConfiguration($classAnnotations) + $enumConfiguration;
477
478 19
        return ['type' => 'enum', 'config' => $enumConfiguration];
479
    }
480
481
    /**
482
     * Get a GraphQL Union configuration from given union annotation.
483
     *
484
     * @param string    $className
485
     * @param GQL\Union $unionAnnotation
486
     * @param array     $classAnnotations
487
     * @param array     $methods
488
     *
489
     * @return array
490
     */
491 19
    private static function unionAnnotationToGQLConfiguration(string $className, GQL\Union $unionAnnotation, array $classAnnotations, array $methods): array
492
    {
493 19
        $unionConfiguration = ['types' => $unionAnnotation->types];
494 19
        $unionConfiguration = self::getDescriptionConfiguration($classAnnotations) + $unionConfiguration;
495
496 19
        if ($unionAnnotation->resolveType) {
497 19
            $unionConfiguration['resolveType'] = self::formatExpression($unionAnnotation->resolveType);
498
        } else {
499 19
            if (isset($methods['resolveType'])) {
500 19
                $method = $methods['resolveType']['method'];
501 19
                if ($method->isStatic() && $method->isPublic()) {
502 19
                    $unionConfiguration['resolveType'] = self::formatExpression(\sprintf("@=call('%s::%s', [service('overblog_graphql.type_resolver'), value], true)", self::formatNamespaceForExpression($className), 'resolveType'));
503
                } else {
504 19
                    throw new InvalidArgumentException(\sprintf('The "resolveType()" method on class must be static and public. Or you must define a "resolveType" attribute on the @Union annotation.'));
505
                }
506
            } else {
507 1
                throw new InvalidArgumentException(\sprintf('The annotation @Union has no "resolveType" attribute and the related class has no "resolveType()" public static method. You need to define of them.'));
508
            }
509
        }
510
511 19
        return ['type' => 'union', 'config' => $unionConfiguration];
512
    }
513
514
    /**
515
     * Create GraphQL fields configuration based on annotations.
516
     *
517
     * @param string $namespace
518
     * @param array  $propertiesOrMethods
519
     * @param bool   $isInput
520
     * @param bool   $isMethod
521
     * @param string $currentValue
522
     *
523
     * @return array
524
     */
525 20
    private static function getGraphQLFieldsFromAnnotations(string $namespace, array $propertiesOrMethods, bool $isInput = false, bool $isMethod = false, string $currentValue = 'value', string $fieldAnnotationName = 'Field'): array
526
    {
527 20
        $fields = [];
528 20
        foreach ($propertiesOrMethods as $target => $config) {
529 19
            $annotations = $config['annotations'];
530 19
            $method = $isMethod ? $config['method'] : false;
531
532 19
            $fieldAnnotation = self::getFirstAnnotationMatching($annotations, \sprintf('Overblog\GraphQLBundle\Annotation\%s', $fieldAnnotationName));
533 19
            $accessAnnotation = self::getFirstAnnotationMatching($annotations, GQL\Access::class);
534 19
            $publicAnnotation = self::getFirstAnnotationMatching($annotations, GQL\IsPublic::class);
535
536 19
            if (!$fieldAnnotation) {
537 19
                if ($accessAnnotation || $publicAnnotation) {
538 1
                    throw new InvalidArgumentException(\sprintf('The annotations "@Access" and/or "@Visible" defined on "%s" are only usable in addition of annotation "@Field"', $target));
539
                }
540 19
                continue;
541
            }
542
543 19
            if ($isMethod && !$method->isPublic()) {
544 1
                throw new InvalidArgumentException(\sprintf('The Annotation "@Field" can only be applied to public method. The method "%s" is not public.', $target));
545
            }
546
547
            // Ignore field with resolver when the type is an Input
548 19
            if ($fieldAnnotation->resolve && $isInput) {
549
                continue;
550
            }
551
552 19
            $fieldName = $target;
553 19
            $fieldType = $fieldAnnotation->type;
554 19
            $fieldConfiguration = [];
555 19
            if ($fieldType) {
556 19
                $resolvedType = self::resolveClassFromType($fieldType);
557 19
                if (null !== $resolvedType && $isInput && !\in_array($resolvedType['type'], self::VALID_INPUT_TYPES)) {
558
                    throw new InvalidArgumentException(\sprintf('The type "%s" on "%s" is a "%s" not valid on an Input @Field. Only Input, Scalar and Enum are allowed.', $fieldType, $target, $resolvedType['type']));
559
                }
560
561 19
                $fieldConfiguration['type'] = $fieldType;
562
            }
563
564 19
            $fieldConfiguration = self::getDescriptionConfiguration($annotations, true) + $fieldConfiguration;
565
566 19
            if (!$isInput) {
567 19
                $args = self::getArgs($fieldAnnotation->args, $isMethod && !$fieldAnnotation->argsBuilder ? $method : null);
0 ignored issues
show
Bug introduced by
It seems like $isMethod && ! $fieldAnn...uilder ? $method : null can also be of type false; however, parameter $method of Overblog\GraphQLBundle\C...tationParser::getArgs() does only seem to accept ReflectionMethod|null, 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

567
                $args = self::getArgs($fieldAnnotation->args, /** @scrutinizer ignore-type */ $isMethod && !$fieldAnnotation->argsBuilder ? $method : null);
Loading history...
568
569 19
                if (!empty($args)) {
570 19
                    $fieldConfiguration['args'] = $args;
571
                }
572
573 19
                $fieldName = $fieldAnnotation->name ?: $fieldName;
574
575 19
                if ($fieldAnnotation->resolve) {
576 19
                    $fieldConfiguration['resolve'] = self::formatExpression($fieldAnnotation->resolve);
577
                } else {
578 19
                    if ($isMethod) {
579 19
                        $fieldConfiguration['resolve'] = self::formatExpression(\sprintf('call(%s.%s, %s)', $currentValue, $target, self::formatArgsForExpression($args)));
580
                    } else {
581 19
                        if ($fieldName !== $target || 'value' !== $currentValue) {
582
                            $fieldConfiguration['resolve'] = self::formatExpression(\sprintf('%s.%s', $currentValue, $target));
583
                        }
584
                    }
585
                }
586
587 19
                if ($fieldAnnotation->argsBuilder) {
588 19
                    if (\is_string($fieldAnnotation->argsBuilder)) {
589
                        $fieldConfiguration['argsBuilder'] = $fieldAnnotation->argsBuilder;
590 19
                    } elseif (\is_array($fieldAnnotation->argsBuilder)) {
591 19
                        list($builder, $builderConfig) = $fieldAnnotation->argsBuilder;
592 19
                        $fieldConfiguration['argsBuilder'] = ['builder' => $builder, 'config' => $builderConfig];
593
                    } else {
594
                        throw new InvalidArgumentException(\sprintf('The attribute "argsBuilder" on GraphQL annotation "@%s" defined on "%s" must be a string or an array where first index is the builder name and the second is the config.', $fieldAnnotationName, $target));
595
                    }
596
                }
597
598 19
                if ($fieldAnnotation->fieldBuilder) {
599 19
                    if (\is_string($fieldAnnotation->fieldBuilder)) {
600
                        $fieldConfiguration['builder'] = $fieldAnnotation->fieldBuilder;
601 19
                    } elseif (\is_array($fieldAnnotation->fieldBuilder)) {
602 19
                        list($builder, $builderConfig) = $fieldAnnotation->fieldBuilder;
603 19
                        $fieldConfiguration['builder'] = $builder;
604 19
                        $fieldConfiguration['builderConfig'] = $builderConfig ?: [];
605
                    } else {
606 19
                        throw new InvalidArgumentException(\sprintf('The attribute "argsBuilder" on GraphQL annotation "@%s" defined on "%s" must be a string or an array where first index is the builder name and the second is the config.', $fieldAnnotationName, $target));
607
                    }
608
                } else {
609 19
                    if (!$fieldType) {
610 19
                        if ($isMethod) {
611 19
                            if ($method->hasReturnType()) {
612
                                try {
613 19
                                    $fieldConfiguration['type'] = self::resolveGraphQLTypeFromReflectionType($method->getReturnType(), self::VALID_OUTPUT_TYPES);
614
                                } catch (\Exception $e) {
615 19
                                    throw new InvalidArgumentException(\sprintf('The attribute "type" on GraphQL annotation "@%s" is missing on method "%s" and cannot be auto-guessed from type hint "%s"', $fieldAnnotationName, $target, (string) $method->getReturnType()));
616
                                }
617
                            } else {
618 19
                                throw new InvalidArgumentException(\sprintf('The attribute "type" on GraphQL annotation "@%s" is missing on method "%s" and cannot be auto-guessed as there is not return type hint.', $fieldAnnotationName, $target));
619
                            }
620
                        } else {
621
                            try {
622 19
                                $fieldConfiguration['type'] = self::guessType($namespace, $annotations);
623 2
                            } catch (\Exception $e) {
624 2
                                throw new InvalidArgumentException(\sprintf('The attribute "type" on "@%s" defined on "%s" is required and cannot be auto-guessed : %s.', $fieldAnnotationName, $target, $e->getMessage()));
625
                            }
626
                        }
627
                    }
628
                }
629
630 19
                if ($accessAnnotation) {
631 19
                    $fieldConfiguration['access'] = self::formatExpression($accessAnnotation->value);
632
                }
633
634 19
                if ($publicAnnotation) {
635 19
                    $fieldConfiguration['public'] = self::formatExpression($publicAnnotation->value);
636
                }
637
638 19
                if ($fieldAnnotation->complexity) {
639 19
                    $fieldConfiguration['complexity'] = self::formatExpression($fieldAnnotation->complexity);
640
                }
641
            }
642
643 19
            $fields[$fieldName] = $fieldConfiguration;
644
        }
645
646 20
        return $fields;
647
    }
648
649
    /**
650
     * Return fields config from Provider methods.
651
     *
652
     * @param string $namespace
653
     * @param string $annotationName
654
     * @param string $targetType
655
     * @param bool   $isRoot
656
     *
657
     * @return array
658
     */
659 20
    private static function getGraphQLFieldsFromProviders(string $namespace, string $annotationName, string $targetType, bool $isRoot = false)
660
    {
661 20
        $fields = [];
662 20
        foreach (self::$providers as $className => $configuration) {
663 19
            $providerMethods = $configuration['methods'];
664 19
            $providerAnnotation = $configuration['annotation'];
665 19
            $providerAnnotations = $configuration['annotations'];
666
667 19
            $defaultAccessAnnotation = self::getFirstAnnotationMatching($providerAnnotations, GQL\Access::class);
668 19
            $defaultIsPublicAnnotation = self::getFirstAnnotationMatching($providerAnnotations, GQL\IsPublic::class);
669
670 19
            $defaultAccess = $defaultAccessAnnotation ? self::formatExpression($defaultAccessAnnotation->value) : false;
671 19
            $defaultIsPublic = $defaultIsPublicAnnotation ? self::formatExpression($defaultIsPublicAnnotation->value) : false;
672
673 19
            $filteredMethods = [];
674 19
            foreach ($providerMethods as $methodName => $config) {
675 19
                $annotations = $config['annotations'];
676
677 19
                $annotation = self::getFirstAnnotationMatching($annotations, \sprintf('Overblog\\GraphQLBundle\\Annotation\\%s', $annotationName));
678 19
                if (!$annotation) {
679 19
                    continue;
680
                }
681
682 19
                $annotationTarget = 'Query' === $annotationName ? $annotation->targetType : null;
683 19
                if (!$annotationTarget && $isRoot) {
684 19
                    $annotationTarget = $targetType;
685
                }
686
687 19
                if ($annotationTarget !== $targetType) {
688 19
                    continue;
689
                }
690
691 19
                $filteredMethods[$methodName] = $config;
692
            }
693
694 19
            $currentValue = \sprintf("service('%s')", self::formatNamespaceForExpression($className));
695 19
            $providerFields = self::getGraphQLFieldsFromAnnotations($namespace, $filteredMethods, false, true, $currentValue, $annotationName);
696 19
            foreach ($providerFields as $fieldName => $fieldConfig) {
697 19
                if ($providerAnnotation->prefix) {
698 19
                    $fieldName = \sprintf('%s%s', $providerAnnotation->prefix, $fieldName);
699
                }
700
701 19
                if ($defaultAccess && !isset($fieldConfig['access'])) {
702 19
                    $fieldConfig['access'] = $defaultAccess;
703
                }
704
705 19
                if ($defaultIsPublic && !isset($fieldConfig['public'])) {
706 19
                    $fieldConfig['public'] = $defaultIsPublic;
707
                }
708
709 19
                $fields[$fieldName] = $fieldConfig;
710
            }
711
        }
712
713 20
        return $fields;
714
    }
715
716
    /**
717
     * Get the config for description & deprecation reason.
718
     *
719
     * @param array $annotations
720
     * @param bool  $withDeprecation
721
     *
722
     * @return array
723
     */
724 20
    private static function getDescriptionConfiguration(array $annotations, bool $withDeprecation = false): array
725
    {
726 20
        $config = [];
727 20
        $descriptionAnnotation = self::getFirstAnnotationMatching($annotations, GQL\Description::class);
728 20
        if ($descriptionAnnotation) {
729 19
            $config['description'] = $descriptionAnnotation->value;
730
        }
731
732 20
        if ($withDeprecation) {
733 19
            $deprecatedAnnotation = self::getFirstAnnotationMatching($annotations, GQL\Deprecated::class);
734 19
            if ($deprecatedAnnotation) {
735 19
                $config['deprecationReason'] = $deprecatedAnnotation->value;
736
            }
737
        }
738
739 20
        return $config;
740
    }
741
742
    /**
743
     * Get args config from an array of @Arg annotation or by auto-guessing if a method is provided.
744
     *
745
     * @param array             $args
746
     * @param \ReflectionMethod $method
747
     *
748
     * @return array
749
     */
750 19
    private static function getArgs(array $args = null, \ReflectionMethod $method = null): array
751
    {
752 19
        $config = [];
753 19
        if ($args && !empty($args)) {
754 19
            foreach ($args as $arg) {
755 19
                $config[$arg->name] = ['type' => $arg->type]
756 19
                    + ($arg->description ? ['description' => $arg->description] : [])
757 19
                    + ($arg->default ? ['defaultValue' => $arg->default] : []);
758
            }
759 19
        } elseif ($method) {
760 19
            $config = self::guessArgs($method);
761
        }
762
763 19
        return $config;
764
    }
765
766
    /**
767
     * Format an array of args to a list of arguments in an expression.
768
     *
769
     * @param array $args
770
     *
771
     * @return string
772
     */
773 19
    private static function formatArgsForExpression(array $args): string
774
    {
775 19
        $mapping = [];
776 19
        foreach ($args as $name => $config) {
777 19
            $mapping[] = \sprintf('%s: "%s"', $name, $config['type']);
778
        }
779
780 19
        return \sprintf('arguments({%s}, args)', \implode(', ', $mapping));
781
    }
782
783
    /**
784
     * Format a namespace to be used in an expression (double escape).
785
     *
786
     * @param string $namespace
787
     *
788
     * @return string
789
     */
790 19
    private static function formatNamespaceForExpression(string $namespace): string
791
    {
792 19
        return \str_replace('\\', '\\\\', $namespace);
793
    }
794
795
    /**
796
     * Get the first annotation matching given class.
797
     *
798
     * @param array        $annotations
799
     * @param string|array $annotationClass
800
     *
801
     * @return mixed
802
     */
803 20
    private static function getFirstAnnotationMatching(array $annotations, $annotationClass)
804
    {
805 20
        if (\is_string($annotationClass)) {
806 20
            $annotationClass = [$annotationClass];
807
        }
808
809 20
        foreach ($annotations as $annotation) {
810 20
            foreach ($annotationClass as $class) {
811 20
                if ($annotation instanceof $class) {
812 19
                    return $annotation;
813
                }
814
            }
815
        }
816
817 20
        return false;
818
    }
819
820
    /**
821
     * Format an expression (ie. add "@=" if not set).
822
     *
823
     * @param string $expression
824
     *
825
     * @return string
826
     */
827 19
    private static function formatExpression(string $expression)
828
    {
829 19
        return '@=' === \substr($expression, 0, 2) ? $expression : \sprintf('@=%s', $expression);
830
    }
831
832
    /**
833
     * Suffix a name if it is not already.
834
     *
835
     * @param string $name
836
     * @param string $suffix
837
     *
838
     * @return string
839
     */
840 19
    private static function suffixName(string $name, string $suffix)
841
    {
842 19
        return \substr($name, -\strlen($suffix)) === $suffix ? $name : \sprintf('%s%s', $name, $suffix);
843
    }
844
845
    /**
846
     * Try to guess a field type base on is annotations.
847
     *
848
     * @param string $namespace
849
     * @param array  $annotations
850
     *
851
     * @return string
852
     *
853
     * @throws \RuntimeException
854
     */
855 19
    private static function guessType(string $namespace, array $annotations): string
856
    {
857 19
        $columnAnnotation = self::getFirstAnnotationMatching($annotations, Column::class);
858 19
        if ($columnAnnotation) {
859 19
            $type = self::resolveTypeFromDoctrineType($columnAnnotation->type);
860 19
            $nullable = $columnAnnotation->nullable;
861 19
            if ($type) {
862 19
                return $nullable ? $type : \sprintf('%s!', $type);
863
            } else {
864 1
                throw new \RuntimeException(\sprintf('Unable to auto-guess GraphQL type from Doctrine type "%s"', $columnAnnotation->type));
865
            }
866
        }
867
868
        $associationAnnotations = [
869 19
            OneToMany::class => true,
870
            OneToOne::class => false,
871
            ManyToMany::class => true,
872
            ManyToOne::class => false,
873
        ];
874
875 19
        $associationAnnotation = self::getFirstAnnotationMatching($annotations, \array_keys($associationAnnotations));
876 19
        if ($associationAnnotation) {
877 19
            $target = self::fullyQualifiedClassName($associationAnnotation->targetEntity, $namespace);
878 19
            $type = self::resolveTypeFromClass($target, ['type']);
879
880 19
            if ($type) {
881 19
                $isMultiple = $associationAnnotations[\get_class($associationAnnotation)];
882 19
                if ($isMultiple) {
883 19
                    return \sprintf('[%s]!', $type);
884
                } else {
885 19
                    $isNullable = false;
886 19
                    $joinColumn = self::getFirstAnnotationMatching($annotations, JoinColumn::class);
887 19
                    if ($joinColumn) {
888 19
                        $isNullable = $joinColumn->nullable;
889
                    }
890
891 19
                    return \sprintf('%s%s', $type, $isNullable ? '' : '!');
892
                }
893
            } else {
894 1
                throw new \RuntimeException(\sprintf('Unable to auto-guess GraphQL type from Doctrine target class "%s" (check if the target class is a GraphQL type itself (with a @GQL\Type annotation).', $target));
895
            }
896
        }
897
898
        throw new InvalidArgumentException(\sprintf('No Doctrine ORM annotation found.'));
899
    }
900
901
    /**
902
     * Resolve a FQN from classname and namespace.
903
     *
904
     * @param string $className
905
     * @param string $namespace
906
     *
907
     * @return string
908
     *
909
     * @internal
910
     */
911 19
    public static function fullyQualifiedClassName(string $className, string $namespace): string
912
    {
913 19
        if (false === \strpos($className, '\\') && $namespace) {
914 19
            return $namespace.'\\'.$className;
915
        }
916
917 1
        return $className;
918
    }
919
920
    /**
921
     * Resolve a GraphQLType from a doctrine type.
922
     *
923
     * @param string $doctrineType
924
     *
925
     * @return string|null
926
     */
927 19
    private static function resolveTypeFromDoctrineType(string $doctrineType): ?string
928
    {
929 19
        if (isset(self::$doctrineMapping[$doctrineType])) {
930 19
            return self::$doctrineMapping[$doctrineType];
931
        }
932
933 19
        switch ($doctrineType) {
934 19
            case 'integer':
935 19
            case 'smallint':
936 19
            case 'bigint':
937 19
                return 'Int';
938 19
            case 'string':
939 1
            case 'text':
940 19
                return 'String';
941 1
            case 'bool':
942 1
            case 'boolean':
943
                return 'Boolean';
944 1
            case 'float':
945 1
            case 'decimal':
946
                return 'Float';
947
            default:
948 1
                return null;
949
        }
950
    }
951
952
    /**
953
     * Transform a method arguments from reflection to a list of GraphQL argument.
954
     */
955 19
    private static function guessArgs(\ReflectionMethod $method): array
956
    {
957 19
        $arguments = [];
958 19
        foreach ($method->getParameters() as $index => $parameter) {
959 19
            if (!$parameter->hasType()) {
960 1
                throw new InvalidArgumentException(\sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed as there is not type hint.', $index + 1, $parameter->getName(), $method->getName()));
961
            }
962
963
            try {
964 19
                $gqlType = self::resolveGraphQLTypeFromReflectionType($parameter->getType(), self::VALID_INPUT_TYPES, $parameter->isDefaultValueAvailable());
965
            } catch (\Exception $e) {
966
                throw new InvalidArgumentException(\sprintf('Argument n°%s "$%s" on method "%s" cannot be auto-guessed : %s".', $index + 1, $parameter->getName(), $method->getName(), $e->getMessage()));
967
            }
968
969 19
            $argumentConfig = [];
970 19
            if ($parameter->isDefaultValueAvailable()) {
971 19
                $argumentConfig['defaultValue'] = $parameter->getDefaultValue();
972
            }
973
974 19
            $argumentConfig['type'] = $gqlType;
975
976 19
            $arguments[$parameter->getName()] = $argumentConfig;
977
        }
978
979 19
        return $arguments;
980
    }
981
982 19
    private static function resolveGraphQLTypeFromReflectionType(\ReflectionType $type, array $filterGraphQLTypes = null, bool $isOptional = false): string
983
    {
984 19
        $sType = $type->getName();
985 19
        if ($type->isBuiltin()) {
986 19
            $gqlType = self::resolveTypeFromPhpType($sType);
987 19
            if (null === $gqlType) {
988 19
                throw new \RuntimeException(\sprintf('No corresponding GraphQL type found for builtin type "%s"', $sType));
989
            }
990
        } else {
991 19
            $gqlType = self::resolveTypeFromClass($sType, $filterGraphQLTypes);
992 19
            if (null === $gqlType) {
993
                throw new \RuntimeException(\sprintf('No corresponding GraphQL %s found for class "%s"', $filterGraphQLTypes ? \implode(',', $filterGraphQLTypes) : 'object', $sType));
994
            }
995
        }
996
997 19
        return \sprintf('%s%s', $gqlType, ($type->allowsNull() || $isOptional) ? '' : '!');
998
    }
999
1000
    /**
1001
     * Resolve a GraphQL Type from a class name.
1002
     *
1003
     * @param string $className
1004
     * @param array  $wantedTypes
1005
     *
1006
     * @return string|null
1007
     */
1008 19
    private static function resolveTypeFromClass(string $className, array $wantedTypes = null): ?string
1009
    {
1010 19
        foreach (self::$classesMap as $gqlType => $config) {
1011 19
            if ($config['class'] === $className) {
1012 19
                if (!$wantedTypes || \in_array($config['type'], $wantedTypes)) {
1013 19
                    return $gqlType;
1014
                }
1015
            }
1016
        }
1017
1018 1
        return null;
1019
    }
1020
1021
    /**
1022
     * Resolve a PHP class from a GraphQL type.
1023
     *
1024
     * @param string $type
1025
     *
1026
     * @return string|array|null
1027
     */
1028 19
    private static function resolveClassFromType(string $type)
1029
    {
1030 19
        return self::$classesMap[$type] ?? null;
1031
    }
1032
1033
    /**
1034
     * Convert a PHP Builtin type to a GraphQL type.
1035
     *
1036
     * @param string $phpType
1037
     *
1038
     * @return string|null
1039
     */
1040 19
    private static function resolveTypeFromPhpType(string $phpType): ?string
1041
    {
1042 19
        switch ($phpType) {
1043 19
            case 'boolean':
1044 19
            case 'bool':
1045 19
                return 'Boolean';
1046 19
            case 'integer':
1047 19
            case 'int':
1048 19
                return 'Int';
1049 19
            case 'float':
1050 19
            case 'double':
1051 19
                return 'Float';
1052 19
            case 'string':
1053 19
                return 'String';
1054
            default:
1055
                return null;
1056
        }
1057
    }
1058
}
1059