Passed
Pull Request — master (#78)
by David
01:56
created

GlobTypeMapper   F

Complexity

Total Complexity 91

Size/Duplication

Total Lines 761
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 91
eloc 285
dl 0
loc 761
rs 2
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getMapInputNameToFactory() 0 3 1
A storeTypeInCache() 0 16 1
A __construct() 0 12 1
A extendTypeForName() 0 22 4
A canExtendTypeForClass() 0 9 2
A getMapNameToType() 0 3 1
A getSupportedClasses() 0 3 1
B getMaps() 0 32 6
A canMapClassToInputType() 0 8 2
A canMapNameToType() 0 16 4
A canMapClassToType() 0 9 2
A getMapNameToExtendType() 0 3 1
A mapNameToType() 0 19 5
A getMapClassToFactory() 0 3 1
B buildMap() 0 26 7
A getTypeFromCacheByObjectClass() 0 23 4
A mapClassToType() 0 12 3
A mapClassToInputType() 0 12 3
A extendTypeForClass() 0 16 4
A getTypeFromCacheByGraphQLTypeName() 0 23 4
A buildExtendMap() 0 8 3
A getExtendTypesFromCacheByGraphQLTypeName() 0 23 4
A getExtendMaps() 0 22 4
A getExtendTypesFromCacheByObjectClass() 0 23 4
A getMapClassToType() 0 3 1
A canExtendTypeForName() 0 16 2
A storeInputTypeInCache() 0 15 1
A getFactoryFromCacheByObjectClass() 0 23 4
A getClassList() 0 18 5
A getMapClassToExtendTypeArray() 0 3 1
A storeExtendTypeInCache() 0 38 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
27
/**
28
 * Scans all the classes in a given namespace of the main project (not the vendor directory).
29
 * Analyzes all classes and uses the @Type annotation to find the types automatically.
30
 *
31
 * Assumes that the container contains a class whose identifier is the same as the class name.
32
 */
33
final class GlobTypeMapper implements TypeMapperInterface
34
{
35
    /**
36
     * @var string
37
     */
38
    private $namespace;
39
    /**
40
     * @var AnnotationReader
41
     */
42
    private $annotationReader;
43
    /**
44
     * @var CacheInterface
45
     */
46
    private $cache;
47
    /**
48
     * @var int|null
49
     */
50
    private $globTtl;
51
    /**
52
     * @var array<string,string> Maps a domain class to the GraphQL type annotated class
53
     */
54
    private $mapClassToTypeArray = [];
55
    /**
56
     * @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
57
     */
58
    private $mapClassToExtendTypeArray = [];
59
    /**
60
     * @var array<string,string> Maps a GraphQL type name to the GraphQL type annotated class
61
     */
62
    private $mapNameToType = [];
63
    /**
64
     * @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
65
     */
66
    private $mapNameToExtendType = [];
67
    /**
68
     * @var array<string,string[]> Maps a domain class to the factory method that creates the input type in the form [classname, methodname]
69
     */
70
    private $mapClassToFactory = [];
71
    /**
72
     * @var array<string,string[]> Maps a GraphQL input type name to the factory method that creates the input type in the form [classname, methodname]
73
     */
74
    private $mapInputNameToFactory = [];
75
    /**
76
     * @var ContainerInterface
77
     */
78
    private $container;
79
    /**
80
     * @var TypeGenerator
81
     */
82
    private $typeGenerator;
83
    /**
84
     * @var int|null
85
     */
86
    private $mapTtl;
87
    /**
88
     * @var bool
89
     */
90
    private $fullMapComputed = false;
91
    /**
92
     * @var bool
93
     */
94
    private $fullExtendMapComputed = false;
95
    /**
96
     * @var NamingStrategy
97
     */
98
    private $namingStrategy;
99
    /**
100
     * @var InputTypeGenerator
101
     */
102
    private $inputTypeGenerator;
103
    /**
104
     * @var InputTypeUtils
105
     */
106
    private $inputTypeUtils;
107
    /**
108
     * The array of globbed classes.
109
     * Only instantiable classes are returned.
110
     * Key: fully qualified class name
111
     *
112
     * @var array<string,ReflectionClass>
113
     */
114
    private $classes;
115
116
    /**
117
     * @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
118
     */
119
    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)
120
    {
121
        $this->namespace = $namespace;
122
        $this->typeGenerator = $typeGenerator;
123
        $this->container = $container;
124
        $this->annotationReader = $annotationReader;
125
        $this->namingStrategy = $namingStrategy;
126
        $this->cache = $cache;
127
        $this->globTtl = $globTtl;
128
        $this->mapTtl = $mapTtl;
129
        $this->inputTypeGenerator = $inputTypeGenerator;
130
        $this->inputTypeUtils = $inputTypeUtils;
131
    }
132
133
    /**
134
     * Returns an array of fully qualified class names.
135
     *
136
     * @return array<string, array<string,string>>
137
     */
138
    private function getMaps(): array
139
    {
140
        if ($this->fullMapComputed === false) {
141
            $namespace = str_replace('\\', '_', $this->namespace);
142
            $keyClassCache = 'globTypeMapper_'.$namespace;
143
            $keyNameCache = 'globTypeMapper_names_'.$namespace;
144
            $keyInputClassCache = 'globInputTypeMapper_'.$namespace;
145
            $keyInputNameCache = 'globInputTypeMapper_names_'.$namespace;
146
            $this->mapClassToTypeArray = $this->cache->get($keyClassCache);
147
            $this->mapNameToType = $this->cache->get($keyNameCache);
148
            $this->mapClassToFactory = $this->cache->get($keyInputClassCache);
149
            $this->mapInputNameToFactory = $this->cache->get($keyInputNameCache);
150
            if ($this->mapClassToTypeArray === null ||
151
                $this->mapNameToType === null ||
152
                $this->mapClassToFactory === null ||
153
                $this->mapInputNameToFactory
154
            ) {
155
                $this->buildMap();
156
                // This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
157
                // Defaults to 2 seconds.
158
                $this->cache->set($keyClassCache, $this->mapClassToTypeArray, $this->globTtl);
159
                $this->cache->set($keyNameCache, $this->mapNameToType, $this->globTtl);
160
                $this->cache->set($keyInputClassCache, $this->mapClassToFactory, $this->globTtl);
161
                $this->cache->set($keyInputNameCache, $this->mapInputNameToFactory, $this->globTtl);
162
            }
163
            $this->fullMapComputed = true;
164
        }
165
        return [
166
            'mapClassToTypeArray' => $this->mapClassToTypeArray,
167
            'mapNameToType' => $this->mapNameToType,
168
            'mapClassToFactory' => $this->mapClassToFactory,
169
            'mapInputNameToFactory' => $this->mapInputNameToFactory,
170
        ];
171
    }
172
173
    private function getMapClassToType(): array
174
    {
175
        return $this->getMaps()['mapClassToTypeArray'];
176
    }
177
178
    private function getMapNameToType(): array
0 ignored issues
show
Unused Code introduced by
The method getMapNameToType() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
179
    {
180
        return $this->getMaps()['mapNameToType'];
181
    }
182
183
    private function getMapClassToFactory(): array
0 ignored issues
show
Unused Code introduced by
The method getMapClassToFactory() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
184
    {
185
        return $this->getMaps()['mapClassToFactory'];
186
    }
187
188
    private function getMapInputNameToFactory(): array
0 ignored issues
show
Unused Code introduced by
The method getMapInputNameToFactory() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
189
    {
190
        return $this->getMaps()['mapInputNameToFactory'];
191
    }
192
193
    /**
194
     * Returns an array of fully qualified class names.
195
     *
196
     * @return array<string,array<string,string>>
197
     */
198
    private function getExtendMaps(): array
199
    {
200
        if ($this->fullExtendMapComputed === false) {
201
            $namespace = str_replace('\\', '_', $this->namespace);
202
            $keyExtendClassCache = 'globTypeMapperExtend_'.$namespace;
203
            $keyExtendNameCache = 'globTypeMapperExtend_names_'.$namespace;
204
            $this->mapClassToExtendTypeArray = $this->cache->get($keyExtendClassCache);
205
            $this->mapNameToExtendType = $this->cache->get($keyExtendNameCache);
206
            if ($this->mapClassToExtendTypeArray === null ||
207
                $this->mapNameToExtendType === null
208
            ) {
209
                $this->buildExtendMap();
210
                // This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
211
                // Defaults to 2 seconds.
212
                $this->cache->set($keyExtendClassCache, $this->mapClassToExtendTypeArray, $this->globTtl);
213
                $this->cache->set($keyExtendNameCache, $this->mapNameToExtendType, $this->globTtl);
214
            }
215
            $this->fullExtendMapComputed = true;
216
        }
217
        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...
218
            'mapClassToExtendTypeArray' => $this->mapClassToExtendTypeArray,
219
            'mapNameToExtendType' => $this->mapNameToExtendType,
220
        ];
221
    }
222
223
    private function getMapClassToExtendTypeArray(): array
0 ignored issues
show
Unused Code introduced by
The method getMapClassToExtendTypeArray() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
224
    {
225
        return $this->getExtendMaps()['mapClassToExtendTypeArray'];
226
    }
227
228
    private function getMapNameToExtendType(): array
0 ignored issues
show
Unused Code introduced by
The method getMapNameToExtendType() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
229
    {
230
        return $this->getExtendMaps()['mapNameToExtendType'];
231
    }
232
233
    /**
234
     * Returns the array of globbed classes.
235
     * Only instantiable classes are returned.
236
     *
237
     * @return array<string,ReflectionClass> Key: fully qualified class name
238
     */
239
    private function getClassList(): array
240
    {
241
        if ($this->classes === null) {
242
            $this->classes = [];
243
            $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->globTtl, ClassNameMapper::createFromComposerFile(null, null, true));
244
            $classes = $explorer->getClasses();
245
            foreach ($classes as $className) {
246
                if (!\class_exists($className)) {
247
                    continue;
248
                }
249
                $refClass = new \ReflectionClass($className);
250
                if (!$refClass->isInstantiable()) {
251
                    continue;
252
                }
253
                $this->classes[$className] = $refClass;
254
            }
255
        }
256
        return $this->classes;
257
    }
258
259
    private function buildMap(): void
260
    {
261
        $classes = $this->getClassList();
262
        foreach ($classes as $className => $refClass) {
263
            $type = $this->annotationReader->getTypeAnnotation($refClass);
264
265
            if ($type !== null) {
266
                if (isset($this->mapClassToTypeArray[$type->getClass()])) {
267
                    /*if ($this->mapClassToTypeArray[$type->getClass()] === $className) {
268
                        // Already mapped. Let's continue
269
                        continue;
270
                    }*/
271
                    throw DuplicateMappingException::createForType($type->getClass(), $this->mapClassToTypeArray[$type->getClass()], $className);
272
                }
273
                $this->storeTypeInCache($className, $type, $refClass->getFileName());
274
            }
275
276
            foreach ($refClass->getMethods() as $method) {
277
                $factory = $this->annotationReader->getFactoryAnnotation($method);
278
                if ($factory !== null) {
279
                    [$inputName, $className] = $this->inputTypeUtils->getInputTypeNameAndClassName($method);
280
281
                    if (isset($this->mapClassToFactory[$className])) {
282
                        throw DuplicateMappingException::createForFactory($className, $this->mapClassToFactory[$className][0], $this->mapClassToFactory[$className][1], $refClass->getName(), $method->name);
283
                    }
284
                    $this->storeInputTypeInCache($method, $inputName, $className, $refClass->getFileName());
285
                }
286
            }
287
288
        }
289
    }
290
291
    private function buildExtendMap(): void
292
    {
293
        $classes = $this->getClassList();
294
        foreach ($classes as $className => $refClass) {
295
            $extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
296
297
            if ($extendType !== null) {
298
                $this->storeExtendTypeInCache($className, $extendType, $refClass->getFileName());
299
            }
300
        }
301
    }
302
303
    /**
304
     * Stores in cache the mapping TypeClass <=> Object class <=> GraphQL type name.
305
     */
306
    private function storeTypeInCache(string $typeClassName, Type $type, string $typeFileName): void
307
    {
308
        $objectClassName = $type->getClass();
309
        $this->mapClassToTypeArray[$objectClassName] = $typeClassName;
310
        $this->cache->set('globTypeMapperByClass_'.str_replace('\\', '_', $objectClassName), [
311
            'filemtime' => filemtime($typeFileName),
312
            'fileName' => $typeFileName,
313
            'typeClass' => $typeClassName
314
        ], $this->mapTtl);
315
        $typeName = $this->namingStrategy->getOutputTypeName($typeClassName, $type);
316
        $this->mapNameToType[$typeName] = $typeClassName;
317
        $this->cache->set('globTypeMapperByName_'.$typeName, [
318
            'filemtime' => filemtime($typeFileName),
319
            'fileName' => $typeFileName,
320
            'typeClass' => $typeClassName
321
        ], $this->mapTtl);
322
    }
323
324
    /**
325
     * Stores in cache the mapping between InputType name <=> Object class
326
     */
327
    private function storeInputTypeInCache(ReflectionMethod $refMethod, string $inputName, string $className, string $fileName): void
328
    {
329
        $refArray = [$refMethod->getDeclaringClass()->getName(), $refMethod->getName()];
330
        $this->mapClassToFactory[$className] = $refArray;
331
        $this->cache->set('globInputTypeMapperByClass_'.str_replace('\\', '_', $className), [
332
            'filemtime' => filemtime($fileName),
333
            'fileName' => $fileName,
334
            'factory' => $refArray
335
        ], $this->mapTtl);
336
        $this->mapInputNameToFactory[$inputName] = $refArray;
337
        $this->cache->set('globInputTypeMapperByName_'.$inputName, [
338
            'filemtime' => filemtime($fileName),
339
            'fileName' => $fileName,
340
            'factory' => $refArray
341
        ], $this->mapTtl);
342
    }
343
344
    /**
345
     * Stores in cache the mapping ExtendTypeClass <=> Object class <=> GraphQL type name.
346
     */
347
    private function storeExtendTypeInCache(string $extendTypeClassName, ExtendType $extendType, string $typeFileName): void
348
    {
349
        $objectClassName = $extendType->getClass();
350
        $this->mapClassToExtendTypeArray[$objectClassName][$extendTypeClassName] = $extendTypeClassName;
351
        $this->cache->set('globExtendTypeMapperByClass_'.str_replace('\\', '_', $objectClassName), [
352
            'filemtime' => filemtime($typeFileName),
353
            'fileName' => $typeFileName,
354
            'extendTypeClasses' => $this->mapClassToExtendTypeArray[$objectClassName]
355
        ], $this->mapTtl);
356
357
        // TODO: this is kind of a hack. Ideally, we would need to find the GraphQL type name from the class name.
358
359
        // FIXME: this is WRONG! we need to get the NAME of the GraphQL type from the $extendTypeClassName
360
        // The only thing we have is the name of the main class (in $extendType->getClass())
361
        // From there, we need to FIND the name of the type. We need a $recursiveTypeMapper->mapClassToTypeName method.
362
363
        // OOOOOOR: MAYBE WE DONT STORE THIS ASSOCIATION AT ALL!! => How???
364
365
        // OOOOOOR again: ExtendType is targetting the GraphQL NAME and not the type!!! @ExtendType(name="Foo") => But that does not work, we also need the class name just above!
366
367
368
        // YET ANOTHER IDEA: global refactor:
369
        // Instead of returning types, we return TypeFactories.
370
        // A type factory is an interface with:
371
        // - className
372
        // - typeName
373
        // - list of field factories
374
        // - list of files used to build it with timestamp. Any change in one file and the type is no longer valid
375
        // A type factory is serializable.
376
377
        $type = new Type(['class'=>$extendType->getClass()]);
378
        $typeName = $this->namingStrategy->getOutputTypeName($extendTypeClassName, $type);
379
        $this->mapNameToExtendType[$typeName][$extendTypeClassName] = $extendTypeClassName;
380
        $this->cache->set('globExtendTypeMapperByName_'.$typeName, [
381
            'filemtime' => filemtime($typeFileName),
382
            'fileName' => $typeFileName,
383
            'extendTypeClasses' => $this->mapClassToExtendTypeArray[$objectClassName]
384
        ], $this->mapTtl);
385
    }
386
387
    private function getTypeFromCacheByObjectClass(string $className): ?string
388
    {
389
        if (isset($this->mapClassToTypeArray[$className])) {
390
            return $this->mapClassToTypeArray[$className];
391
        }
392
393
        // Let's try from the cache
394
        $item = $this->cache->get('globTypeMapperByClass_'.str_replace('\\', '_', $className));
395
        if ($item !== null) {
396
            [
397
                'filemtime' => $filemtime,
398
                'fileName' => $typeFileName,
399
                'typeClass' => $typeClassName
400
            ] = $item;
401
402
            if ($filemtime === filemtime($typeFileName)) {
403
                $this->mapClassToTypeArray[$className] = $typeClassName;
404
                return $typeClassName;
405
            }
406
        }
407
408
        // cache miss
409
        return null;
410
    }
411
412
    private function getTypeFromCacheByGraphQLTypeName(string $graphqlTypeName): ?string
413
    {
414
        if (isset($this->mapNameToType[$graphqlTypeName])) {
415
            return $this->mapNameToType[$graphqlTypeName];
416
        }
417
418
        // Let's try from the cache
419
        $item = $this->cache->get('globTypeMapperByName_'.$graphqlTypeName);
420
        if ($item !== null) {
421
            [
422
                'filemtime' => $filemtime,
423
                'fileName' => $typeFileName,
424
                'typeClass' => $typeClassName
425
            ] = $item;
426
427
            if ($filemtime === filemtime($typeFileName)) {
428
                $this->mapNameToType[$graphqlTypeName] = $typeClassName;
429
                return $typeClassName;
430
            }
431
        }
432
433
        // cache miss
434
        return null;
435
    }
436
437
    /**
438
     * @return string[]|null A pointer to the factory [$className, $methodName] or null on cache miss
439
     */
440
    private function getFactoryFromCacheByObjectClass(string $className): ?array
441
    {
442
        if (isset($this->mapClassToFactory[$className])) {
443
            return $this->mapClassToFactory[$className];
444
        }
445
446
        // Let's try from the cache
447
        $item = $this->cache->get('globInputTypeMapperByClass_'.str_replace('\\', '_', $className));
448
        if ($item !== null) {
449
            [
450
                'filemtime' => $filemtime,
451
                'fileName' => $typeFileName,
452
                'factory' => $factory
453
            ] = $item;
454
455
            if ($filemtime === filemtime($typeFileName)) {
456
                $this->mapClassToFactory[$className] = $factory;
457
                return $factory;
458
            }
459
        }
460
461
        // cache miss
462
        return null;
463
    }
464
465
    /**
466
     * @param string $className
467
     * @return array<string,string>|null An array of classes with the @ExtendType annotation (key and value = FQCN)
468
     */
469
    private function getExtendTypesFromCacheByObjectClass(string $className): ?array
470
    {
471
        if (isset($this->mapClassToExtendTypeArray[$className])) {
472
            return $this->mapClassToExtendTypeArray[$className];
473
        }
474
475
        // Let's try from the cache
476
        $item = $this->cache->get('globExtendTypeMapperByClass_'.str_replace('\\', '_', $className));
477
        if ($item !== null) {
478
            [
479
                'filemtime' => $filemtime,
480
                'fileName' => $typeFileName,
481
                'extendTypeClasses' => $extendTypeClassNames
482
            ] = $item;
483
484
            if ($filemtime === filemtime($typeFileName)) {
485
                $this->mapClassToExtendTypeArray[$className] = $extendTypeClassNames;
486
                return $extendTypeClassNames;
487
            }
488
        }
489
490
        // cache miss
491
        return null;
492
    }
493
494
    /**
495
     * @param string $graphqlTypeName
496
     * @return array<string,string>|null An array of classes with the @ExtendType annotation (key and value = FQCN)
497
     */
498
    private function getExtendTypesFromCacheByGraphQLTypeName(string $graphqlTypeName): ?array
499
    {
500
        if (isset($this->mapNameToExtendType[$graphqlTypeName])) {
501
            return $this->mapNameToExtendType[$graphqlTypeName];
502
        }
503
504
        // Let's try from the cache
505
        $item = $this->cache->get('globExtendTypeMapperByName_'.$graphqlTypeName);
506
        if ($item !== null) {
507
            [
508
                'filemtime' => $filemtime,
509
                'fileName' => $typeFileName,
510
                'extendTypeClasses' => $extendTypeClassNames
511
            ] = $item;
512
513
            if ($filemtime === filemtime($typeFileName)) {
514
                $this->mapNameToExtendType[$graphqlTypeName] = $extendTypeClassNames;
515
                return $extendTypeClassNames;
516
            }
517
        }
518
519
        // cache miss
520
        return null;
521
    }
522
523
    /**
524
     * @return string[]|null A pointer to the factory [$className, $methodName] or null on cache miss
525
     */
526
    private function getFactoryFromCacheByGraphQLInputTypeName(string $graphqlTypeName): ?array
527
    {
528
        if (isset($this->mapInputNameToFactory[$graphqlTypeName])) {
529
            return $this->mapInputNameToFactory[$graphqlTypeName];
530
        }
531
532
        // Let's try from the cache
533
        $item = $this->cache->get('globInputTypeMapperByName_'.$graphqlTypeName);
534
        if ($item !== null) {
535
            [
536
                'filemtime' => $filemtime,
537
                'fileName' => $typeFileName,
538
                'factory' => $factory
539
            ] = $item;
540
541
            if ($filemtime === filemtime($typeFileName)) {
542
                $this->mapInputNameToFactory[$graphqlTypeName] = $factory;
543
                return $factory;
544
            }
545
        }
546
547
        // cache miss
548
        return null;
549
    }
550
551
    /**
552
     * Returns true if this type mapper can map the $className FQCN to a GraphQL type.
553
     *
554
     * @param string $className
555
     * @return bool
556
     */
557
    public function canMapClassToType(string $className): bool
558
    {
559
        $typeClassName = $this->getTypeFromCacheByObjectClass($className);
560
561
        if ($typeClassName === null) {
562
            $this->getMaps();
563
        }
564
565
        return isset($this->mapClassToTypeArray[$className]);
566
    }
567
568
    /**
569
     * Maps a PHP fully qualified class name to a GraphQL type.
570
     *
571
     * @param string $className The exact class name to look for (this function does not look into parent classes).
572
     * @param OutputType|null $subType An optional sub-type if the main class is an iterator that needs to be typed.
573
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
574
     * @return ObjectType
575
     * @throws CannotMapTypeExceptionInterface
576
     */
577
    public function mapClassToType(string $className, ?OutputType $subType, RecursiveTypeMapperInterface $recursiveTypeMapper): ObjectType
578
    {
579
        $typeClassName = $this->getTypeFromCacheByObjectClass($className);
580
581
        if ($typeClassName === null) {
582
            $this->getMaps();
583
        }
584
585
        if (!isset($this->mapClassToTypeArray[$className])) {
586
            throw CannotMapTypeException::createForType($className);
587
        }
588
        return $this->typeGenerator->mapAnnotatedObject($this->container->get($this->mapClassToTypeArray[$className]), $recursiveTypeMapper);
589
    }
590
591
    /**
592
     * Returns the list of classes that have matching input GraphQL types.
593
     *
594
     * @return string[]
595
     */
596
    public function getSupportedClasses(): array
597
    {
598
        return array_keys($this->getMapClassToType());
599
    }
600
601
    /**
602
     * Returns true if this type mapper can map the $className FQCN to a GraphQL input type.
603
     *
604
     * @param string $className
605
     * @return bool
606
     */
607
    public function canMapClassToInputType(string $className): bool
608
    {
609
        $factory = $this->getFactoryFromCacheByObjectClass($className);
610
611
        if ($factory === null) {
612
            $this->getMaps();
613
        }
614
        return isset($this->mapClassToFactory[$className]);
615
    }
616
617
    /**
618
     * Maps a PHP fully qualified class name to a GraphQL input type.
619
     *
620
     * @param string $className
621
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
622
     * @return InputObjectType
623
     * @throws CannotMapTypeExceptionInterface
624
     */
625
    public function mapClassToInputType(string $className, RecursiveTypeMapperInterface $recursiveTypeMapper): InputObjectType
626
    {
627
        $factory = $this->getFactoryFromCacheByObjectClass($className);
628
629
        if ($factory === null) {
630
            $this->getMaps();
631
        }
632
633
        if (!isset($this->mapClassToFactory[$className])) {
634
            throw CannotMapTypeException::createForInputType($className);
635
        }
636
        return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($this->mapClassToFactory[$className][0]), $this->mapClassToFactory[$className][1], $recursiveTypeMapper);
637
    }
638
639
    /**
640
     * Returns a GraphQL type by name (can be either an input or output type)
641
     *
642
     * @param string $typeName The name of the GraphQL type
643
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
644
     * @return \GraphQL\Type\Definition\Type&(InputType|OutputType)
645
     * @throws CannotMapTypeExceptionInterface
646
     * @throws \ReflectionException
647
     */
648
    public function mapNameToType(string $typeName, RecursiveTypeMapperInterface $recursiveTypeMapper): \GraphQL\Type\Definition\Type
649
    {
650
        $typeClassName = $this->getTypeFromCacheByGraphQLTypeName($typeName);
651
        if ($typeClassName === null) {
652
            $factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
653
            if ($factory === null) {
654
                $this->getMaps();
655
            }
656
        }
657
658
        if (isset($this->mapNameToType[$typeName])) {
659
            return $this->typeGenerator->mapAnnotatedObject($this->container->get($this->mapNameToType[$typeName]), $recursiveTypeMapper);
660
        }
661
        if (isset($this->mapInputNameToFactory[$typeName])) {
662
            $factory = $this->mapInputNameToFactory[$typeName];
663
            return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($factory[0]), $factory[1], $recursiveTypeMapper);
664
        }
665
666
        throw CannotMapTypeException::createForName($typeName);
667
    }
668
669
    /**
670
     * Returns true if this type mapper can map the $typeName GraphQL name to a GraphQL type.
671
     *
672
     * @param string $typeName The name of the GraphQL type
673
     * @return bool
674
     */
675
    public function canMapNameToType(string $typeName): bool
676
    {
677
        $typeClassName = $this->getTypeFromCacheByGraphQLTypeName($typeName);
678
679
        if ($typeClassName !== null) {
680
            return true;
681
        }
682
683
        $factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
684
        if ($factory !== null) {
685
            return true;
686
        }
687
688
        $this->getMaps();
689
690
        return isset($this->mapNameToType[$typeName]) || isset($this->mapInputNameToFactory[$typeName]);
691
    }
692
693
    /**
694
     * Returns true if this type mapper can extend an existing type for the $className FQCN
695
     *
696
     * @param string $className
697
     * @param ObjectType $type
698
     * @return bool
699
     */
700
    public function canExtendTypeForClass(string $className, ObjectType $type): bool
701
    {
702
        $extendTypeClassName = $this->getExtendTypesFromCacheByObjectClass($className);
703
704
        if ($extendTypeClassName === null) {
705
            $this->getExtendMaps();
706
        }
707
708
        return isset($this->mapClassToExtendTypeArray[$className]);
709
    }
710
711
    /**
712
     * Extends the existing GraphQL type that is mapped to $className.
713
     *
714
     * @param string $className
715
     * @param ObjectType $type
716
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
717
     * @return ObjectType
718
     * @throws CannotMapTypeExceptionInterface
719
     */
720
    public function extendTypeForClass(string $className, ObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): ObjectType
721
    {
722
        $extendTypeClassNames = $this->getExtendTypesFromCacheByObjectClass($className);
723
724
        if ($extendTypeClassNames === null) {
725
            $this->getExtendMaps();
726
        }
727
728
        if (!isset($this->mapClassToExtendTypeArray[$className])) {
729
            throw CannotMapTypeException::createForExtendType($className, $type);
730
        }
731
732
        foreach ($this->mapClassToExtendTypeArray[$className] as $extendedTypeClass) {
733
            $type = $this->typeGenerator->extendAnnotatedObject($this->container->get($extendedTypeClass), $type, $recursiveTypeMapper);
734
        }
735
        return $type;
736
    }
737
738
    /**
739
     * Returns true if this type mapper can extend an existing type for the $typeName GraphQL type
740
     *
741
     * @param string $typeName
742
     * @param ObjectType $type
743
     * @return bool
744
     */
745
    public function canExtendTypeForName(string $typeName, ObjectType $type): bool
746
    {
747
        $typeClassNames = $this->getExtendTypesFromCacheByGraphQLTypeName($typeName);
748
749
        if ($typeClassNames !== null) {
750
            return true;
751
        }
752
753
        /*$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
754
        if ($factory !== null) {
755
            return true;
756
        }*/
757
758
        $this->getExtendMaps();
759
760
        return isset($this->mapNameToExtendType[$typeName])/* || isset($this->mapInputNameToFactory[$typeName])*/;
761
    }
762
763
    /**
764
     * Extends the existing GraphQL type that is mapped to the $typeName GraphQL type.
765
     *
766
     * @param string $typeName
767
     * @param ObjectType $type
768
     * @param RecursiveTypeMapperInterface $recursiveTypeMapper
769
     * @return ObjectType
770
     * @throws CannotMapTypeExceptionInterface
771
     */
772
    public function extendTypeForName(string $typeName, ObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): ObjectType
773
    {
774
        $extendTypeClassNames = $this->getExtendTypesFromCacheByGraphQLTypeName($typeName);
775
        if ($extendTypeClassNames === null) {
776
            /*$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
777
            if ($factory === null) {*/
778
                $this->getExtendMaps();
779
            //}
780
        }
781
782
        if (isset($this->mapNameToExtendType[$typeName])) {
783
            foreach ($this->mapNameToExtendType[$typeName] as $extendedTypeClass) {
784
                $type = $this->typeGenerator->extendAnnotatedObject($this->container->get($extendedTypeClass), $type, $recursiveTypeMapper);
785
            }
786
            return $type;
787
        }
788
        /*if (isset($this->mapInputNameToFactory[$typeName])) {
789
            $factory = $this->mapInputNameToFactory[$typeName];
790
            return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($factory[0]), $factory[1], $recursiveTypeMapper);
791
        }*/
792
793
        throw CannotMapTypeException::createForExtendName($typeName, $type);
794
    }
795
}
796