Completed
Push — master ( 6fb2a7...77b356 )
by Adrien
02:36
created

Types   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 22
dl 0
loc 234
ccs 62
cts 62
cp 1
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getOutput() 0 11 2
A throwIfNotEntity() 0 4 2
A getPartialInput() 0 11 2
A getPhpToGraphQLMapping() 0 8 1
A registerInstance() 0 3 1
A has() 0 3 3
A get() 0 14 4
A getInput() 0 11 2
A isEntity() 0 3 2
A getId() 0 11 2
A __construct() 0 11 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Doctrine;
6
7
use Doctrine\Common\Annotations\AnnotationRegistry;
8
use Doctrine\ORM\EntityManager;
9
use GraphQL\Doctrine\Definition\EntityIDType;
10
use GraphQL\Doctrine\Factory\InputTypeFactory;
11
use GraphQL\Doctrine\Factory\ObjectTypeFactory;
12
use GraphQL\Doctrine\Factory\PartialInputTypeFactory;
13
use GraphQL\Type\Definition\InputObjectType;
14
use GraphQL\Type\Definition\ObjectType;
15
use GraphQL\Type\Definition\Type;
16
use Psr\Container\ContainerInterface;
17
18
/**
19
 * Registry of types to manage all GraphQL types
20
 *
21
 * This is the entry point for the library.
22
 */
23
class Types
24
{
25
    /**
26
     * @var null|ContainerInterface
27
     */
28
    private $customTypes;
29
30
    /**
31
     * @var array mapping of type name to type instances
32
     */
33
    private $types = [];
34
35
    /**
36
     * @var ObjectTypeFactory
37
     */
38
    private $objectTypeFactory;
39
40
    /**
41
     * @var InputTypeFactory
42
     */
43
    private $inputTypeFactory;
44
45
    /**
46
     * @var PartialInputTypeFactory
47
     */
48
    private $partialInputTypeFactory;
49
50
    /**
51
     * @var EntityManager
52
     */
53
    private $entityManager;
54
55 24
    public function __construct(EntityManager $entityManager, ?ContainerInterface $customTypes = null)
56
    {
57 24
        $this->customTypes = $customTypes;
58 24
        $this->types = $this->getPhpToGraphQLMapping();
59 24
        $this->entityManager = $entityManager;
60 24
        $this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager);
61 24
        $this->inputTypeFactory = new InputTypeFactory($this, $entityManager);
62 24
        $this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager);
63
64 24
        $entityManager->getConfiguration()->newDefaultAnnotationDriver();
65 24
        AnnotationRegistry::registerLoader('class_exists');
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Common\Annotati...istry::registerLoader() has been deprecated: this method is deprecated and will be removed in doctrine/annotations 2.0 autoloading should be deferred to the globally registered autoloader by then. For now, use @example AnnotationRegistry::registerLoader('class_exists') ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

65
        /** @scrutinizer ignore-deprecated */ AnnotationRegistry::registerLoader('class_exists');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
66 24
    }
67
68
    /**
69
     * Returns whether a type exists for the given key
70
     *
71
     * @param string $key
72
     *
73
     * @return bool
74
     */
75 1
    public function has(string $key): bool
76
    {
77 1
        return $this->customTypes && $this->customTypes->has($key) || array_key_exists($key, $this->types);
78
    }
79
80
    /**
81
     * Always return the same instance of `Type` for the given key
82
     *
83
     * It will first look for the type in the custom types container, and then
84
     * use automatically generated types. This allow for custom types to override
85
     * automatic ones.
86
     *
87
     * @param string $key the key the type was registered with (eg: "Post", "PostInput", "PostPartialInput" or "PostStatus")
88
     *
89
     * @return Type
90
     */
91 14
    public function get(string $key): Type
92
    {
93 14
        if ($this->customTypes && $this->customTypes->has($key)) {
94 12
            $t = $this->customTypes->get($key);
95 12
            $this->registerInstance($t);
96
97 12
            return $t;
98
        }
99
100 10
        if (array_key_exists($key, $this->types)) {
101 9
            return $this->types[$key];
102
        }
103
104 1
        throw new Exception('No type registered with key `' . $key . '`. Either correct the usage, or register it in your custom types container when instantiating `' . self::class . '`.');
105
    }
106
107
    /**
108
     * Returns an output type for the given entity
109
     *
110
     * All entity getter methods will be exposed, unless specified otherwise
111
     * with annotations.
112
     *
113
     * @param string $className the class name of an entity (`Post::class`)
114
     *
115
     * @return ObjectType
116
     */
117 14
    public function getOutput(string $className): ObjectType
118
    {
119 14
        $this->throwIfNotEntity($className);
120 13
        $key = Utils::getTypeName($className);
121
122 13
        if (!isset($this->types[$key])) {
123 13
            $instance = $this->objectTypeFactory->create($className);
124 13
            $this->registerInstance($instance);
125
        }
126
127 13
        return $this->types[$key];
128
    }
129
130
    /**
131
     * Returns an input type for the given entity
132
     *
133
     * This would typically be used in mutations to create new entities.
134
     *
135
     * All entity setter methods will be exposed, unless specified otherwise
136
     * with annotations.
137
     *
138
     * @param string $className the class name of an entity (`Post::class`)
139
     *
140
     * @return InputObjectType
141
     */
142 5
    public function getInput(string $className): InputObjectType
143
    {
144 5
        $this->throwIfNotEntity($className);
145 5
        $key = Utils::getInputTypeName($className);
146
147 5
        if (!isset($this->types[$key])) {
148 5
            $instance = $this->inputTypeFactory->create($className);
149 5
            $this->registerInstance($instance);
150
        }
151
152 5
        return $this->types[$key];
153
    }
154
155
    /**
156
     * Returns a partial input type for the given entity
157
     *
158
     * This would typically be used in mutations to update existing entities.
159
     *
160
     * All entity setter methods will be exposed, unless specified otherwise
161
     * with annotations. But they will all be marked as optional and without
162
     * default values. So this allow the API client to specify only some fields
163
     * to be updated, and not necessarily all of them at once.
164
     *
165
     * @param string $className the class name of an entity (`Post::class`)
166
     *
167
     * @return InputObjectType
168
     */
169 1
    public function getPartialInput(string $className): InputObjectType
170
    {
171 1
        $this->throwIfNotEntity($className);
172 1
        $key = Utils::getPartialInputTypeName($className);
173
174 1
        if (!isset($this->types[$key])) {
175 1
            $instance = $this->partialInputTypeFactory->create($className);
176 1
            $this->registerInstance($instance);
177
        }
178
179 1
        return $this->types[$key];
180
    }
181
182
    /**
183
     * Returns an special ID type for the given entity
184
     *
185
     * This is mostly useful for internal usage when a getter has an entity
186
     * as parameter. This type will automatically load the entity from DB, so
187
     * the resolve functions can use a real instance of entity instead of an ID.
188
     * But this can also be used to build your own schema and thus avoid
189
     * manually fetching objects from database for simple cases.
190
     *
191
     * @param string $className the class name of an entity (`Post::class`)
192
     *
193
     * @return EntityIDType
194
     */
195 3
    public function getId(string $className): EntityIDType
196
    {
197 3
        $this->throwIfNotEntity($className);
198 3
        $key = Utils::getIDTypeName($className);
199
200 3
        if (!isset($this->types[$key])) {
201 3
            $instance = new EntityIDType($this->entityManager, $className);
202 3
            $this->registerInstance($instance);
203
        }
204
205 3
        return $this->types[$key];
206
    }
207
208
    /**
209
     * Register the given type in our internal registry with its name
210
     *
211
     * @param Type $instance
212
     */
213 21
    private function registerInstance(Type $instance): void
214
    {
215 21
        $this->types[$instance->name] = $instance;
216 21
    }
217
218
    /**
219
     * Checks if a className is a valid doctrine entity
220
     *
221
     * @param string $className
222
     *
223
     * @return bool
224
     */
225 18
    public function isEntity(string $className): bool
226
    {
227 18
        return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className);
228
    }
229
230
    /**
231
     * Returns the list of native GraphQL types
232
     *
233
     * @return array
234
     */
235 24
    private function getPhpToGraphQLMapping(): array
236
    {
237
        return [
238 24
            'id' => Type::id(),
239 24
            'bool' => Type::boolean(),
240 24
            'int' => Type::int(),
241 24
            'float' => Type::float(),
242 24
            'string' => Type::string(),
243
        ];
244
    }
245
246
    /**
247
     * Throw an exception if the class name is not Doctrine entity
248
     *
249
     * @param string $className
250
     *
251
     * @throws \UnexpectedValueException
252
     */
253 18
    private function throwIfNotEntity(string $className): void
254
    {
255 18
        if (!$this->isEntity($className)) {
256 1
            throw new \UnexpectedValueException('Given class name `' . $className . '` is not a Doctrine entity. Either register a custom GraphQL type for `' . $className . '` when instantiating `' . self::class . '`, or change the usage of that class to something else.');
257
        }
258 17
    }
259
}
260