Completed
Pull Request — master (#51)
by David
01:51
created

ControllerQueryProvider::isNullable()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

176
                $docBlockObj = $docBlockFactory->create(/** @scrutinizer ignore-type */ $docComment, $context);
Loading history...
177
178
                // TODO: change CommentParser to use $docBlockObj methods instead.
179
                $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

179
                $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
180
181
                $methodName = $refMethod->getName();
182
                $name = $queryAnnotation->getName() ?: $methodName;
183
184
                $parameters = $refMethod->getParameters();
185
                if ($injectSource === true) {
186
                    $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...
187
                    // TODO: check that $first_parameter type is correct.
188
                }
189
190
                $args = $this->mapParameters($parameters, $docBlockObj);
191
192
                if ($queryAnnotation->getReturnType()) {
193
                    $type = $this->registry->get($queryAnnotation->getReturnType());
194
                    if (!$type instanceof OutputType) {
195
                        throw new \InvalidArgumentException("The 'returnType' parameter in @Type annotation should contain a container identifier that points to an entry that implements GraphQL\\Type\\Definition\\OutputType.");
196
                    }
197
                } else {
198
                    $phpdocType = null;
199
                    $returnType = $refMethod->getReturnType();
200
                    if ($returnType !== null) {
201
                        $phpdocType = $typeResolver->resolve((string) $returnType);
202
                    } else {
203
                        $phpdocType = new Mixed_();
204
                    }
205
206
                    $docBlockReturnType = $this->getDocBlocReturnType($docBlockObj, $refMethod);
207
208
                    try {
209
                        $type = $this->mapType($phpdocType, $docBlockReturnType, $returnType ? $returnType->allowsNull() : false, false);
210
                    } catch (TypeMappingException $e) {
211
                        throw TypeMappingException::wrapWithReturnInfo($e, $refMethod);
212
                    } catch (CannotMapTypeException $e) {
213
                        throw CannotMapTypeException::wrapWithReturnInfo($e, $refMethod);
214
                    }
215
                }
216
217
                $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

217
                $queryList[] = new QueryField($name, /** @scrutinizer ignore-type */ $type, $args, [$this->controller, $methodName], null, $this->hydrator, $docBlock->getComment(), $injectSource);
Loading history...
218
            }
219
        }
220
221
        return $queryList;
222
    }
223
224
    private function getDocBlocReturnType(DocBlock $docBlock, \ReflectionMethod $refMethod): ?Type
225
    {
226
        /** @var Return_[] $returnTypeTags */
227
        $returnTypeTags = $docBlock->getTagsByName('return');
228
        if (count($returnTypeTags) > 1) {
229
            throw InvalidDocBlockException::tooManyReturnTags($refMethod);
230
        }
231
        $docBlockReturnType = null;
232
        if (isset($returnTypeTags[0])) {
233
            $docBlockReturnType = $returnTypeTags[0]->getType();
234
        }
235
        return $docBlockReturnType;
236
    }
237
238
    /**
239
     * @return QueryField[]
240
     */
241
    private function getSourceFields(): array
242
    {
243
        $refClass = new \ReflectionClass($this->controller);
244
        $docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
245
        $contextFactory = new ContextFactory();
246
247
        /** @var SourceField[] $sourceFields */
248
        $sourceFields = $this->annotationReader->getSourceFields($refClass);
249
250
        if ($this->controller instanceof FromSourceFieldsInterface) {
251
            $sourceFields = array_merge($sourceFields, $this->controller->getSourceFields());
252
        }
253
254
        if (empty($sourceFields)) {
255
            return [];
256
        }
257
258
        $typeField = $this->annotationReader->getTypeAnnotation($refClass);
259
260
        if ($typeField === null) {
261
            throw MissingAnnotationException::missingTypeExceptionToUseSourceField();
262
        }
263
264
        $objectClass = $typeField->getClass();
265
        $objectRefClass = new \ReflectionClass($objectClass);
266
267
        $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
268
269
        $oldDeclaringClass = null;
270
        $context = null;
271
        $queryList = [];
272
273
        foreach ($sourceFields as $sourceField) {
274
            // Ignore the field if we must be logged.
275
            if ($sourceField->isLogged() && !$this->authenticationService->isLogged()) {
276
                continue;
277
            }
278
279
            $right = $sourceField->getRight();
280
            if ($right !== null && !$this->authorizationService->isAllowed($right->getName())) {
281
                continue;
282
            }
283
284
            try {
285
                $refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getName());
286
            } catch (FieldNotFoundException $e) {
287
                throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName());
288
            }
289
290
            $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

290
            $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
291
292
            $methodName = $refMethod->getName();
293
294
295
            // context is changing based on the class the method is declared in.
296
            // we assume methods will be returned packed by classes so we do this little cache
297
            if ($oldDeclaringClass !== $refMethod->getDeclaringClass()->getName()) {
298
                $context = $contextFactory->createFromReflector($refMethod);
299
                $oldDeclaringClass = $refMethod->getDeclaringClass()->getName();
300
            }
301
302
            $docComment = $refMethod->getDocComment() ?: '/** */';
303
            $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

303
            $docBlockObj = $docBlockFactory->create(/** @scrutinizer ignore-type */ $docComment, $context);
Loading history...
304
305
            $args = $this->mapParameters($refMethod->getParameters(), $docBlockObj);
306
307
            if ($sourceField->isId()) {
308
                $type = GraphQLType::id();
309
                if (!$refMethod->getReturnType()->allowsNull()) {
310
                    $type = GraphQLType::nonNull($type);
311
                }
312
            } elseif ($sourceField->getReturnType()) {
313
                $type = $this->registry->get($sourceField->getReturnType());
314
            } else {
315
                $returnType = $refMethod->getReturnType();
316
                if ($returnType !== null) {
317
                    $phpdocType = $typeResolver->resolve((string) $returnType);
318
                } else {
319
                    $phpdocType = new Mixed_();
320
                }
321
322
                $docBlockReturnType = $this->getDocBlocReturnType($docBlockObj, $refMethod);
323
324
                try {
325
                    $type = $this->mapType($phpdocType, $docBlockReturnType, $returnType ? $returnType->allowsNull() : false, false);
326
                } catch (TypeMappingException $e) {
327
                    throw TypeMappingException::wrapWithReturnInfo($e, $refMethod);
328
                }
329
            }
330
331
            $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

331
            $queryList[] = new QueryField($sourceField->getName(), /** @scrutinizer ignore-type */ $type, $args, null, $methodName, $this->hydrator, $docBlock->getComment(), false);
Loading history...
332
333
        }
334
        return $queryList;
335
    }
336
337
    private function getMethodFromPropertyName(\ReflectionClass $reflectionClass, string $propertyName): \ReflectionMethod
338
    {
339
        $upperCasePropertyName = \ucfirst($propertyName);
340
        if ($reflectionClass->hasMethod('get'.$upperCasePropertyName)) {
341
            $methodName = 'get'.$upperCasePropertyName;
342
        } elseif ($reflectionClass->hasMethod('is'.$upperCasePropertyName)) {
343
            $methodName = 'is'.$upperCasePropertyName;
344
        } else {
345
            throw FieldNotFoundException::missingField($reflectionClass->getName(), $propertyName);
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($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
     * @param Type $docBlockTypeHint
577
     * @return bool
578
     */
579
    private function isNullable(Type $docBlockTypeHint): bool
580
    {
581
        if ($docBlockTypeHint instanceof Null_) {
582
            return true;
583
        }
584
        if ($docBlockTypeHint instanceof Compound) {
585
            foreach ($docBlockTypeHint as $type) {
586
                if ($type instanceof Null_) {
587
                    return true;
588
                }
589
            }
590
        }
591
592
        return false;
593
    }
594
}
595