Completed
Push — master ( 4cae74...e1abd3 )
by David
03:07 queued 42s
created

ControllerQueryProvider::getMutations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
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 ReflectionMethod;
13
use TheCodingMachine\GraphQL\Controllers\Annotations\Exceptions\ClassNotFoundException;
14
use TheCodingMachine\GraphQL\Controllers\Mappers\RecursiveTypeMapper;
15
use TheCodingMachine\GraphQL\Controllers\Mappers\TypeMapperInterface;
16
use TheCodingMachine\GraphQL\Controllers\Types\UnionType;
17
use function is_object;
18
use Iterator;
19
use IteratorAggregate;
20
use phpDocumentor\Reflection\DocBlock;
21
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
22
use phpDocumentor\Reflection\Type;
23
use phpDocumentor\Reflection\Types\Array_;
24
use phpDocumentor\Reflection\Types\Boolean;
25
use phpDocumentor\Reflection\Types\Compound;
26
use phpDocumentor\Reflection\Types\ContextFactory;
27
use phpDocumentor\Reflection\Types\Float_;
28
use phpDocumentor\Reflection\Types\Iterable_;
29
use phpDocumentor\Reflection\Types\Mixed_;
30
use phpDocumentor\Reflection\Types\Null_;
31
use phpDocumentor\Reflection\Types\Object_;
32
use phpDocumentor\Reflection\Types\String_;
33
use phpDocumentor\Reflection\Types\Integer;
34
use ReflectionClass;
35
use TheCodingMachine\GraphQL\Controllers\Annotations\SourceField;
36
use TheCodingMachine\GraphQL\Controllers\Annotations\Logged;
37
use TheCodingMachine\GraphQL\Controllers\Annotations\Mutation;
38
use TheCodingMachine\GraphQL\Controllers\Annotations\Query;
39
use TheCodingMachine\GraphQL\Controllers\Mappers\CannotMapTypeException;
40
use TheCodingMachine\GraphQL\Controllers\Mappers\RecursiveTypeMapperInterface;
41
use TheCodingMachine\GraphQL\Controllers\Reflection\CommentParser;
42
use TheCodingMachine\GraphQL\Controllers\Security\AuthenticationServiceInterface;
43
use TheCodingMachine\GraphQL\Controllers\Security\AuthorizationServiceInterface;
44
use TheCodingMachine\GraphQL\Controllers\Types\DateTimeType;
45
use GraphQL\Type\Definition\Type as GraphQLType;
46
47
/**
48
 * A query provider that looks for queries in a "controller"
49
 */
50
class ControllerQueryProvider implements QueryProviderInterface
51
{
52
    /**
53
     * @var object
54
     */
55
    private $controller;
56
    /**
57
     * @var AnnotationReader
58
     */
59
    private $annotationReader;
60
    /**
61
     * @var RecursiveTypeMapperInterface
62
     */
63
    private $typeMapper;
64
    /**
65
     * @var HydratorInterface
66
     */
67
    private $hydrator;
68
    /**
69
     * @var AuthenticationServiceInterface
70
     */
71
    private $authenticationService;
72
    /**
73
     * @var AuthorizationServiceInterface
74
     */
75
    private $authorizationService;
76
    /**
77
     * @var ContainerInterface
78
     */
79
    private $registry;
80
81
    /**
82
     * @param object $controller
83
     * @param AnnotationReader $annotationReader
84
     * @param RecursiveTypeMapperInterface $typeMapper
85
     * @param HydratorInterface $hydrator
86
     * @param AuthenticationServiceInterface $authenticationService
87
     * @param AuthorizationServiceInterface $authorizationService
88
     * @param ContainerInterface $registry The registry is used to fetch custom return types by container identifier (using the returnType parameter of the Type annotation)
89
     */
90
    public function __construct($controller, AnnotationReader $annotationReader, RecursiveTypeMapperInterface $typeMapper,
91
                                HydratorInterface $hydrator, AuthenticationServiceInterface $authenticationService,
92
                                AuthorizationServiceInterface $authorizationService, ContainerInterface $registry)
93
    {
94
        $this->controller = $controller;
95
        $this->annotationReader = $annotationReader;
96
        $this->typeMapper = $typeMapper;
97
        $this->hydrator = $hydrator;
98
        $this->authenticationService = $authenticationService;
99
        $this->authorizationService = $authorizationService;
100
        $this->registry = $registry;
101
    }
102
103
    /**
104
     * @return QueryField[]
105
     */
106
    public function getQueries(): array
107
    {
108
        return $this->getFieldsByAnnotations(Query::class, false);
109
    }
110
111
    /**
112
     * @return QueryField[]
113
     */
114
    public function getMutations(): array
115
    {
116
        return $this->getFieldsByAnnotations(Mutation::class, false);
117
    }
118
119
    /**
120
     * @return array<string, QueryField> QueryField indexed by name.
121
     */
122
    public function getFields(): array
123
    {
124
        $fieldAnnotations = $this->getFieldsByAnnotations(Annotations\Field::class, true);
125
        $sourceFields = $this->getSourceFields();
126
127
        $fields = [];
128
        foreach ($fieldAnnotations as $field) {
129
            $fields[$field->name] = $field;
130
        }
131
        foreach ($sourceFields as $field) {
132
            $fields[$field->name] = $field;
133
        }
134
135
        return $fields;
136
    }
137
138
    /**
139
     * @param string $annotationName
140
     * @param bool $injectSource Whether to inject the source object or not as the first argument. True for @Field, false for @Query and @Mutation
141
     * @return QueryField[]
142
     * @throws \ReflectionException
143
     */
144
    private function getFieldsByAnnotations(string $annotationName, bool $injectSource): array
145
    {
146
        $refClass = new \ReflectionClass($this->controller);
147
        $docBlockFactory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
148
        $contextFactory = new ContextFactory();
149
150
        //$refClass = ReflectionClass::createFromInstance($this->controller);
151
152
        $queryList = [];
153
154
        $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
0 ignored issues
show
Unused Code introduced by
The assignment to $typeResolver is dead and can be removed.
Loading history...
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("The 'returnType' parameter in @Type annotation should contain a container identifier that points to an entry that implements GraphQL\\Type\\Definition\\OutputType.");
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
        $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
0 ignored issues
show
Unused Code introduced by
The assignment to $typeResolver is dead and can be removed.
Loading history...
279
280
        $oldDeclaringClass = null;
281
        $context = null;
282
        $queryList = [];
283
284
        foreach ($sourceFields as $sourceField) {
285
            // Ignore the field if we must be logged.
286
            if ($sourceField->isLogged() && !$this->authenticationService->isLogged()) {
287
                continue;
288
            }
289
290
            $right = $sourceField->getRight();
291
            if ($right !== null && !$this->authorizationService->isAllowed($right->getName())) {
292
                continue;
293
            }
294
295
            try {
296
                $refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getName());
297
            } catch (FieldNotFoundException $e) {
298
                throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName());
299
            }
300
301
            $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

301
            $docBlock = new CommentParser(/** @scrutinizer ignore-type */ $refMethod->getDocComment());
Loading history...
302
303
            $methodName = $refMethod->getName();
304
305
306
            // context is changing based on the class the method is declared in.
307
            // we assume methods will be returned packed by classes so we do this little cache
308
            if ($oldDeclaringClass !== $refMethod->getDeclaringClass()->getName()) {
309
                $context = $contextFactory->createFromReflector($refMethod);
310
                $oldDeclaringClass = $refMethod->getDeclaringClass()->getName();
311
            }
312
313
            $docComment = $refMethod->getDocComment() ?: '/** */';
314
            $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

314
            $docBlockObj = $docBlockFactory->create(/** @scrutinizer ignore-type */ $docComment, $context);
Loading history...
315
316
            $args = $this->mapParameters($refMethod->getParameters(), $docBlockObj);
317
318
            if ($sourceField->isId()) {
319
                $type = GraphQLType::id();
320
                if (!$refMethod->getReturnType()->allowsNull()) {
321
                    $type = GraphQLType::nonNull($type);
322
                }
323
            } elseif ($sourceField->getReturnType()) {
324
                $type = $this->registry->get($sourceField->getReturnType());
325
            } else {
326
                $type = $this->mapReturnType($refMethod, $docBlockObj);
327
            }
328
329
            $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

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