Passed
Pull Request — master (#78)
by David
02:09
created

getExtendTypesFromCacheByObjectClass()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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