GlobTypeMapper   F
last analyzed

Complexity

Total Complexity 93

Size/Duplication

Total Lines 759
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 93
eloc 301
dl 0
loc 759
rs 2
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getMapInputNameToFactory() 0 3 1
A __construct() 0 13 1
A getMapNameToType() 0 3 1
B getMaps() 0 32 6
A getMapNameToExtendType() 0 3 1
A getMapClassToFactory() 0 3 1
A getExtendMaps() 0 22 4
A getMapClassToType() 0 3 1
A getClassList() 0 18 5
A getMapClassToExtendTypeArray() 0 3 1
A storeTypeInCache() 0 16 1
A extendTypeForName() 0 18 4
A canExtendTypeForClass() 0 9 2
A getSupportedClasses() 0 3 1
A canMapClassToInputType() 0 9 2
A canMapNameToType() 0 16 4
A canMapClassToType() 0 11 2
B mapNameToType() 0 26 7
B buildMap() 0 31 7
A getTypeFromCacheByObjectClass() 0 23 4
A mapClassToType() 0 13 3
A mapClassToInputType() 0 13 3
A extendTypeForClass() 0 14 4
A getTypeFromCacheByGraphQLTypeName() 0 23 4
A buildExtendMap() 0 10 3
A getExtendTypesFromCacheByGraphQLTypeName() 0 23 4
A getExtendTypesFromCacheByObjectClass() 0 23 4
A canExtendTypeForName() 0 16 2
A storeInputTypeInCache() 0 15 1
A getFactoryFromCacheByObjectClass() 0 23 4
A storeExtendTypeInCache() 0 19 1
A getFactoryFromCacheByGraphQLInputTypeName() 0 23 4

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
4
namespace TheCodingMachine\GraphQL\Controllers\Mappers;
5
6
use function array_keys;
7
use function filemtime;
8
use GraphQL\Type\Definition\InputObjectType;
9
use GraphQL\Type\Definition\InputType;
10
use GraphQL\Type\Definition\ObjectType;
11
use GraphQL\Type\Definition\OutputType;
12
use Mouf\Composer\ClassNameMapper;
13
use Psr\Container\ContainerInterface;
14
use Psr\SimpleCache\CacheInterface;
15
use ReflectionClass;
16
use ReflectionMethod;
17
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
18
use TheCodingMachine\GraphQL\Controllers\AnnotationReader;
19
use TheCodingMachine\GraphQL\Controllers\Annotations\ExtendType;
20
use TheCodingMachine\GraphQL\Controllers\Annotations\Factory;
21
use TheCodingMachine\GraphQL\Controllers\Annotations\Type;
22
use TheCodingMachine\GraphQL\Controllers\InputTypeGenerator;
23
use TheCodingMachine\GraphQL\Controllers\InputTypeUtils;
24
use TheCodingMachine\GraphQL\Controllers\NamingStrategy;
25
use TheCodingMachine\GraphQL\Controllers\TypeGenerator;
26
use TheCodingMachine\GraphQL\Controllers\Types\MutableObjectType;
27
28
/**
29
 * Scans all the classes in a given namespace of the main project (not the vendor directory).
30
 * Analyzes all classes and uses the @Type annotation to find the types automatically.
31
 *
32
 * Assumes that the container contains a class whose identifier is the same as the class name.
33
 */
34
final class GlobTypeMapper implements TypeMapperInterface
35
{
36
    /**
37
     * @var string
38
     */
39
    private $namespace;
40
    /**
41
     * @var AnnotationReader
42
     */
43
    private $annotationReader;
44
    /**
45
     * @var CacheInterface
46
     */
47
    private $cache;
48
    /**
49
     * @var int|null
50
     */
51
    private $globTtl;
52
    /**
53
     * @var array<string,string> Maps a domain class to the GraphQL type annotated class
54
     */
55
    private $mapClassToTypeArray = [];
56
    /**
57
     * @var array<string,array<string,string>> Maps a domain class to one or many type extenders (with the @ExtendType annotation) The array of type extenders has a key and value equals to FQCN
58
     */
59
    private $mapClassToExtendTypeArray = [];
60
    /**
61
     * @var array<string,string> Maps a GraphQL type name to the GraphQL type annotated class
62
     */
63
    private $mapNameToType = [];
64
    /**
65
     * @var array<string,array<string,string>> Maps a GraphQL type name to one or many type extenders (with the @ExtendType annotation) The array of type extenders has a key and value equals to FQCN
66
     */
67
    private $mapNameToExtendType = [];
68
    /**
69
     * @var array<string,string[]> Maps a domain class to the factory method that creates the input type in the form [classname, methodname]
70
     */
71
    private $mapClassToFactory = [];
72
    /**
73
     * @var array<string,string[]> Maps a GraphQL input type name to the factory method that creates the input type in the form [classname, methodname]
74
     */
75
    private $mapInputNameToFactory = [];
76
    /**
77
     * @var ContainerInterface
78
     */
79
    private $container;
80
    /**
81
     * @var TypeGenerator
82
     */
83
    private $typeGenerator;
84
    /**
85
     * @var int|null
86
     */
87
    private $mapTtl;
88
    /**
89
     * @var bool
90
     */
91
    private $fullMapComputed = false;
92
    /**
93
     * @var bool
94
     */
95
    private $fullExtendMapComputed = false;
96
    /**
97
     * @var NamingStrategy
98
     */
99
    private $namingStrategy;
100
    /**
101
     * @var InputTypeGenerator
102
     */
103
    private $inputTypeGenerator;
104
    /**
105
     * @var InputTypeUtils
106
     */
107
    private $inputTypeUtils;
108
    /**
109
     * The array of globbed classes.
110
     * Only instantiable classes are returned.
111
     * Key: fully qualified class name
112
     *
113
     * @var array<string,ReflectionClass>
114
     */
115
    private $classes;
116
    /**
117
     * @var bool
118
     */
119
    private $recursive;
120
121
    /**
122
     * @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
123
     */
124
    public function __construct(string $namespace, TypeGenerator $typeGenerator, InputTypeGenerator $inputTypeGenerator, InputTypeUtils $inputTypeUtils, ContainerInterface $container, AnnotationReader $annotationReader, NamingStrategy $namingStrategy, CacheInterface $cache, ?int $globTtl = 2, ?int $mapTtl = null, bool $recursive = true)
125
    {
126
        $this->namespace = $namespace;
127
        $this->typeGenerator = $typeGenerator;
128
        $this->container = $container;
129
        $this->annotationReader = $annotationReader;
130
        $this->namingStrategy = $namingStrategy;
131
        $this->cache = $cache;
132
        $this->globTtl = $globTtl;
133
        $this->mapTtl = $mapTtl;
134
        $this->inputTypeGenerator = $inputTypeGenerator;
135
        $this->inputTypeUtils = $inputTypeUtils;
136
        $this->recursive = $recursive;
137
    }
138
139
    /**
140
     * Returns an array of fully qualified class names.
141
     *
142
     * @return array<string, array<string,string>>
143
     */
144
    private function getMaps(): array
145
    {
146
        if ($this->fullMapComputed === false) {
147
            $namespace = str_replace('\\', '_', $this->namespace);
148
            $keyClassCache = 'globTypeMapper_'.$namespace;
149
            $keyNameCache = 'globTypeMapper_names_'.$namespace;
150
            $keyInputClassCache = 'globInputTypeMapper_'.$namespace;
151
            $keyInputNameCache = 'globInputTypeMapper_names_'.$namespace;
152
            $this->mapClassToTypeArray = $this->cache->get($keyClassCache);
153
            $this->mapNameToType = $this->cache->get($keyNameCache);
154
            $this->mapClassToFactory = $this->cache->get($keyInputClassCache);
155
            $this->mapInputNameToFactory = $this->cache->get($keyInputNameCache);
156
            if ($this->mapClassToTypeArray === null ||
157
                $this->mapNameToType === null ||
158
                $this->mapClassToFactory === null ||
159
                $this->mapInputNameToFactory
160
            ) {
161
                $this->buildMap();
162
                // This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
163
                // Defaults to 2 seconds.
164
                $this->cache->set($keyClassCache, $this->mapClassToTypeArray, $this->globTtl);
165
                $this->cache->set($keyNameCache, $this->mapNameToType, $this->globTtl);
166
                $this->cache->set($keyInputClassCache, $this->mapClassToFactory, $this->globTtl);
167
                $this->cache->set($keyInputNameCache, $this->mapInputNameToFactory, $this->globTtl);
168
            }
169
            $this->fullMapComputed = true;
170
        }
171
        return [
172
            'mapClassToTypeArray' => $this->mapClassToTypeArray,
173
            'mapNameToType' => $this->mapNameToType,
174
            'mapClassToFactory' => $this->mapClassToFactory,
175
            'mapInputNameToFactory' => $this->mapInputNameToFactory,
176
        ];
177
    }
178
179
    private function getMapClassToType(): array
180
    {
181
        return $this->getMaps()['mapClassToTypeArray'];
182
    }
183
184
    private function getMapNameToType(): array
185
    {
186
        return $this->getMaps()['mapNameToType'];
187
    }
188
189
    private function getMapClassToFactory(): array
190
    {
191
        return $this->getMaps()['mapClassToFactory'];
192
    }
193
194
    private function getMapInputNameToFactory(): array
195
    {
196
        return $this->getMaps()['mapInputNameToFactory'];
197
    }
198
199
    /**
200
     * Returns an array of fully qualified class names.
201
     *
202
     * @return array<string,array<string,string>>
203
     */
204
    private function getExtendMaps(RecursiveTypeMapperInterface $recursiveTypeMapper): array
205
    {
206
        if ($this->fullExtendMapComputed === false) {
207
            $namespace = str_replace('\\', '_', $this->namespace);
208
            $keyExtendClassCache = 'globTypeMapperExtend_'.$namespace;
209
            $keyExtendNameCache = 'globTypeMapperExtend_names_'.$namespace;
210
            $this->mapClassToExtendTypeArray = $this->cache->get($keyExtendClassCache);
211
            $this->mapNameToExtendType = $this->cache->get($keyExtendNameCache);
212
            if ($this->mapClassToExtendTypeArray === null ||
213
                $this->mapNameToExtendType === null
214
            ) {
215
                $this->buildExtendMap($recursiveTypeMapper);
216
                // This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
217
                // Defaults to 2 seconds.
218
                $this->cache->set($keyExtendClassCache, $this->mapClassToExtendTypeArray, $this->globTtl);
219
                $this->cache->set($keyExtendNameCache, $this->mapNameToExtendType, $this->globTtl);
220
            }
221
            $this->fullExtendMapComputed = true;
222
        }
223
        return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('mapClassTo...s->mapNameToExtendType) returns the type array<string,array<string,array<string,string>>> which is incompatible with the documented return type array<string,array<string,string>>.
Loading history...
224
            'mapClassToExtendTypeArray' => $this->mapClassToExtendTypeArray,
225
            'mapNameToExtendType' => $this->mapNameToExtendType,
226
        ];
227
    }
228
229
    private function getMapClassToExtendTypeArray(RecursiveTypeMapperInterface $recursiveTypeMapper): array
230
    {
231
        return $this->getExtendMaps($recursiveTypeMapper)['mapClassToExtendTypeArray'];
232
    }
233
234
    private function getMapNameToExtendType(RecursiveTypeMapperInterface $recursiveTypeMapper): array
235
    {
236
        return $this->getExtendMaps($recursiveTypeMapper)['mapNameToExtendType'];
237
    }
238
239
    /**
240
     * Returns the array of globbed classes.
241
     * Only instantiable classes are returned.
242
     *
243
     * @return array<string,ReflectionClass> Key: fully qualified class name
244
     */
245
    private function getClassList(): array
246
    {
247
        if ($this->classes === null) {
248
            $this->classes = [];
249
            $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->globTtl, ClassNameMapper::createFromComposerFile(null, null, true), $this->recursive);
250
            $classes = $explorer->getClasses();
251
            foreach ($classes as $className) {
252
                if (!\class_exists($className)) {
253
                    continue;
254
                }
255
                $refClass = new \ReflectionClass($className);
256
                if (!$refClass->isInstantiable()) {
257
                    continue;
258
                }
259
                $this->classes[$className] = $refClass;
260
            }
261
        }
262
        return $this->classes;
263
    }
264
265
    private function buildMap(): void
266
    {
267
        $this->mapClassToTypeArray = [];
268
        $this->mapNameToType = [];
269
        $this->mapClassToFactory = [];
270
        $this->mapInputNameToFactory = [];
271
272
        $classes = $this->getClassList();
273
        foreach ($classes as $className => $refClass) {
274
            $type = $this->annotationReader->getTypeAnnotation($refClass);
275
276
            if ($type !== null) {
277
                if (isset($this->mapClassToTypeArray[$type->getClass()])) {
278
                    /*if ($this->mapClassToTypeArray[$type->getClass()] === $className) {
279
                        // Already mapped. Let's continue
280
                        continue;
281
                    }*/
282
                    throw DuplicateMappingException::createForType($type->getClass(), $this->mapClassToTypeArray[$type->getClass()], $className);
283
                }
284
                $this->storeTypeInCache($className, $type, $refClass->getFileName());
285
            }
286
287
            foreach ($refClass->getMethods() as $method) {
288
                $factory = $this->annotationReader->getFactoryAnnotation($method);
289
                if ($factory !== null) {
290
                    [$inputName, $className] = $this->inputTypeUtils->getInputTypeNameAndClassName($method);
291
292
                    if (isset($this->mapClassToFactory[$className])) {
293
                        throw DuplicateMappingException::createForFactory($className, $this->mapClassToFactory[$className][0], $this->mapClassToFactory[$className][1], $refClass->getName(), $method->name);
294
                    }
295
                    $this->storeInputTypeInCache($method, $inputName, $className, $refClass->getFileName());
296
                }
297
            }
298
299
        }
300
    }
301
302
    private function buildExtendMap(RecursiveTypeMapperInterface $recursiveTypeMapper): void
303
    {
304
        $this->mapClassToExtendTypeArray = [];
305
        $this->mapNameToExtendType = [];
306
        $classes = $this->getClassList();
307
        foreach ($classes as $className => $refClass) {
308
            $extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
309
310
            if ($extendType !== null) {
311
                $this->storeExtendTypeInCache($className, $extendType, $refClass->getFileName(), $recursiveTypeMapper);
312
            }
313
        }
314
    }
315
316
    /**
317
     * Stores in cache the mapping TypeClass <=> Object class <=> GraphQL type name.
318
     */
319
    private function storeTypeInCache(string $typeClassName, Type $type, string $typeFileName): void
320
    {
321
        $objectClassName = $type->getClass();
322
        $this->mapClassToTypeArray[$objectClassName] = $typeClassName;
323
        $this->cache->set('globTypeMapperByClass_'.str_replace('\\', '_', $objectClassName), [
324
            'filemtime' => filemtime($typeFileName),
325
            'fileName' => $typeFileName,
326
            'typeClass' => $typeClassName
327
        ], $this->mapTtl);
328
        $typeName = $this->namingStrategy->getOutputTypeName($typeClassName, $type);
329
        $this->mapNameToType[$typeName] = $typeClassName;
330
        $this->cache->set('globTypeMapperByName_'.$typeName, [
331
            'filemtime' => filemtime($typeFileName),
332
            'fileName' => $typeFileName,
333
            'typeClass' => $typeClassName
334
        ], $this->mapTtl);
335
    }
336
337
    /**
338
     * Stores in cache the mapping between InputType name <=> Object class
339
     */
340
    private function storeInputTypeInCache(ReflectionMethod $refMethod, string $inputName, string $className, string $fileName): void
341
    {
342
        $refArray = [$refMethod->getDeclaringClass()->getName(), $refMethod->getName()];
343
        $this->mapClassToFactory[$className] = $refArray;
344
        $this->cache->set('globInputTypeMapperByClass_'.str_replace('\\', '_', $className), [
345
            'filemtime' => filemtime($fileName),
346
            'fileName' => $fileName,
347
            'factory' => $refArray
348
        ], $this->mapTtl);
349
        $this->mapInputNameToFactory[$inputName] = $refArray;
350
        $this->cache->set('globInputTypeMapperByName_'.$inputName, [
351
            'filemtime' => filemtime($fileName),
352
            'fileName' => $fileName,
353
            'factory' => $refArray
354
        ], $this->mapTtl);
355
    }
356
357
    /**
358
     * Stores in cache the mapping ExtendTypeClass <=> Object class <=> GraphQL type name.
359
     */
360
    private function storeExtendTypeInCache(string $extendTypeClassName, ExtendType $extendType, string $typeFileName, RecursiveTypeMapperInterface $recursiveTypeMapper): void
361
    {
362
        $objectClassName = $extendType->getClass();
363
        $this->mapClassToExtendTypeArray[$objectClassName][$extendTypeClassName] = $extendTypeClassName;
364
        $this->cache->set('globExtendTypeMapperByClass_'.str_replace('\\', '_', $objectClassName), [
365
            'filemtime' => filemtime($typeFileName),
366
            'fileName' => $typeFileName,
367
            'extendTypeClasses' => $this->mapClassToExtendTypeArray[$objectClassName]
368
        ], $this->mapTtl);
369
370
        $targetType = $recursiveTypeMapper->mapClassToType($extendType->getClass(), null);
371
        $typeName = $targetType->name;
372
373
        $this->mapNameToExtendType[$typeName][$extendTypeClassName] = $extendTypeClassName;
374
        $this->cache->set('globExtendTypeMapperByName_'.$typeName, [
375
            'filemtime' => filemtime($typeFileName),
376
            'fileName' => $typeFileName,
377
            'extendTypeClasses' => $this->mapClassToExtendTypeArray[$objectClassName]
378
        ], $this->mapTtl);
379
    }
380
381
    private function getTypeFromCacheByObjectClass(string $className): ?string
382
    {
383
        if (isset($this->mapClassToTypeArray[$className])) {
384
            return $this->mapClassToTypeArray[$className];
385
        }
386
387
        // Let's try from the cache
388
        $item = $this->cache->get('globTypeMapperByClass_'.str_replace('\\', '_', $className));
389
        if ($item !== null) {
390
            [
391
                'filemtime' => $filemtime,
392
                'fileName' => $typeFileName,
393
                'typeClass' => $typeClassName
394
            ] = $item;
395
396
            if ($filemtime === filemtime($typeFileName)) {
397
                $this->mapClassToTypeArray[$className] = $typeClassName;
398
                return $typeClassName;
399
            }
400
        }
401
402
        // cache miss
403
        return null;
404
    }
405
406
    private function getTypeFromCacheByGraphQLTypeName(string $graphqlTypeName): ?string
407
    {
408
        if (isset($this->mapNameToType[$graphqlTypeName])) {
409
            return $this->mapNameToType[$graphqlTypeName];
410
        }
411
412
        // Let's try from the cache
413
        $item = $this->cache->get('globTypeMapperByName_'.$graphqlTypeName);
414
        if ($item !== null) {
415
            [
416
                'filemtime' => $filemtime,
417
                'fileName' => $typeFileName,
418
                'typeClass' => $typeClassName
419
            ] = $item;
420
421
            if ($filemtime === filemtime($typeFileName)) {
422
                $this->mapNameToType[$graphqlTypeName] = $typeClassName;
423
                return $typeClassName;
424
            }
425
        }
426
427
        // cache miss
428
        return null;
429
    }
430
431
    /**
432
     * @return string[]|null A pointer to the factory [$className, $methodName] or null on cache miss
433
     */
434
    private function getFactoryFromCacheByObjectClass(string $className): ?array
435
    {
436
        if (isset($this->mapClassToFactory[$className])) {
437
            return $this->mapClassToFactory[$className];
438
        }
439
440
        // Let's try from the cache
441
        $item = $this->cache->get('globInputTypeMapperByClass_'.str_replace('\\', '_', $className));
442
        if ($item !== null) {
443
            [
444
                'filemtime' => $filemtime,
445
                'fileName' => $typeFileName,
446
                'factory' => $factory
447
            ] = $item;
448
449
            if ($filemtime === filemtime($typeFileName)) {
450
                $this->mapClassToFactory[$className] = $factory;
451
                return $factory;
452
            }
453
        }
454
455
        // cache miss
456
        return null;
457
    }
458
459
    /**
460
     * @param string $className
461
     * @return array<string,string>|null An array of classes with the @ExtendType annotation (key and value = FQCN)
462
     */
463
    private function getExtendTypesFromCacheByObjectClass(string $className): ?array
464
    {
465
        if (isset($this->mapClassToExtendTypeArray[$className])) {
466
            return $this->mapClassToExtendTypeArray[$className];
467
        }
468
469
        // Let's try from the cache
470
        $item = $this->cache->get('globExtendTypeMapperByClass_'.str_replace('\\', '_', $className));
471
        if ($item !== null) {
472
            [
473
                'filemtime' => $filemtime,
474
                'fileName' => $typeFileName,
475
                'extendTypeClasses' => $extendTypeClassNames
476
            ] = $item;
477
478
            if ($filemtime === filemtime($typeFileName)) {
479
                $this->mapClassToExtendTypeArray[$className] = $extendTypeClassNames;
480
                return $extendTypeClassNames;
481
            }
482
        }
483
484
        // cache miss
485
        return null;
486
    }
487
488
    /**
489
     * @param string $graphqlTypeName
490
     * @return array<string,string>|null An array of classes with the @ExtendType annotation (key and value = FQCN)
491
     */
492
    private function getExtendTypesFromCacheByGraphQLTypeName(string $graphqlTypeName): ?array
493
    {
494
        if (isset($this->mapNameToExtendType[$graphqlTypeName])) {
495
            return $this->mapNameToExtendType[$graphqlTypeName];
496
        }
497
498
        // Let's try from the cache
499
        $item = $this->cache->get('globExtendTypeMapperByName_'.$graphqlTypeName);
500
        if ($item !== null) {
501
            [
502
                'filemtime' => $filemtime,
503
                'fileName' => $typeFileName,
504
                'extendTypeClasses' => $extendTypeClassNames
505
            ] = $item;
506
507
            if ($filemtime === filemtime($typeFileName)) {
508
                $this->mapNameToExtendType[$graphqlTypeName] = $extendTypeClassNames;
509
                return $extendTypeClassNames;
510
            }
511
        }
512
513
        // cache miss
514
        return null;
515
    }
516
517
    /**
518
     * @return string[]|null A pointer to the factory [$className, $methodName] or null on cache miss
519
     */
520
    private function getFactoryFromCacheByGraphQLInputTypeName(string $graphqlTypeName): ?array
521
    {
522
        if (isset($this->mapInputNameToFactory[$graphqlTypeName])) {
523
            return $this->mapInputNameToFactory[$graphqlTypeName];
524
        }
525
526
        // Let's try from the cache
527
        $item = $this->cache->get('globInputTypeMapperByName_'.$graphqlTypeName);
528
        if ($item !== null) {
529
            [
530
                'filemtime' => $filemtime,
531
                'fileName' => $typeFileName,
532
                'factory' => $factory
533
            ] = $item;
534
535
            if ($filemtime === filemtime($typeFileName)) {
536
                $this->mapInputNameToFactory[$graphqlTypeName] = $factory;
537
                return $factory;
538
            }
539
        }
540
541
        // cache miss
542
        return null;
543
    }
544
545
    /**
546
     * Returns true if this type mapper can map the $className FQCN to a GraphQL type.
547
     *
548
     * @param string $className
549
     * @return bool
550
     */
551
    public function canMapClassToType(string $className): bool
552
    {
553
        $typeClassName = $this->getTypeFromCacheByObjectClass($className);
554
555
        if ($typeClassName !== null) {
556
            return true;
557
        }
558
559
        $map = $this->getMapClassToType();
560
561
        return isset($map[$className]);
562
    }
563
564
    /**
565
     * Maps a PHP fully qualified class name to a GraphQL type.
566
     *
567
     * @param string $className The exact class name to look for (this function does not look into parent classes).
568
     * @param OutputType|null $subType An optional sub-type if the main class is an iterator that needs to be typed.
569
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
570
     * @return MutableObjectType
571
     * @throws CannotMapTypeExceptionInterface
572
     */
573
    public function mapClassToType(string $className, ?OutputType $subType, RecursiveTypeMapperInterface $recursiveTypeMapper): MutableObjectType
574
    {
575
        $typeClassName = $this->getTypeFromCacheByObjectClass($className);
576
577
        if ($typeClassName === null) {
578
            $map = $this->getMapClassToType();
579
            if (!isset($map[$className])) {
580
                throw CannotMapTypeException::createForType($className);
581
            }
582
            $typeClassName = $map[$className];
583
        }
584
585
        return $this->typeGenerator->mapAnnotatedObject($typeClassName, $recursiveTypeMapper);
586
    }
587
588
    /**
589
     * Returns the list of classes that have matching input GraphQL types.
590
     *
591
     * @return string[]
592
     */
593
    public function getSupportedClasses(): array
594
    {
595
        return array_keys($this->getMapClassToType());
596
    }
597
598
    /**
599
     * Returns true if this type mapper can map the $className FQCN to a GraphQL input type.
600
     *
601
     * @param string $className
602
     * @return bool
603
     */
604
    public function canMapClassToInputType(string $className): bool
605
    {
606
        $factory = $this->getFactoryFromCacheByObjectClass($className);
607
608
        if ($factory !== null) {
609
            return true;
610
        }
611
        $map = $this->getMapClassToFactory();
612
        return isset($map[$className]);
613
    }
614
615
    /**
616
     * Maps a PHP fully qualified class name to a GraphQL input type.
617
     *
618
     * @param string $className
619
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
620
     * @return InputObjectType
621
     * @throws CannotMapTypeExceptionInterface
622
     */
623
    public function mapClassToInputType(string $className, RecursiveTypeMapperInterface $recursiveTypeMapper): InputObjectType
624
    {
625
        $factory = $this->getFactoryFromCacheByObjectClass($className);
626
627
        if ($factory === null) {
628
            $map = $this->getMapClassToFactory();
629
            if (!isset($map[$className])) {
630
                throw CannotMapTypeException::createForInputType($className);
631
            }
632
            $factory = $map[$className];
633
        }
634
635
        return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($factory[0]), $factory[1], $recursiveTypeMapper);
636
    }
637
638
    /**
639
     * Returns a GraphQL type by name (can be either an input or output type)
640
     *
641
     * @param string $typeName The name of the GraphQL type
642
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
643
     * @return \GraphQL\Type\Definition\Type&(InputType|OutputType)
644
     * @throws CannotMapTypeExceptionInterface
645
     * @throws \ReflectionException
646
     */
647
    public function mapNameToType(string $typeName, RecursiveTypeMapperInterface $recursiveTypeMapper): \GraphQL\Type\Definition\Type
648
    {
649
        $typeClassName = $this->getTypeFromCacheByGraphQLTypeName($typeName);
650
        if ($typeClassName === null) {
651
            $factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
652
            if ($factory === null) {
653
                $mapNameToType = $this->getMapNameToType();
654
                if (isset($mapNameToType[$typeName])) {
655
                    $typeClassName = $mapNameToType[$typeName];
656
                } else {
657
                    $mapInputNameToFactory = $this->getMapInputNameToFactory();
658
                    if (isset($mapInputNameToFactory[$typeName])) {
659
                        $factory = $mapInputNameToFactory[$typeName];
660
                    }
661
                }
662
            }
663
        }
664
665
        if (isset($typeClassName)) {
666
            return $this->typeGenerator->mapAnnotatedObject($typeClassName, $recursiveTypeMapper);
667
        }
668
        if (isset($factory)) {
669
            return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($factory[0]), $factory[1], $recursiveTypeMapper);
670
        }
671
672
        throw CannotMapTypeException::createForName($typeName);
673
    }
674
675
    /**
676
     * Returns true if this type mapper can map the $typeName GraphQL name to a GraphQL type.
677
     *
678
     * @param string $typeName The name of the GraphQL type
679
     * @return bool
680
     */
681
    public function canMapNameToType(string $typeName): bool
682
    {
683
        $typeClassName = $this->getTypeFromCacheByGraphQLTypeName($typeName);
684
685
        if ($typeClassName !== null) {
686
            return true;
687
        }
688
689
        $factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
690
        if ($factory !== null) {
691
            return true;
692
        }
693
694
        $this->getMaps();
695
696
        return isset($this->mapNameToType[$typeName]) || isset($this->mapInputNameToFactory[$typeName]);
697
    }
698
699
    /**
700
     * Returns true if this type mapper can extend an existing type for the $className FQCN
701
     *
702
     * @param string $className
703
     * @param MutableObjectType $type
704
     * @return bool
705
     */
706
    public function canExtendTypeForClass(string $className, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): bool
707
    {
708
        $extendTypeClassName = $this->getExtendTypesFromCacheByObjectClass($className);
709
710
        if ($extendTypeClassName === null) {
711
            $map = $this->getMapClassToExtendTypeArray($recursiveTypeMapper);
0 ignored issues
show
Unused Code introduced by
The assignment to $map is dead and can be removed.
Loading history...
712
        }
713
714
        return isset($this->mapClassToExtendTypeArray[$className]);
715
    }
716
717
    /**
718
     * Extends the existing GraphQL type that is mapped to $className.
719
     *
720
     * @param string $className
721
     * @param MutableObjectType $type
722
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
723
     * @throws CannotMapTypeExceptionInterface
724
     */
725
    public function extendTypeForClass(string $className, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): void
726
    {
727
        $extendTypeClassNames = $this->getExtendTypesFromCacheByObjectClass($className);
728
729
        if ($extendTypeClassNames === null) {
730
            $this->getExtendMaps($recursiveTypeMapper);
731
        }
732
733
        if (!isset($this->mapClassToExtendTypeArray[$className])) {
734
            throw CannotMapTypeException::createForExtendType($className, $type);
735
        }
736
737
        foreach ($this->mapClassToExtendTypeArray[$className] as $extendedTypeClass) {
738
            $this->typeGenerator->extendAnnotatedObject($this->container->get($extendedTypeClass), $type, $recursiveTypeMapper);
739
        }
740
    }
741
742
    /**
743
     * Returns true if this type mapper can extend an existing type for the $typeName GraphQL type
744
     *
745
     * @param string $typeName
746
     * @param MutableObjectType $type
747
     * @return bool
748
     */
749
    public function canExtendTypeForName(string $typeName, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): bool
750
    {
751
        $typeClassNames = $this->getExtendTypesFromCacheByGraphQLTypeName($typeName);
752
753
        if ($typeClassNames !== null) {
754
            return true;
755
        }
756
757
        /*$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
758
        if ($factory !== null) {
759
            return true;
760
        }*/
761
762
        $this->getExtendMaps($recursiveTypeMapper);
763
764
        return isset($this->mapNameToExtendType[$typeName])/* || isset($this->mapInputNameToFactory[$typeName])*/;
765
    }
766
767
    /**
768
     * Extends the existing GraphQL type that is mapped to the $typeName GraphQL type.
769
     *
770
     * @param string $typeName
771
     * @param MutableObjectType $type
772
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
773
     * @throws CannotMapTypeExceptionInterface
774
     */
775
    public function extendTypeForName(string $typeName, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): void
776
    {
777
        $extendTypeClassNames = $this->getExtendTypesFromCacheByGraphQLTypeName($typeName);
778
        if ($extendTypeClassNames === null) {
779
            /*$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
780
            if ($factory === null) {*/
781
                $map = $this->getMapNameToExtendType($recursiveTypeMapper);
782
                if (!isset($map[$typeName])) {
783
                    throw CannotMapTypeException::createForExtendName($typeName, $type);
784
                }
785
                $extendTypeClassNames = $map[$typeName];
786
787
            //}
788
        }
789
790
791
        foreach ($extendTypeClassNames as $extendedTypeClass) {
792
            $this->typeGenerator->extendAnnotatedObject($this->container->get($extendedTypeClass), $type, $recursiveTypeMapper);
793
        }
794
795
        /*if (isset($this->mapInputNameToFactory[$typeName])) {
796
            $factory = $this->mapInputNameToFactory[$typeName];
797
            return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($factory[0]), $factory[1], $recursiveTypeMapper);
798
        }*/
799
    }
800
}
801