Completed
Push — master ( 65b378...79b6ae )
by David
12s
created

ControllerQueryProvider::toGraphQlType()   B

Complexity

Conditions 11
Paths 10

Size

Total Lines 28
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 28
rs 7.3166
c 0
b 0
f 0
cc 11
nc 10
nop 2

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
4
namespace TheCodingMachine\GraphQL\Controllers;
5
6
use function array_map;
7
use function array_merge;
8
use function get_class;
9
use function gettype;
10
use GraphQL\Type\Definition\InputType;
11
use GraphQL\Type\Definition\OutputType;
12
use phpDocumentor\Reflection\Types\Nullable;
13
use Psr\Container\ContainerInterface;
14
use ReflectionMethod;
15
use TheCodingMachine\GraphQL\Controllers\Annotations\Exceptions\ClassNotFoundException;
16
use TheCodingMachine\GraphQL\Controllers\Mappers\RecursiveTypeMapper;
17
use TheCodingMachine\GraphQL\Controllers\Mappers\TypeMapperInterface;
18
use TheCodingMachine\GraphQL\Controllers\Types\UnionType;
19
use function is_object;
20
use Iterator;
21
use IteratorAggregate;
22
use phpDocumentor\Reflection\DocBlock;
23
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
24
use phpDocumentor\Reflection\Type;
25
use phpDocumentor\Reflection\Types\Array_;
26
use phpDocumentor\Reflection\Types\Boolean;
27
use phpDocumentor\Reflection\Types\Compound;
28
use phpDocumentor\Reflection\Types\ContextFactory;
29
use phpDocumentor\Reflection\Types\Float_;
30
use phpDocumentor\Reflection\Types\Iterable_;
31
use phpDocumentor\Reflection\Types\Mixed_;
32
use phpDocumentor\Reflection\Types\Null_;
33
use phpDocumentor\Reflection\Types\Object_;
34
use phpDocumentor\Reflection\Types\String_;
35
use phpDocumentor\Reflection\Types\Integer;
36
use ReflectionClass;
37
use TheCodingMachine\GraphQL\Controllers\Annotations\SourceField;
38
use TheCodingMachine\GraphQL\Controllers\Annotations\Logged;
39
use TheCodingMachine\GraphQL\Controllers\Annotations\Mutation;
40
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
41
use TheCodingMachine\GraphQL\Controllers\Mappers\CannotMapTypeException;
42
use TheCodingMachine\GraphQL\Controllers\Mappers\RecursiveTypeMapperInterface;
43
use TheCodingMachine\GraphQL\Controllers\Reflection\CommentParser;
44
use TheCodingMachine\GraphQL\Controllers\Security\AuthenticationServiceInterface;
45
use TheCodingMachine\GraphQL\Controllers\Security\AuthorizationServiceInterface;
46
use TheCodingMachine\GraphQL\Controllers\Types\DateTimeType;
47
use GraphQL\Type\Definition\Type as GraphQLType;
48
49
/**
50
 * A query provider that looks for queries in a "controller"
51
 */
52
class ControllerQueryProvider implements QueryProviderInterface
53
{
54
    /**
55
     * @var object
56
     */
57
    private $controller;
58
    /**
59
     * @var AnnotationReader
60
     */
61
    private $annotationReader;
62
    /**
63
     * @var RecursiveTypeMapperInterface
64
     */
65
    private $typeMapper;
66
    /**
67
     * @var HydratorInterface
68
     */
69
    private $hydrator;
70
    /**
71
     * @var AuthenticationServiceInterface
72
     */
73
    private $authenticationService;
74
    /**
75
     * @var AuthorizationServiceInterface
76
     */
77
    private $authorizationService;
78
    /**
79
     * @var ContainerInterface
80
     */
81
    private $registry;
82
83
    /**
84
     * @param object $controller
85
     * @param AnnotationReader $annotationReader
86
     * @param RecursiveTypeMapperInterface $typeMapper
87
     * @param HydratorInterface $hydrator
88
     * @param AuthenticationServiceInterface $authenticationService
89
     * @param AuthorizationServiceInterface $authorizationService
90
     * @param ContainerInterface $registry The registry is used to fetch custom return types by container identifier (using the returnType parameter of the Type annotation)
91
     */
92
    public function __construct($controller, AnnotationReader $annotationReader, RecursiveTypeMapperInterface $typeMapper,
93
                                HydratorInterface $hydrator, AuthenticationServiceInterface $authenticationService,
94
                                AuthorizationServiceInterface $authorizationService, ContainerInterface $registry)
95
    {
96
        $this->controller = $controller;
97
        $this->annotationReader = $annotationReader;
98
        $this->typeMapper = $typeMapper;
99
        $this->hydrator = $hydrator;
100
        $this->authenticationService = $authenticationService;
101
        $this->authorizationService = $authorizationService;
102
        $this->registry = $registry;
103
    }
104
105
    /**
106
     * @return QueryField[]
107
     */
108
    public function getQueries(): array
109
    {
110
        return $this->getFieldsByAnnotations(Query::class, false);
111
    }
112
113
    /**
114
     * @return QueryField[]
115
     */
116
    public function getMutations(): array
117
    {
118
        return $this->getFieldsByAnnotations(Mutation::class, false);
119
    }
120
121
    /**
122
     * @return array<string, QueryField> QueryField indexed by name.
123
     */
124
    public function getFields(): array
125
    {
126
        $fieldAnnotations = $this->getFieldsByAnnotations(Annotations\Field::class, true);
127
        $sourceFields = $this->getSourceFields();
128
129
        $fields = [];
130
        foreach ($fieldAnnotations as $field) {
131
            $fields[$field->name] = $field;
132
        }
133
        foreach ($sourceFields as $field) {
134
            $fields[$field->name] = $field;
135
        }
136
137
        return $fields;
138
    }
139
140
    /**
141
     * @param string $annotationName
142
     * @param bool $injectSource Whether to inject the source object or not as the first argument. True for @Field, false for @Query and @Mutation
143
     * @return QueryField[]
144
     * @throws \ReflectionException
145
     */
146
    private function getFieldsByAnnotations(string $annotationName, bool $injectSource): array
147
    {
148
        $refClass = new \ReflectionClass($this->controller);
149
        $docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
150
        $contextFactory = new ContextFactory();
151
152
        //$refClass = ReflectionClass::createFromInstance($this->controller);
153
154
        $queryList = [];
155
156
        $oldDeclaringClass = null;
157
        $context = null;
158
159
        foreach ($refClass->getMethods() as $refMethod) {
160
            // First, let's check the "Query" or "Mutation" or "Field" annotation
161
            $queryAnnotation = $this->annotationReader->getRequestAnnotation($refMethod, $annotationName);
162
163
            if ($queryAnnotation !== null) {
164
                if (!$this->isAuthorized($refMethod)) {
165
                    continue;
166
                }
167
168
                $docComment = $refMethod->getDocComment() ?: '/** */';
169
170
                // context is changing based on the class the method is declared in.
171
                // we assume methods will be returned packed by classes so we do this little cache
172
                if ($oldDeclaringClass !== $refMethod->getDeclaringClass()->getName()) {
173
                    $context = $contextFactory->createFromReflector($refMethod);
174
                    $oldDeclaringClass = $refMethod->getDeclaringClass()->getName();
175
                }
176
177
                $docBlockObj = $docBlockFactory->create($docComment, $context);
0 ignored issues
show
Bug introduced by
It seems like $docComment can also be of type true; however, parameter $docblock of phpDocumentor\Reflection\DocBlockFactory::create() does only seem to accept object|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

177
                $docBlockObj = $docBlockFactory->create(/** @scrutinizer ignore-type */ $docComment, $context);
Loading history...
178
179
                // TODO: change CommentParser to use $docBlockObj methods instead.
180
                $docBlock = new CommentParser($refMethod->getDocComment());
0 ignored issues
show
Bug introduced by
It seems like $refMethod->getDocComment() can also be of type boolean; however, parameter $docBlock of TheCodingMachine\GraphQL...ntParser::__construct() 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

180
                $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
181
182
                $methodName = $refMethod->getName();
183
                $name = $queryAnnotation->getName() ?: $methodName;
184
185
                $parameters = $refMethod->getParameters();
186
                if ($injectSource === true) {
187
                    $first_parameter = array_shift($parameters);
0 ignored issues
show
Unused Code introduced by
The assignment to $first_parameter is dead and can be removed.
Loading history...
188
                    // TODO: check that $first_parameter type is correct.
189
                }
190
191
                $args = $this->mapParameters($parameters, $docBlockObj);
192
193
                if ($queryAnnotation->getReturnType()) {
194
                    $type = $this->registry->get($queryAnnotation->getReturnType());
195
                    if (!$type instanceof OutputType) {
196
                        throw new \InvalidArgumentException(sprintf("In %s::%s, the 'returnType' parameter in @Type annotation should contain a container identifier that points to an entry that implements GraphQL\\Type\\Definition\\OutputType. The '%s' container entry does not implement GraphQL\\Type\\Definition\\OutputType", $refMethod->getDeclaringClass()->getName(), $refMethod->getName(), $queryAnnotation->getReturnType()));
197
                    }
198
                } else {
199
                    $type = $this->mapReturnType($refMethod, $docBlockObj);
200
                }
201
202
                $queryList[] = new QueryField($name, $type, $args, [$this->controller, $methodName], null, $this->hydrator, $docBlock->getComment(), $injectSource);
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type GraphQL\Type\Definition\Type; however, parameter $type of TheCodingMachine\GraphQL...eryField::__construct() does only seem to accept GraphQL\Type\Definition\OutputType, 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

202
                $queryList[] = new QueryField($name, /** @scrutinizer ignore-type */ $type, $args, [$this->controller, $methodName], null, $this->hydrator, $docBlock->getComment(), $injectSource);
Loading history...
203
            }
204
        }
205
206
        return $queryList;
207
    }
208
209
    /**
210
     * @return GraphQLType&OutputType
211
     */
212
    private function mapReturnType(ReflectionMethod $refMethod, DocBlock $docBlockObj): GraphQLType
213
    {
214
        $returnType = $refMethod->getReturnType();
215
        if ($returnType !== null) {
216
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
217
            $phpdocType = $typeResolver->resolve((string) $returnType);
218
        } else {
219
            $phpdocType = new Mixed_();
220
        }
221
222
        $docBlockReturnType = $this->getDocBlocReturnType($docBlockObj, $refMethod);
223
224
        try {
225
            /** @var GraphQLType&OutputType $type */
226
            $type = $this->mapType($phpdocType, $docBlockReturnType, $returnType ? $returnType->allowsNull() : false, false);
227
        } catch (TypeMappingException $e) {
228
            throw TypeMappingException::wrapWithReturnInfo($e, $refMethod);
229
        } catch (CannotMapTypeException $e) {
230
            throw CannotMapTypeException::wrapWithReturnInfo($e, $refMethod);
231
        }
232
        return $type;
233
    }
234
235
    private function getDocBlocReturnType(DocBlock $docBlock, \ReflectionMethod $refMethod): ?Type
236
    {
237
        /** @var Return_[] $returnTypeTags */
238
        $returnTypeTags = $docBlock->getTagsByName('return');
239
        if (count($returnTypeTags) > 1) {
240
            throw InvalidDocBlockException::tooManyReturnTags($refMethod);
241
        }
242
        $docBlockReturnType = null;
243
        if (isset($returnTypeTags[0])) {
244
            $docBlockReturnType = $returnTypeTags[0]->getType();
245
        }
246
        return $docBlockReturnType;
247
    }
248
249
    /**
250
     * @return QueryField[]
251
     */
252
    private function getSourceFields(): array
253
    {
254
        $refClass = new \ReflectionClass($this->controller);
255
        $docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
256
        $contextFactory = new ContextFactory();
257
258
        /** @var SourceField[] $sourceFields */
259
        $sourceFields = $this->annotationReader->getSourceFields($refClass);
260
261
        if ($this->controller instanceof FromSourceFieldsInterface) {
262
            $sourceFields = array_merge($sourceFields, $this->controller->getSourceFields());
263
        }
264
265
        if (empty($sourceFields)) {
266
            return [];
267
        }
268
269
        $typeField = $this->annotationReader->getTypeAnnotation($refClass);
270
271
        if ($typeField === null) {
272
            throw MissingAnnotationException::missingTypeExceptionToUseSourceField();
273
        }
274
275
        $objectClass = $typeField->getClass();
276
        $objectRefClass = new \ReflectionClass($objectClass);
277
278
        $oldDeclaringClass = null;
279
        $context = null;
280
        $queryList = [];
281
282
        foreach ($sourceFields as $sourceField) {
283
            // Ignore the field if we must be logged.
284
            if ($sourceField->isLogged() && !$this->authenticationService->isLogged()) {
285
                continue;
286
            }
287
288
            $right = $sourceField->getRight();
289
            if ($right !== null && !$this->authorizationService->isAllowed($right->getName())) {
290
                continue;
291
            }
292
293
            try {
294
                $refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getName());
295
            } catch (FieldNotFoundException $e) {
296
                throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName());
297
            }
298
299
            $docBlock = new CommentParser($refMethod->getDocComment());
0 ignored issues
show
Bug introduced by
It seems like $refMethod->getDocComment() can also be of type boolean; however, parameter $docBlock of TheCodingMachine\GraphQL...ntParser::__construct() 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

299
            $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
300
301
            $methodName = $refMethod->getName();
302
303
304
            // context is changing based on the class the method is declared in.
305
            // we assume methods will be returned packed by classes so we do this little cache
306
            if ($oldDeclaringClass !== $refMethod->getDeclaringClass()->getName()) {
307
                $context = $contextFactory->createFromReflector($refMethod);
308
                $oldDeclaringClass = $refMethod->getDeclaringClass()->getName();
309
            }
310
311
            $docComment = $refMethod->getDocComment() ?: '/** */';
312
            $docBlockObj = $docBlockFactory->create($docComment, $context);
0 ignored issues
show
Bug introduced by
It seems like $docComment can also be of type true; however, parameter $docblock of phpDocumentor\Reflection\DocBlockFactory::create() does only seem to accept object|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

312
            $docBlockObj = $docBlockFactory->create(/** @scrutinizer ignore-type */ $docComment, $context);
Loading history...
313
314
            $args = $this->mapParameters($refMethod->getParameters(), $docBlockObj);
315
316
            if ($sourceField->isId()) {
317
                $type = GraphQLType::id();
318
                if (!$refMethod->getReturnType()->allowsNull()) {
319
                    $type = GraphQLType::nonNull($type);
320
                }
321
            } elseif ($sourceField->getReturnType()) {
322
                $type = $this->registry->get($sourceField->getReturnType());
323
            } else {
324
                $type = $this->mapReturnType($refMethod, $docBlockObj);
325
            }
326
327
            $queryList[] = new QueryField($sourceField->getName(), $type, $args, null, $methodName, $this->hydrator, $docBlock->getComment(), false);
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type GraphQL\Type\Definition\Type; however, parameter $type of TheCodingMachine\GraphQL...eryField::__construct() does only seem to accept GraphQL\Type\Definition\OutputType, 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

327
            $queryList[] = new QueryField($sourceField->getName(), /** @scrutinizer ignore-type */ $type, $args, null, $methodName, $this->hydrator, $docBlock->getComment(), false);
Loading history...
328
329
        }
330
        return $queryList;
331
    }
332
333
    private function getMethodFromPropertyName(\ReflectionClass $reflectionClass, string $propertyName): \ReflectionMethod
334
    {
335
        if ($reflectionClass->hasMethod($propertyName)) {
336
            $methodName = $propertyName;
337
        } else {
338
            $upperCasePropertyName = \ucfirst($propertyName);
339
            if ($reflectionClass->hasMethod('get'.$upperCasePropertyName)) {
340
                $methodName = 'get'.$upperCasePropertyName;
341
            } elseif ($reflectionClass->hasMethod('is'.$upperCasePropertyName)) {
342
                $methodName = 'is'.$upperCasePropertyName;
343
            } else {
344
                throw FieldNotFoundException::missingField($reflectionClass->getName(), $propertyName);
345
            }
346
        }
347
348
        return $reflectionClass->getMethod($methodName);
349
    }
350
351
    /**
352
     * Checks the @Logged and @Right annotations.
353
     *
354
     * @param \ReflectionMethod $reflectionMethod
355
     * @return bool
356
     */
357
    private function isAuthorized(\ReflectionMethod $reflectionMethod) : bool
358
    {
359
        $loggedAnnotation = $this->annotationReader->getLoggedAnnotation($reflectionMethod);
360
361
        if ($loggedAnnotation !== null && !$this->authenticationService->isLogged()) {
362
            return false;
363
        }
364
365
366
        $rightAnnotation = $this->annotationReader->getRightAnnotation($reflectionMethod);
367
368
        if ($rightAnnotation !== null && !$this->authorizationService->isAllowed($rightAnnotation->getName())) {
369
            return false;
370
        }
371
372
        return true;
373
    }
374
375
    /**
376
     * Note: there is a bug in $refMethod->allowsNull that forces us to use $standardRefMethod->allowsNull instead.
377
     *
378
     * @param \ReflectionParameter[] $refParameters
379
     * @return array[] An array of ['type'=>Type, 'default'=>val]
380
     * @throws MissingTypeHintException
381
     */
382
    private function mapParameters(array $refParameters, DocBlock $docBlock): array
383
    {
384
        $args = [];
385
386
        $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
387
388
        foreach ($refParameters as $parameter) {
389
            $parameterType = $parameter->getType();
390
            $allowsNull = $parameterType === null ? true : $parameterType->allowsNull();
391
392
            $type = (string) $parameterType;
393
            if ($type === '') {
394
                throw MissingTypeHintException::missingTypeHint($parameter);
395
            }
396
            $phpdocType = $typeResolver->resolve($type);
397
398
            /** @var DocBlock\Tags\Param[] $paramTags */
399
            $paramTags = $docBlock->getTagsByName('param');
400
            $docBlockType = null;
401
            foreach ($paramTags as $paramTag) {
402
                if ($paramTag->getVariableName() === $parameter->getName()) {
403
                    $docBlockType = $paramTag->getType();
404
                    break;
405
                }
406
            }
407
408
            try {
409
                $arr = [
410
                    'type' => $this->mapType($phpdocType, $docBlockType, $allowsNull || $parameter->isDefaultValueAvailable(), true),
411
                ];
412
            } catch (TypeMappingException $e) {
413
                throw TypeMappingException::wrapWithParamInfo($e, $parameter);
414
            } catch (CannotMapTypeException $e) {
415
                throw CannotMapTypeException::wrapWithParamInfo($e, $parameter);
416
            }
417
418
            if ($parameter->allowsNull()) {
419
                $arr['default'] = null;
420
            }
421
            if ($parameter->isDefaultValueAvailable()) {
422
                $arr['default'] = $parameter->getDefaultValue();
423
            }
424
425
            $args[$parameter->getName()] = $arr;
426
        }
427
428
        return $args;
429
    }
430
431
    /**
432
     * @param Type $type
433
     * @param Type|null $docBlockType
434
     * @return GraphQLType
435
     */
436
    private function mapType(Type $type, ?Type $docBlockType, bool $isNullable, bool $mapToInputType): GraphQLType
437
    {
438
        $graphQlType = null;
439
440
        if ($type instanceof Array_ || $type instanceof Iterable_ || $type instanceof Mixed_) {
441
            $graphQlType = $this->mapDocBlockType($type, $docBlockType, $isNullable, $mapToInputType);
442
        } else {
443
            try {
444
                $graphQlType = $this->toGraphQlType($type, $mapToInputType);
445
                if (!$isNullable) {
446
                    $graphQlType = GraphQLType::nonNull($graphQlType);
447
                }
448
            } catch (TypeMappingException | CannotMapTypeException $e) {
449
                // Is the type iterable? If yes, let's analyze the docblock
450
                // TODO: it would be better not to go through an exception for this.
451
                if ($type instanceof Object_) {
452
                    $fqcn = (string) $type->getFqsen();
453
                    $refClass = new ReflectionClass($fqcn);
454
                    // Note : $refClass->isIterable() is only accessible in PHP 7.2
455
                    if ($refClass->implementsInterface(Iterator::class) || $refClass->implementsInterface(IteratorAggregate::class)) {
456
                        $graphQlType = $this->mapDocBlockType($type, $docBlockType, $isNullable, $mapToInputType);
457
                    } else {
458
                        throw $e;
459
                    }
460
                } else {
461
                    throw $e;
462
                }
463
            }
464
        }
465
466
467
        return $graphQlType;
468
    }
469
470
    private function mapDocBlockType(Type $type, ?Type $docBlockType, bool $isNullable, bool $mapToInputType): GraphQLType
471
    {
472
        if ($docBlockType === null) {
473
            throw TypeMappingException::createFromType($type);
474
        }
475
        if (!$isNullable) {
476
            // Let's check a "null" value in the docblock
477
            $isNullable = $this->isNullable($docBlockType);
478
        }
479
480
        $filteredDocBlockTypes = $this->typesWithoutNullable($docBlockType);
481
        if (empty($filteredDocBlockTypes)) {
482
            throw TypeMappingException::createFromType($type);
483
        }
484
485
        $unionTypes = [];
486
        foreach ($filteredDocBlockTypes as $singleDocBlockType) {
487
            try {
488
                $unionTypes[] = $this->toGraphQlType($this->dropNullableType($singleDocBlockType), $mapToInputType);
489
            } catch (TypeMappingException | CannotMapTypeException $e) {
490
                // We have several types. It is ok not to be able to match one.
491
            }
492
        }
493
494
        if (empty($unionTypes)) {
495
            throw TypeMappingException::createFromType($docBlockType);
496
        }
497
498
        if (count($unionTypes) === 1) {
499
            $graphQlType = $unionTypes[0];
500
        } else {
501
            $graphQlType = new UnionType($unionTypes, $this->typeMapper);
502
        }
503
504
        /* elseif (count($filteredDocBlockTypes) === 1) {
505
            $graphQlType = $this->toGraphQlType($filteredDocBlockTypes[0], $mapToInputType);
506
        } else {
507
            throw new GraphQLException('Union types are not supported (yet)');
508
            //$graphQlTypes = array_map([$this, 'toGraphQlType'], $filteredDocBlockTypes);
509
            //$$graphQlType = new UnionType($graphQlTypes);
510
        }*/
511
512
        if (!$isNullable) {
513
            $graphQlType = GraphQLType::nonNull($graphQlType);
514
        }
515
        return $graphQlType;
516
    }
517
518
    /**
519
     * Casts a Type to a GraphQL type.
520
     * Does not deal with nullable.
521
     *
522
     * @param Type $type
523
     * @param bool $mapToInputType
524
     * @return (InputType&GraphQLType)|(OutputType&GraphQLType)
525
     */
526
    private function toGraphQlType(Type $type, bool $mapToInputType): GraphQLType
527
    {
528
        if ($type instanceof Integer) {
529
            return GraphQLType::int();
0 ignored issues
show
Bug Best Practice introduced by
The expression return GraphQL\Type\Definition\Type::int() returns the type GraphQL\Type\Definition\IntType which is incompatible with the documented return type GraphQL\Type\Definition\...QL\Type\Definition\Type.
Loading history...
530
        } elseif ($type instanceof String_) {
531
            return GraphQLType::string();
0 ignored issues
show
Bug Best Practice introduced by
The expression return GraphQL\Type\Definition\Type::string() returns the type GraphQL\Type\Definition\StringType which is incompatible with the documented return type GraphQL\Type\Definition\...QL\Type\Definition\Type.
Loading history...
532
        } elseif ($type instanceof Boolean) {
533
            return GraphQLType::boolean();
0 ignored issues
show
Bug Best Practice introduced by
The expression return GraphQL\Type\Definition\Type::boolean() returns the type GraphQL\Type\Definition\BooleanType which is incompatible with the documented return type GraphQL\Type\Definition\...QL\Type\Definition\Type.
Loading history...
534
        } elseif ($type instanceof Float_) {
535
            return GraphQLType::float();
0 ignored issues
show
Bug Best Practice introduced by
The expression return GraphQL\Type\Definition\Type::float() returns the type GraphQL\Type\Definition\FloatType which is incompatible with the documented return type GraphQL\Type\Definition\...QL\Type\Definition\Type.
Loading history...
536
        } elseif ($type instanceof Object_) {
537
            $fqcn = (string) $type->getFqsen();
538
            if ($fqcn === '\\DateTimeImmutable' || $fqcn === '\\DateTimeInterface') {
539
                return DateTimeType::getInstance();
0 ignored issues
show
Bug Best Practice introduced by
The expression return TheCodingMachine\...TimeType::getInstance() returns the type TheCodingMachine\GraphQL...lers\Types\DateTimeType which is incompatible with the documented return type GraphQL\Type\Definition\...QL\Type\Definition\Type.
Loading history...
540
            } elseif ($fqcn === '\\DateTime') {
541
                throw new GraphQLException('Type-hinting a parameter against DateTime is not allowed. Please use the DateTimeImmutable type instead.');
542
            }
543
544
            $className = ltrim($type->getFqsen(), '\\');
545
            if ($mapToInputType) {
546
                return $this->typeMapper->mapClassToInputType($className);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->typeMapper...ToInputType($className) returns the type GraphQL\Type\Definition\InputType which is incompatible with the type-hinted return GraphQL\Type\Definition\Type.
Loading history...
547
            } else {
548
                return $this->typeMapper->mapClassToInterfaceOrType($className);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->typeMapper...rfaceOrType($className) returns the type GraphQL\Type\Definition\OutputType which is incompatible with the type-hinted return GraphQL\Type\Definition\Type.
Loading history...
549
            }
550
        } elseif ($type instanceof Array_) {
551
            return GraphQLType::listOf(GraphQLType::nonNull($this->toGraphQlType($type->getValueType(), $mapToInputType)));
0 ignored issues
show
Bug Best Practice introduced by
The expression return GraphQL\Type\Defi...e(), $mapToInputType))) returns the type GraphQL\Type\Definition\ListOfType which is incompatible with the documented return type GraphQL\Type\Definition\...QL\Type\Definition\Type.
Loading history...
552
        } else {
553
            throw TypeMappingException::createFromType($type);
554
        }
555
    }
556
557
    /**
558
     * Removes "null" from the type (if it is compound). Return an array of types (not a Compound type).
559
     *
560
     * @param Type $docBlockTypeHint
561
     * @return array
562
     */
563
    private function typesWithoutNullable(Type $docBlockTypeHint): array
564
    {
565
        if ($docBlockTypeHint instanceof Compound) {
566
            $docBlockTypeHints = \iterator_to_array($docBlockTypeHint);
567
        } else {
568
            $docBlockTypeHints = [$docBlockTypeHint];
569
        }
570
        return array_filter($docBlockTypeHints, function ($item) {
571
            return !$item instanceof Null_;
572
        });
573
    }
574
575
    /**
576
     * Drops "Nullable" types and return the core type.
577
     *
578
     * @param Type $typeHint
579
     * @return Type
580
     */
581
    private function dropNullableType(Type $typeHint): Type
582
    {
583
        if ($typeHint instanceof Nullable) {
584
            return $typeHint->getActualType();
585
        }
586
        return $typeHint;
587
    }
588
589
    /**
590
     * @param Type $docBlockTypeHint
591
     * @return bool
592
     */
593
    private function isNullable(Type $docBlockTypeHint): bool
594
    {
595
        if ($docBlockTypeHint instanceof Null_) {
596
            return true;
597
        }
598
        if ($docBlockTypeHint instanceof Compound) {
599
            foreach ($docBlockTypeHint as $type) {
600
                if ($type instanceof Null_) {
601
                    return true;
602
                }
603
            }
604
        }
605
606
        return false;
607
    }
608
}
609