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

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