Completed
Push — master ( 949371...0dbb40 )
by Adrien
03:12
created

Types::isEntity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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

118
        /** @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...
119
120 87
        $this->initializeInternalTypes();
121 87
    }
122
123
    /**
124
     * Returns whether a type exists for the given key
125
     *
126
     * @param string $key
127
     *
128
     * @return bool
129
     */
130 1
    public function has(string $key): bool
131
    {
132 1
        return $this->customTypes && $this->customTypes->has($key) || array_key_exists($key, $this->types);
133
    }
134
135
    /**
136
     * Always return the same instance of `Type` for the given key
137
     *
138
     * It will first look for the type in the custom types container, and then
139
     * use automatically generated types. This allow for custom types to override
140
     * automatic ones.
141
     *
142
     * @param string $key the key the type was registered with (eg: "Post", "PostInput", "PostPartialInput" or "PostStatus")
143
     *
144
     * @return Type
145
     */
146 37
    public function get(string $key): Type
147
    {
148 37
        if ($this->customTypes && $this->customTypes->has($key)) {
149 23
            $t = $this->customTypes->get($key);
150 23
            $this->registerInstance($t);
151
152 23
            return $t;
153
        }
154
155 33
        if (array_key_exists($key, $this->types)) {
156 32
            return $this->types[$key];
157
        }
158
159 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 . '`.');
160
    }
161
162
    /**
163
     * Get a type from internal registry, and create it via the factory if needed
164
     *
165
     * @param string $className
166
     * @param string $typeName
167
     * @param AbstractTypeFactory $factory
168
     *
169
     * @return Type
170
     */
171 41
    private function getViaFactory(string $className, string $typeName, AbstractTypeFactory $factory): Type
172
    {
173 41
        $this->throwIfNotEntity($className);
174
175 40
        if (!isset($this->types[$typeName])) {
176 40
            $instance = $factory->create($className, $typeName);
177 40
            $this->registerInstance($instance);
178
        }
179
180 40
        return $this->types[$typeName];
181
    }
182
183
    /**
184
     * Returns an output type for the given entity
185
     *
186
     * All entity getter 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 ObjectType
192
     */
193 14
    public function getOutput(string $className): ObjectType
194
    {
195
        /** @var ObjectType $type */
196 14
        $type = $this->getViaFactory($className, Utils::getTypeName($className), $this->objectTypeFactory);
197
198 13
        return $type;
199
    }
200
201
    /**
202
     * Returns an input type for the given entity
203
     *
204
     * This would typically be used in mutations to create new entities.
205
     *
206
     * All entity setter methods will be exposed, unless specified otherwise
207
     * with annotations.
208
     *
209
     * @param string $className the class name of an entity (`Post::class`)
210
     *
211
     * @return InputObjectType
212
     */
213 5
    public function getInput(string $className): InputObjectType
214
    {
215
        /** @var InputObjectType $type */
216 5
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Input', $this->inputTypeFactory);
217
218 5
        return $type;
219
    }
220
221
    /**
222
     * Returns a partial input type for the given entity
223
     *
224
     * This would typically be used in mutations to update existing entities.
225
     *
226
     * All entity setter methods will be exposed, unless specified otherwise
227
     * with annotations. But they will all be marked as optional and without
228
     * default values. So this allow the API client to specify only some fields
229
     * to be updated, and not necessarily all of them at once.
230
     *
231
     * @param string $className the class name of an entity (`Post::class`)
232
     *
233
     * @return InputObjectType
234
     */
235 2
    public function getPartialInput(string $className): InputObjectType
236
    {
237
        /** @var InputObjectType $type */
238 2
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'PartialInput', $this->partialInputTypeFactory);
239
240 2
        return $type;
241
    }
242
243
    /**
244
     * Returns a filter input type for the given entity
245
     *
246
     * This would typically be used to filter queries.
247
     *
248
     * @param string $className the class name of an entity (`Post::class`)
249
     *
250
     * @return InputObjectType
251
     */
252 22
    public function getFilter(string $className): InputObjectType
253
    {
254
        /** @var InputObjectType $type */
255 22
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Filter', $this->filterTypeFactory);
256
257 22
        return $type;
258
    }
259
260
    /**
261
     * Returns a sorting input type for the given entity
262
     *
263
     * This would typically be used to sort queries.
264
     *
265
     * @param string $className the class name of an entity (`Post::class`)
266
     *
267
     * @return ListOfType
268
     */
269 3
    public function getSorting(string $className): ListOfType
270
    {
271
        /** @var InputObjectType $type */
272 3
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Sorting', $this->sortingTypeFactory);
273
274 3
        return Type::listOf(Type::nonNull($type));
275
    }
276
277
    /**
278
     * Returns a joinOn input type for the given entity
279
     *
280
     * This is for internal use only.
281
     *
282
     * @param string $className the class name of an entity (`Post::class`)
283
     *
284
     * @return InputObjectType
285
     */
286 2
    public function getJoinOn(string $className): InputObjectType
287
    {
288
        /** @var InputObjectType $type */
289 2
        $type = $this->getViaFactory($className, 'JoinOn' . Utils::getTypeName($className), $this->joinOnTypeFactory);
290
291 2
        return $type;
292
    }
293
294
    /**
295
     * Returns a joins input type for the given entity
296
     *
297
     * This is for internal use only.
298
     *
299
     * @param string $className the class name of an entity (`Post::class`)
300
     *
301
     * @return InputObjectType
302
     */
303 22
    public function getFilterJoins(string $className): InputObjectType
304
    {
305
        /** @var InputObjectType $type */
306 22
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'FilterJoins', $this->filterJoinsTypeFactory);
307
308 22
        return $type;
309
    }
310
311
    /**
312
     * Returns a condition input type for the given entity
313
     *
314
     * This is for internal use only.
315
     *
316
     * @param string $className the class name of an entity (`Post::class`)
317
     *
318
     * @return InputObjectType
319
     */
320 22
    public function getFilterGroupCondition(string $className): InputObjectType
321
    {
322
        /** @var InputObjectType $type */
323 22
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'FilterGroupCondition', $this->filterGroupConditionTypeFactory);
324
325 22
        return $type;
326
    }
327
328
    /**
329
     * Returns an special ID type for the given entity
330
     *
331
     * This is mostly useful for internal usage when a getter has an entity
332
     * as parameter. This type will automatically load the entity from DB, so
333
     * the resolve functions can use a real instance of entity instead of an ID.
334
     * But this can also be used to build your own schema and thus avoid
335
     * manually fetching objects from database for simple cases.
336
     *
337
     * @param string $className the class name of an entity (`Post::class`)
338
     *
339
     * @return EntityIDType
340
     */
341 3
    public function getId(string $className): EntityIDType
342
    {
343
        /** @var EntityIDType $type */
344 3
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'ID', $this->entityIDTypeFactory);
345
346 3
        return $type;
347
    }
348
349
    /**
350
     * Returns an operator input type
351
     *
352
     * This is for internal use only.
353
     *
354
     * @param string $className the class name of an operator (`EqualOperatorType::class`)
355
     * @param LeafType $type
356
     *
357
     * @throws Exception
358
     *
359
     * @return AbstractOperator
360
     */
361 18
    public function getOperator(string $className, LeafType $type): AbstractOperator
362
    {
363 18
        if (!is_a($className, AbstractOperator::class, true)) {
364 1
            throw new Exception('Expects a FQCN implementing `' . AbstractOperator::class . '`, but instead got: ' . $className);
365
        }
366
367 17
        $key = Utils::getOperatorTypeName($className, $type);
368
369 17
        if (!isset($this->types[$key])) {
370 17
            $instance = new $className($this, $type);
371 17
            $this->registerInstance($instance);
372
        }
373
374 17
        return $this->types[$key];
375
    }
376
377
    /**
378
     * Register the given type in our internal registry with its name
379
     *
380
     * This is for internal use only. You should declare custom types via the constructor, not this method.
381
     *
382
     * @param Type $instance
383
     */
384 87
    public function registerInstance(Type $instance): void
385
    {
386 87
        $this->types[$instance->name] = $instance;
387 87
    }
388
389
    /**
390
     * Checks if a className is a valid doctrine entity
391
     *
392
     * @param string $className
393
     *
394
     * @return bool
395
     */
396 41
    public function isEntity(string $className): bool
397
    {
398 41
        return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className);
399
    }
400
401
    /**
402
     * Initialize internal types for common needs
403
     */
404 87
    private function initializeInternalTypes(): void
405
    {
406
        $phpToGraphQLMapping = [
407
            // PHP types
408 87
            'id' => Type::id(),
409 87
            'bool' => Type::boolean(),
410 87
            'int' => Type::int(),
411 87
            'float' => Type::float(),
412 87
            'string' => Type::string(),
413
414
            // Doctrine types
415 87
            'boolean' => Type::boolean(),
416 87
            'integer' => Type::int(),
417 87
            'smallint' => Type::int(),
418 87
            'bigint' => Type::int(),
419 87
            'decimal' => Type::string(),
420 87
            'text' => Type::string(),
421
        ];
422
423 87
        $this->types = $phpToGraphQLMapping;
424 87
        $this->registerInstance(new LogicalOperatorType());
425 87
        $this->registerInstance(new JoinTypeType());
426 87
        $this->registerInstance(new SortingOrderType());
427 87
    }
428
429
    /**
430
     * Throw an exception if the class name is not Doctrine entity
431
     *
432
     * @param string $className
433
     *
434
     * @throws \UnexpectedValueException
435
     */
436 41
    private function throwIfNotEntity(string $className): void
437
    {
438 41
        if (!$this->isEntity($className)) {
439 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.');
440
        }
441 40
    }
442
443
    /**
444
     * Create and return a query builder that is filtered and sorted for the given entity
445
     *
446
     * Typical usage would be to call this method in your query resolver with the filter and sorting arguments directly
447
     * coming from GraphQL.
448
     *
449
     * You may apply further pagination according to your needs before executing the query.
450
     *
451
     * Filter and sorting arguments are assumed to be valid and complete as the validation should have happened when
452
     * parsing the GraphQL query.
453
     *
454
     * @param string $className
455
     * @param array $filter
456
     * @param array $sorting
457
     *
458
     * @return QueryBuilder
459
     */
460 18
    public function createFilteredQueryBuilder(string $className, array $filter, array $sorting): QueryBuilder
461
    {
462 18
        return $this->filteredQueryBuilderFactory->create($className, $filter, $sorting);
463
    }
464
}
465