Failed Conditions
Push — master ( 77b356...527c49 )
by Adrien
03:51 queued 01:07
created

Types::get()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 1
crap 4
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
final 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 62
    public function __construct(EntityManager $entityManager, ?ContainerInterface $customTypes = null)
94
    {
95 62
        $this->customTypes = $customTypes;
96 62
        $this->entityManager = $entityManager;
97 62
        $this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager);
98 62
        $this->inputTypeFactory = new InputTypeFactory($this, $entityManager);
99 62
        $this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager);
100 62
        $this->filterTypeFactory = new FilterTypeFactory($this, $entityManager);
101 62
        $this->sortingTypeFactory = new SortingTypeFactory($this, $entityManager);
102 62
        $this->entityIDTypeFactory = new EntityIDTypeFactory($this, $entityManager);
103 62
        $this->joinTypeFactory = new JoinTypeFactory($this, $entityManager);
104 62
        $this->filteredQueryBuilderFactory = new FilteredQueryBuilderFactory($this, $entityManager, $this->sortingTypeFactory);
105
106 62
        $entityManager->getConfiguration()->newDefaultAnnotationDriver();
107 62
        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 62
        $this->initializeInternalTypes();
110 62
    }
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 29
    public function get(string $key): Type
136
    {
137 29
        if ($this->customTypes && $this->customTypes->has($key)) {
138 19
            $t = $this->customTypes->get($key);
139 19
            $this->registerInstance($t);
140
141 19
            return $t;
142
        }
143
144 25
        if (array_key_exists($key, $this->types)) {
145 24
            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
    /**
152
     * Get a type from internal registry, and create it via the factory if needed
153
     *
154
     * @param string $className
155
     * @param string $typeName
156
     * @param AbstractTypeFactory $factory
157
     *
158
     * @return Type
159
     */
160 33
    private function getViaFactory(string $className, string $typeName, AbstractTypeFactory $factory): Type
161
    {
162 33
        $this->throwIfNotEntity($className);
163
164 32
        if (!isset($this->types[$typeName])) {
165 32
            $instance = $factory->create($className, $typeName);
166 32
            $this->registerInstance($instance);
167
        }
168
169 32
        return $this->types[$typeName];
170
    }
171
172
    /**
173
     * Returns an output type for the given entity
174
     *
175
     * All entity getter methods will be exposed, unless specified otherwise
176
     * with annotations.
177
     *
178
     * @param string $className the class name of an entity (`Post::class`)
179
     *
180
     * @return ObjectType
181
     */
182 14
    public function getOutput(string $className): ObjectType
183
    {
184
        /** @var ObjectType $type */
185 14
        $type = $this->getViaFactory($className, Utils::getTypeName($className), $this->objectTypeFactory);
186
187 13
        return $type;
188
    }
189
190
    /**
191
     * Returns an input type for the given entity
192
     *
193
     * This would typically be used in mutations to create new entities.
194
     *
195
     * All entity setter methods will be exposed, unless specified otherwise
196
     * with annotations.
197
     *
198
     * @param string $className the class name of an entity (`Post::class`)
199
     *
200
     * @return InputObjectType
201
     */
202 5
    public function getInput(string $className): InputObjectType
203
    {
204
        /** @var InputObjectType $type */
205 5
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Input', $this->inputTypeFactory);
206
207 5
        return $type;
208
    }
209
210
    /**
211
     * Returns a partial input type for the given entity
212
     *
213
     * This would typically be used in mutations to update existing entities.
214
     *
215
     * All entity setter methods will be exposed, unless specified otherwise
216
     * with annotations. But they will all be marked as optional and without
217
     * default values. So this allow the API client to specify only some fields
218
     * to be updated, and not necessarily all of them at once.
219
     *
220
     * @param string $className the class name of an entity (`Post::class`)
221
     *
222
     * @return InputObjectType
223
     */
224 2
    public function getPartialInput(string $className): InputObjectType
225
    {
226
        /** @var InputObjectType $type */
227 2
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'PartialInput', $this->partialInputTypeFactory);
228
229 2
        return $type;
230
    }
231
232
    /**
233
     * Returns a filter input type for the given entity
234
     *
235
     * This would typically be used to filter queries.
236
     *
237
     * @param string $className the class name of an entity (`Post::class`)
238
     *
239
     * @return InputObjectType
240
     */
241 15
    public function getFilter(string $className): InputObjectType
242
    {
243
        /** @var InputObjectType $type */
244 15
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Filter', $this->filterTypeFactory);
245
246 15
        return $type;
247
    }
248
249
    /**
250
     * Returns a sorting input type for the given entity
251
     *
252
     * This would typically be used to sort queries.
253
     *
254
     * @param string $className the class name of an entity (`Post::class`)
255
     *
256
     * @return ListOfType
257
     */
258 2
    public function getSorting(string $className): ListOfType
259
    {
260
        /** @var InputObjectType $type */
261 2
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Sorting', $this->sortingTypeFactory);
262
263 2
        return Type::listOf(Type::nonNull($type));
264
    }
265
266
    /**
267
     * Returns a join input type for the given entity
268
     *
269
     * This is for internal use only.
270
     *
271
     * @param string $className the class name of an entity (`Post::class`)
272
     *
273
     * @return InputObjectType
274
     */
275 14
    public function getJoin(string $className): InputObjectType
276
    {
277
        /** @var InputObjectType $type */
278 14
        $type = $this->getViaFactory($className, 'JoinOn' . Utils::getTypeName($className), $this->joinTypeFactory);
279
280 14
        return $type;
281
    }
282
283
    /**
284
     * Returns an special ID type for the given entity
285
     *
286
     * This is mostly useful for internal usage when a getter has an entity
287
     * as parameter. This type will automatically load the entity from DB, so
288
     * the resolve functions can use a real instance of entity instead of an ID.
289
     * But this can also be used to build your own schema and thus avoid
290
     * manually fetching objects from database for simple cases.
291
     *
292
     * @param string $className the class name of an entity (`Post::class`)
293
     *
294
     * @return EntityIDType
295
     */
296 3
    public function getId(string $className): EntityIDType
297
    {
298
        /** @var EntityIDType $type */
299 3
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'ID', $this->entityIDTypeFactory);
300
301 3
        return $type;
302
    }
303
304
    /**
305
     * Returns an operator input type
306
     *
307
     * This is for internal use only.
308
     *
309
     * @param string $className the class name of an operator (`EqualOperatorType::class`)
310
     * @param LeafType $type
311
     *
312
     * @throws Exception
313
     *
314
     * @return AbstractOperator
315
     */
316 9
    public function getOperator(string $className, LeafType $type): AbstractOperator
317
    {
318 9
        if (!is_a($className, AbstractOperator::class, true)) {
319 1
            throw new Exception('Expects a FQCN implementing `' . AbstractOperator::class . '`, but instead got: ' . $className);
320
        }
321
322 8
        $key = Utils::getOperatorTypeName($className, $type);
323
324 8
        if (!isset($this->types[$key])) {
325 8
            $instance = new $className($this, $type);
326 8
            $this->registerInstance($instance);
327
        }
328
329 8
        return $this->types[$key];
330
    }
331
332
    /**
333
     * Register the given type in our internal registry with its name
334
     *
335
     * This is for internal use only. You should declare custom types via the constructor, not this method.
336
     *
337
     * @param Type $instance
338
     */
339 62
    public function registerInstance(Type $instance): void
340
    {
341 62
        $this->types[$instance->name] = $instance;
342 62
    }
343
344
    /**
345
     * Checks if a className is a valid doctrine entity
346
     *
347
     * @param string $className
348
     *
349
     * @return bool
350
     */
351 33
    public function isEntity(string $className): bool
352
    {
353 33
        return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className);
354
    }
355
356
    /**
357
     * Initialize internal types for common needs
358
     */
359 62
    private function initializeInternalTypes(): void
360
    {
361
        $phpToGraphQLMapping = [
362
            // PHP types
363 62
            'id' => Type::id(),
364 62
            'bool' => Type::boolean(),
365 62
            'int' => Type::int(),
366 62
            'float' => Type::float(),
367 62
            'string' => Type::string(),
368
369
            // Doctrine types
370 62
            'boolean' => Type::boolean(),
371 62
            'integer' => Type::int(),
372 62
            'smallint' => Type::int(),
373 62
            'bigint' => Type::int(),
374 62
            'decimal' => Type::string(),
375 62
            'text' => Type::string(),
376
        ];
377
378 62
        $this->types = $phpToGraphQLMapping;
379 62
        $this->registerInstance(new LogicalOperatorType());
380 62
        $this->registerInstance(new JoinTypeType());
381 62
        $this->registerInstance(new SortingOrderType());
382 62
    }
383
384
    /**
385
     * Throw an exception if the class name is not Doctrine entity
386
     *
387
     * @param string $className
388
     *
389
     * @throws \UnexpectedValueException
390
     */
391 33
    private function throwIfNotEntity(string $className): void
392
    {
393 33
        if (!$this->isEntity($className)) {
394 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.');
395
        }
396 32
    }
397
398
    /**
399
     * Create and return a query builder that is filtered and sorted for the given entity
400
     *
401
     * Typical usage would be to call this method in your query resolver with the filter and sorting arguments directly
402
     * coming from GraphQL.
403
     *
404
     * You may apply further pagination according to your needs before executing the query.
405
     *
406
     * Filter and sorting arguments are assumed to be valid and complete as the validation should have happened when
407
     * parsing the GraphQL query.
408
     *
409
     * @param string $className
410
     * @param array $filter
411
     * @param array $sorting
412
     *
413
     * @return QueryBuilder
414
     */
415 12
    public function createFilteredQueryBuilder(string $className, array $filter, array $sorting): QueryBuilder
416
    {
417 12
        return $this->filteredQueryBuilderFactory->create($className, $filter, $sorting);
418
    }
419
}
420