Completed
Push — master ( ce3fe8...382658 )
by David
10s
created

ControllerQueryProvider::typesWithoutNullable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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

161
                $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
162
163
                $methodName = $refMethod->getName();
164
                $name = $queryAnnotation->getName() ?: $methodName;
165
166
                $args = $this->mapParameters($refMethod, $docBlockObj);
167
168
                $phpdocType = $typeResolver->resolve((string) $refMethod->getReturnType());
169
170
                if ($queryAnnotation->getReturnType()) {
171
                    $type = $this->registry->get($queryAnnotation->getReturnType());
172
                } else {
173
                    $docBlockReturnType = $this->getDocBlocReturnType($docBlockObj, $refMethod);
174
175
                    try {
176
                        $type = $this->mapType($phpdocType, $docBlockReturnType, $refMethod->getReturnType()->allowsNull(), false);
177
                    } catch (TypeMappingException $e) {
178
                        throw TypeMappingException::wrapWithReturnInfo($e, $refMethod);
179
                    }
180
                }
181
182
                //$sourceType = null;
183
                if ($injectSource === true) {
184
                    /*$sourceArr = */\array_shift($args);
185
                    // Security check: if the first parameter of the correct type?
186
                    //$sourceType = $sourceArr['type'];
187
                    /* @var $sourceType TypeInterface */
188
                    // TODO
189
                }
190
                $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

190
                $queryList[] = new QueryField($name, /** @scrutinizer ignore-type */ $type, $args, [$this->controller, $methodName], null, $this->hydrator, $docBlock->getComment(), $injectSource);
Loading history...
191
            }
192
        }
193
194
        return $queryList;
195
    }
196
197
    private function getDocBlocReturnType(DocBlock $docBlock, \ReflectionMethod $refMethod): ?Type
198
    {
199
        /** @var Return_[] $returnTypeTags */
200
        $returnTypeTags = $docBlock->getTagsByName('return');
201
        if (count($returnTypeTags) > 1) {
202
            throw InvalidDocBlockException::tooManyReturnTags($refMethod);
203
        }
204
        $docBlockReturnType = null;
205
        if (isset($returnTypeTags[0])) {
206
            $docBlockReturnType = $returnTypeTags[0]->getType();
207
        }
208
        return $docBlockReturnType;
209
    }
210
211
    /**
212
     * @return QueryField[]
213
     */
214
    private function getSourceFields(): array
215
    {
216
        $refClass = new \ReflectionClass($this->controller);
217
        $docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
218
        $contextFactory = new ContextFactory();
219
220
        /** @var SourceField[] $sourceFields */
221
        $sourceFields = AnnotationUtils::getClassAnnotations($this->annotationReader, $refClass);
222
        $sourceFields = \array_filter($sourceFields, function($annotation): bool {
223
            return $annotation instanceof SourceField;
224
        });
225
226
        if ($this->controller instanceof FromSourceFieldsInterface) {
227
            $sourceFields = array_merge($sourceFields, $this->controller->getSourceFields());
228
        }
229
230
        if (empty($sourceFields)) {
231
            return [];
232
        }
233
234
        /** @var \TheCodingMachine\GraphQL\Controllers\Annotations\Type|null $typeField */
235
        $typeField = AnnotationUtils::getClassAnnotation($this->annotationReader, $refClass, \TheCodingMachine\GraphQL\Controllers\Annotations\Type::class);
236
237
        if ($typeField === null) {
238
            throw MissingAnnotationException::missingTypeExceptionToUseSourceField();
239
        }
240
241
        $objectClass = $typeField->getClass();
242
        $objectRefClass = new \ReflectionClass($objectClass);
243
244
        $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
245
246
        $oldDeclaringClass = null;
247
        $context = null;
248
        $queryList = [];
249
250
        foreach ($sourceFields as $sourceField) {
251
            // Ignore the field if we must be logged.
252
            if ($sourceField->isLogged() && !$this->authenticationService->isLogged()) {
253
                continue;
254
            }
255
256
            $right = $sourceField->getRight();
257
            if ($right !== null && !$this->authorizationService->isAllowed($right->getName())) {
258
                continue;
259
            }
260
261
            try {
262
                $refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getName());
263
            } catch (FieldNotFoundException $e) {
264
                throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName());
265
            }
266
267
            $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

267
            $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
268
269
            $methodName = $refMethod->getName();
270
271
272
            // context is changing based on the class the method is declared in.
273
            // we assume methods will be returned packed by classes so we do this little cache
274
            if ($oldDeclaringClass !== $refMethod->getDeclaringClass()->getName()) {
275
                $context = $contextFactory->createFromReflector($refMethod);
276
                $oldDeclaringClass = $refMethod->getDeclaringClass()->getName();
277
            }
278
279
            $docComment = $refMethod->getDocComment() ?: '/** */';
280
            $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

280
            $docBlockObj = $docBlockFactory->create(/** @scrutinizer ignore-type */ $docComment, $context);
Loading history...
281
282
            $args = $this->mapParameters($refMethod, $docBlockObj);
283
284
            $phpdocType = $typeResolver->resolve((string) $refMethod->getReturnType());
285
286
            if ($sourceField->getReturnType()) {
287
                $type = $this->registry->get($sourceField->getReturnType());
288
            } else {
289
                $docBlockReturnType = $this->getDocBlocReturnType($docBlockObj, $refMethod);
290
291
                try {
292
                    $type = $this->mapType($phpdocType, $docBlockReturnType, $refMethod->getReturnType()->allowsNull(), false);
293
                } catch (TypeMappingException $e) {
294
                    throw TypeMappingException::wrapWithReturnInfo($e, $refMethod);
295
                }
296
            }
297
298
            $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

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