Failed Conditions
Pull Request — master (#10)
by Adrien
02:25
created

Types::throwIfNotEntity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
cc 2
eloc 2
nc 2
nop 1
crap 2
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 Doctrine\ORM\QueryBuilder;
10
use GraphQL\Doctrine\Definition\EntityIDType;
11
use GraphQL\Doctrine\Definition\JoinTypeType;
12
use GraphQL\Doctrine\Definition\LogicalOperatorType;
13
use GraphQL\Doctrine\Definition\Operator\AbstractOperator;
14
use GraphQL\Doctrine\Definition\SortingOrderType;
15
use GraphQL\Doctrine\Factory\FilteredQueryBuilderFactory;
16
use GraphQL\Doctrine\Factory\Type\AbstractTypeFactory;
17
use GraphQL\Doctrine\Factory\Type\EntityIDTypeFactory;
18
use GraphQL\Doctrine\Factory\Type\FilterTypeFactory;
19
use GraphQL\Doctrine\Factory\Type\InputTypeFactory;
20
use GraphQL\Doctrine\Factory\Type\JoinTypeFactory;
21
use GraphQL\Doctrine\Factory\Type\ObjectTypeFactory;
22
use GraphQL\Doctrine\Factory\Type\PartialInputTypeFactory;
23
use GraphQL\Doctrine\Factory\Type\SortingTypeFactory;
24
use GraphQL\Type\Definition\InputObjectType;
25
use GraphQL\Type\Definition\LeafType;
26
use GraphQL\Type\Definition\ListOfType;
27
use GraphQL\Type\Definition\ObjectType;
28
use GraphQL\Type\Definition\Type;
29
use Psr\Container\ContainerInterface;
30
31
/**
32
 * Registry of types to manage all GraphQL types
33
 *
34
 * This is the entry point for the library.
35
 */
36
class Types
37
{
38
    /**
39
     * @var null|ContainerInterface
40
     */
41
    private $customTypes;
42
43
    /**
44
     * @var array mapping of type name to type instances
45
     */
46
    private $types = [];
47
48
    /**
49
     * @var ObjectTypeFactory
50
     */
51
    private $objectTypeFactory;
52
53
    /**
54
     * @var InputTypeFactory
55
     */
56
    private $inputTypeFactory;
57
58
    /**
59
     * @var PartialInputTypeFactory
60
     */
61
    private $partialInputTypeFactory;
62
63
    /**
64
     * @var FilterTypeFactory
65
     */
66
    private $filterTypeFactory;
67
68
    /**
69
     * @var EntityManager
70
     */
71
    private $entityManager;
72
73
    /**
74
     * @var FilteredQueryBuilderFactory
75
     */
76
    private $filteredQueryBuilderFactory;
77
78
    /**
79
     * @var SortingTypeFactory
80
     */
81
    private $sortingTypeFactory;
82
83
    /**
84
     * @var EntityIDTypeFactory
85
     */
86
    private $entityIDTypeFactory;
87
88
    /**
89
     * @var JoinTypeFactory
90
     */
91
    private $joinTypeFactory;
92
93 36
    public function __construct(EntityManager $entityManager, ?ContainerInterface $customTypes = null)
94
    {
95 36
        $this->customTypes = $customTypes;
96 36
        $this->entityManager = $entityManager;
97 36
        $this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager);
98 36
        $this->inputTypeFactory = new InputTypeFactory($this, $entityManager);
99 36
        $this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager);
100 36
        $this->filterTypeFactory = new FilterTypeFactory($this, $entityManager);
101 36
        $this->sortingTypeFactory = new SortingTypeFactory($this, $entityManager);
102 36
        $this->entityIDTypeFactory = new EntityIDTypeFactory($this, $entityManager);
103 36
        $this->joinTypeFactory = new JoinTypeFactory($this, $entityManager);
104 36
        $this->filteredQueryBuilderFactory = new FilteredQueryBuilderFactory($this, $entityManager);
105
106 36
        $entityManager->getConfiguration()->newDefaultAnnotationDriver();
107 36
        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

107
        /** @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...
108
109 36
        $this->initializeInternalTypes();
110 36
    }
111
112
    /**
113
     * Returns whether a type exists for the given key
114
     *
115
     * @param string $key
116
     *
117
     * @return bool
118
     */
119 1
    public function has(string $key): bool
120
    {
121 1
        return $this->customTypes && $this->customTypes->has($key) || array_key_exists($key, $this->types);
122
    }
123
124
    /**
125
     * Always return the same instance of `Type` for the given key
126
     *
127
     * It will first look for the type in the custom types container, and then
128
     * use automatically generated types. This allow for custom types to override
129
     * automatic ones.
130
     *
131
     * @param string $key the key the type was registered with (eg: "Post", "PostInput", "PostPartialInput" or "PostStatus")
132
     *
133
     * @return Type
134
     */
135 26
    public function get(string $key): Type
136
    {
137 26
        if ($this->customTypes && $this->customTypes->has($key)) {
138 18
            $t = $this->customTypes->get($key);
139 18
            $this->registerInstance($t);
140
141 18
            return $t;
142
        }
143
144 22
        if (array_key_exists($key, $this->types)) {
145 21
            return $this->types[$key];
146
        }
147
148 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 . '`.');
149
    }
150
151 30
    private function getViaFactory(string $className, string $typeName, AbstractTypeFactory $factory): Type
152
    {
153 30
        $this->throwIfNotEntity($className);
154
155 29
        if (!isset($this->types[$typeName])) {
156 29
            $instance = $factory->create($className, $typeName);
157 29
            $this->registerInstance($instance);
158
        }
159
160 29
        return $this->types[$typeName];
161
    }
162
163
    /**
164
     * Returns an output type for the given entity
165
     *
166
     * All entity getter methods will be exposed, unless specified otherwise
167
     * with annotations.
168
     *
169
     * @param string $className the class name of an entity (`Post::class`)
170
     *
171
     * @return ObjectType
172
     */
173 14
    public function getOutput(string $className): ObjectType
174
    {
175
        /** @var ObjectType $type */
176 14
        $type = $this->getViaFactory($className, Utils::getTypeName($className), $this->objectTypeFactory);
177
178 13
        return $type;
179
    }
180
181
    /**
182
     * Returns an input type for the given entity
183
     *
184
     * This would typically be used in mutations to create new entities.
185
     *
186
     * All entity setter methods will be exposed, unless specified otherwise
187
     * with annotations.
188
     *
189
     * @param string $className the class name of an entity (`Post::class`)
190
     *
191
     * @return InputObjectType
192
     */
193 5
    public function getInput(string $className): InputObjectType
194
    {
195
        /** @var InputObjectType $type */
196 5
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Input', $this->inputTypeFactory);
197
198 5
        return $type;
199
    }
200
201
    /**
202
     * Returns a partial input type for the given entity
203
     *
204
     * This would typically be used in mutations to update existing entities.
205
     *
206
     * All entity setter methods will be exposed, unless specified otherwise
207
     * with annotations. But they will all be marked as optional and without
208
     * default values. So this allow the API client to specify only some fields
209
     * to be updated, and not necessarily all of them at once.
210
     *
211
     * @param string $className the class name of an entity (`Post::class`)
212
     *
213
     * @return InputObjectType
214
     */
215 1
    public function getPartialInput(string $className): InputObjectType
216
    {
217
        /** @var InputObjectType $type */
218 1
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'PartialInput', $this->partialInputTypeFactory);
219
220 1
        return $type;
221
    }
222
223
    /**
224
     * Returns a filter input type for the given entity
225
     *
226
     * This would typically be used to filter queries.
227
     *
228
     * @param string $className the class name of an entity (`Post::class`)
229
     *
230
     * @return InputObjectType
231
     */
232 11
    public function getFilter(string $className): InputObjectType
233
    {
234
        /** @var InputObjectType $type */
235 11
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Filter', $this->filterTypeFactory);
236
237 11
        return $type;
238
    }
239
240 1
    public function getSorting(string $className): ListOfType
241
    {
242
        /** @var InputObjectType $type */
243 1
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Sorting', $this->sortingTypeFactory);
244
245 1
        return Type::listOf(Type::nonNull($type));
246
    }
247
248 11
    public function getJoin(string $className): InputObjectType
249
    {
250
        /** @var InputObjectType $type */
251 11
        $type = $this->getViaFactory($className, 'JoinOn' . Utils::getTypeName($className), $this->joinTypeFactory);
252
253 11
        return $type;
254
    }
255
256
    /**
257
     * Returns an special ID type for the given entity
258
     *
259
     * This is mostly useful for internal usage when a getter has an entity
260
     * as parameter. This type will automatically load the entity from DB, so
261
     * the resolve functions can use a real instance of entity instead of an ID.
262
     * But this can also be used to build your own schema and thus avoid
263
     * manually fetching objects from database for simple cases.
264
     *
265
     * @param string $className the class name of an entity (`Post::class`)
266
     *
267
     * @return EntityIDType
268
     */
269 3
    public function getId(string $className): EntityIDType
270
    {
271
        /** @var EntityIDType $type */
272 3
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'ID', $this->entityIDTypeFactory);
273
274 3
        return $type;
275
    }
276
277 6
    public function getOperator(string $className, LeafType $type): AbstractOperator
278
    {
279 6
        if (!is_a($className, AbstractOperator::class, true)) {
280
            throw new Exception('Must be a class implementing ' . AbstractOperator::class);
281
        }
282
283 6
        $key = Utils::getOperatorTypeName($className, $type);
284
285 6
        if (!isset($this->types[$key])) {
286 6
            $instance = new $className($type);
287 6
            $this->registerInstance($instance);
288
        }
289
290 6
        return $this->types[$key];
291
    }
292
293
    /**
294
     * Register the given type in our internal registry with its name
295
     *
296
     * @param Type $instance
297
     */
298 36
    public function registerInstance(Type $instance): void
299
    {
300 36
        $this->types[$instance->name] = $instance;
301 36
    }
302
303
    /**
304
     * Checks if a className is a valid doctrine entity
305
     *
306
     * @param string $className
307
     *
308
     * @return bool
309
     */
310 30
    public function isEntity(string $className): bool
311
    {
312 30
        return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className);
313
    }
314
315
    /**
316
     * Initialize internal types for common needs
317
     */
318 36
    private function initializeInternalTypes(): void
319
    {
320
        $phpToGraphQLMapping = [
321
            // PHP types
322 36
            'id' => Type::id(),
323 36
            'bool' => Type::boolean(),
324 36
            'int' => Type::int(),
325 36
            'float' => Type::float(),
326 36
            'string' => Type::string(),
327
328
            // Doctrine types
329 36
            'boolean' => Type::boolean(),
330 36
            'integer' => Type::int(),
331 36
            'smallint' => Type::int(),
332 36
            'bigint' => Type::int(),
333 36
            'decimal' => Type::string(),
334 36
            'text' => Type::string(),
335
        ];
336
337 36
        $this->types = $phpToGraphQLMapping;
338 36
        $this->registerInstance(new LogicalOperatorType());
339 36
        $this->registerInstance(new JoinTypeType());
340 36
        $this->registerInstance(new SortingOrderType());
341 36
    }
342
343
    /**
344
     * Throw an exception if the class name is not Doctrine entity
345
     *
346
     * @param string $className
347
     *
348
     * @throws \UnexpectedValueException
349
     */
350 30
    private function throwIfNotEntity(string $className): void
351
    {
352 30
        if (!$this->isEntity($className)) {
353 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.');
354
        }
355 29
    }
356
357 10
    public function createFilteredQueryBuilder(string $className, array $filter, array $sorting): QueryBuilder
358
    {
359 10
        return $this->filteredQueryBuilderFactory->create($className, $filter, $sorting);
360
    }
361
}
362