Completed
Pull Request — master (#44)
by David
02:24
created

GlobTypeMapper::canMapClassToType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
4
namespace TheCodingMachine\GraphQL\Controllers\Mappers;
5
6
use function array_keys;
7
use Doctrine\Common\Annotations\Reader;
8
use GraphQL\Type\Definition\InputType;
9
use GraphQL\Type\Definition\OutputType;
10
use Mouf\Composer\ClassNameMapper;
11
use Psr\Container\ContainerInterface;
12
use Psr\SimpleCache\CacheInterface;
13
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
14
use TheCodingMachine\GraphQL\Controllers\Annotations\Exceptions\ClassNotFoundException;
15
use TheCodingMachine\GraphQL\Controllers\Annotations\Type;
16
use TheCodingMachine\GraphQL\Controllers\AnnotationUtils;
17
use TheCodingMachine\GraphQL\Controllers\TypeGenerator;
18
19
/**
20
 * Scans all the classes in a given namespace of the main project (not the vendor directory).
21
 * Analyzes all classes and uses the @Type annotation to find the types automatically.
22
 *
23
 * Assumes that the container contains a class whose identifier is the same as the class name.
24
 */
25
final class GlobTypeMapper implements TypeMapperInterface
26
{
27
    /**
28
     * @var string
29
     */
30
    private $namespace;
31
    /**
32
     * @var Reader
33
     */
34
    private $annotationReader;
35
    /**
36
     * @var CacheInterface
37
     */
38
    private $cache;
39
    /**
40
     * @var int|null
41
     */
42
    private $cacheTtl;
43
    /**
44
     * @var array<string,string>|null
45
     */
46
    private $map;
47
    /**
48
     * @var ContainerInterface
49
     */
50
    private $container;
51
    /**
52
     * @var TypeGenerator
53
     */
54
    private $typeGenerator;
55
56
    /**
57
     * @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
58
     */
59
    public function __construct(string $namespace, TypeGenerator $typeGenerator, ContainerInterface $container, Reader $annotationReader, CacheInterface $cache, ?int $cacheTtl = null)
60
    {
61
        $this->namespace = $namespace;
62
        $this->container = $container;
63
        $this->annotationReader = $annotationReader;
64
        $this->cache = $cache;
65
        $this->cacheTtl = $cacheTtl;
66
        $this->typeGenerator = $typeGenerator;
67
    }
68
69
    /**
70
     * Returns an array of fully qualified class names.
71
     *
72
     * @return array<string,string>
73
     */
74
    private function getMap(): array
75
    {
76
        if ($this->map === null) {
77
            $key = 'globTypeMapper_'.str_replace('\\', '_', $this->namespace);
78
            $this->map = $this->cache->get($key);
79
            if ($this->map === null) {
80
                $this->map = $this->buildMap();
81
                $this->cache->set($key, $this->map, $this->cacheTtl);
82
            }
83
        }
84
        return $this->map;
85
    }
86
87
    /**
88
     * @return array<string,string> Maps the class name of a target object to the class name of the GraphQL Type class.
89
     */
90
    private function buildMap(): array
91
    {
92
        $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->cacheTtl, ClassNameMapper::createFromComposerFile(null, null, true));
93
        $classes = $explorer->getClasses();
94
        $map = [];
95
        foreach ($classes as $className) {
96
            if (!\class_exists($className)) {
97
                continue;
98
            }
99
            $refClass = new \ReflectionClass($className);
100
            if (!$refClass->isInstantiable()) {
101
                continue;
102
            }
103
            /** @var Type|null $type */
104
            try {
105
                $type = AnnotationUtils::getClassAnnotation($this->annotationReader, $refClass, Type::class);
106
            } catch (ClassNotFoundException $e) {
107
                throw ClassNotFoundException::wrapException($e, $className);
108
            }
109
110
            if ($type === null) {
111
                continue;
112
            }
113
            if (isset($map[$type->getClass()])) {
114
                throw DuplicateMappingException::create($type->getClass(), $map[$type->getClass()], $className);
115
            }
116
            $map[$type->getClass()] = $className;
117
        }
118
        return $map;
119
    }
120
121
    /**
122
     * Returns true if this type mapper can map the $className FQCN to a GraphQL type.
123
     *
124
     * @param string $className
125
     * @return bool
126
     */
127
    public function canMapClassToType(string $className): bool
128
    {
129
        $map = $this->getMap();
130
        return isset($map[$className]);
131
    }
132
133
    /**
134
     * Maps a PHP fully qualified class name to a GraphQL type.
135
     *
136
     * @param string $className
137
     * @return OutputType
138
     * @throws CannotMapTypeException
139
     */
140
    public function mapClassToType(string $className): OutputType
141
    {
142
        $map = $this->getMap();
143
        if (!isset($map[$className])) {
144
            throw CannotMapTypeException::createForType($className);
145
        }
146
        return $this->typeGenerator->mapAnnotatedObject($this->container->get($map[$className]));
147
    }
148
149
    /**
150
     * Returns the list of classes that have matching input GraphQL types.
151
     *
152
     * @return string[]
153
     */
154
    public function getSupportedClasses(): array
155
    {
156
        return array_keys($this->getMap());
157
    }
158
159
    /**
160
     * Returns true if this type mapper can map the $className FQCN to a GraphQL input type.
161
     *
162
     * @param string $className
163
     * @return bool
164
     */
165
    public function canMapClassToInputType(string $className): bool
166
    {
167
        return false;
168
    }
169
170
    /**
171
     * Maps a PHP fully qualified class name to a GraphQL input type.
172
     *
173
     * @param string $className
174
     * @return InputType
175
     * @throws CannotMapTypeException
176
     */
177
    public function mapClassToInputType(string $className): InputType
178
    {
179
        throw CannotMapTypeException::createForInputType($className);
180
    }
181
}
182