Failed Conditions
Pull Request — master (#62)
by Adrien
06:30 queued 04:20
created

Types::initializeInternalTypes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 23
rs 9.7333
c 0
b 0
f 0
ccs 16
cts 16
cp 1
cc 1
nc 1
nop 0
crap 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 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\FilterGroupJoinTypeFactory;
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
use UnexpectedValueException;
33
34
/**
35
 * Registry of types to manage all GraphQL types.
36
 *
37
 * This is the entry point for the library.
38
 */
39
final class Types implements TypesInterface
40
{
41
    /**
42
     * @var array mapping of type name to type instances
43
     */
44
    private array $types = [];
45
46
    private readonly ObjectTypeFactory $objectTypeFactory;
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting T_VARIABLE on line 46 at column 21
Loading history...
47
48
    private readonly InputTypeFactory $inputTypeFactory;
49
50
    private readonly PartialInputTypeFactory $partialInputTypeFactory;
51
52
    private readonly FilterTypeFactory $filterTypeFactory;
53
54
    private readonly FilteredQueryBuilderFactory $filteredQueryBuilderFactory;
55
56
    private readonly SortingTypeFactory $sortingTypeFactory;
57
58
    private readonly EntityIDTypeFactory $entityIDTypeFactory;
59
60
    private readonly JoinOnTypeFactory $joinOnTypeFactory;
61
62
    private readonly FilterGroupJoinTypeFactory $filterGroupJoinTypeFactory;
63
64
    private readonly FilterGroupConditionTypeFactory $filterGroupConditionTypeFactory;
65
66 111
    public function __construct(private readonly EntityManager $entityManager, private readonly ?ContainerInterface $customTypes = null)
67
    {
68 111
        $this->objectTypeFactory = new ObjectTypeFactory($this, $entityManager);
69 111
        $this->inputTypeFactory = new InputTypeFactory($this, $entityManager);
70 111
        $this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager);
71 111
        $this->sortingTypeFactory = new SortingTypeFactory($this, $entityManager);
72 111
        $this->entityIDTypeFactory = new EntityIDTypeFactory($this, $entityManager);
73 111
        $this->filterGroupJoinTypeFactory = new FilterGroupJoinTypeFactory($this, $entityManager);
74 111
        $this->filterGroupConditionTypeFactory = new FilterGroupConditionTypeFactory($this, $entityManager);
75 111
        $this->filteredQueryBuilderFactory = new FilteredQueryBuilderFactory($this, $entityManager, $this->sortingTypeFactory);
76 111
        $this->filterTypeFactory = new FilterTypeFactory($this, $entityManager, $this->filterGroupJoinTypeFactory, $this->filterGroupConditionTypeFactory);
77 111
        $this->joinOnTypeFactory = new JoinOnTypeFactory($this, $entityManager, $this->filterGroupJoinTypeFactory, $this->filterGroupConditionTypeFactory);
78
79 111
        $entityManager->getConfiguration()->newDefaultAnnotationDriver();
80 111
        AnnotationRegistry::registerLoader('class_exists');
81
82 111
        $this->initializeInternalTypes();
83
    }
84
85 1
    public function has(string $key): bool
86
    {
87 1
        return $this->customTypes && $this->customTypes->has($key) || array_key_exists($key, $this->types);
88
    }
89
90 47
    public function get(string $key): Type
91
    {
92 47
        if ($this->customTypes && $this->customTypes->has($key)) {
93
            /** @var Type $t */
94 30
            $t = $this->customTypes->get($key);
95 30
            $this->registerInstance($t);
96
97 30
            return $t;
98
        }
99
100 39
        if (array_key_exists($key, $this->types)) {
101 38
            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
     * Get a type from internal registry, and create it via the factory if needed.
109
     *
110
     * @param class-string $className
111
     */
112 52
    private function getViaFactory(string $className, string $typeName, AbstractTypeFactory $factory): Type
113
    {
114 52
        $this->throwIfNotEntity($className);
115
116 51
        if (!isset($this->types[$typeName])) {
117 51
            $instance = $factory->create($className, $typeName);
118 51
            $this->registerInstance($instance);
119
        }
120
121 51
        return $this->types[$typeName];
122
    }
123
124 20
    public function getOutput(string $className): ObjectType
125
    {
126
        /** @var ObjectType $type */
127 20
        $type = $this->getViaFactory($className, Utils::getTypeName($className), $this->objectTypeFactory);
128
129 19
        return $type;
130
    }
131
132 7
    public function getInput(string $className): InputObjectType
133
    {
134
        /** @var InputObjectType $type */
135 7
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Input', $this->inputTypeFactory);
136
137 7
        return $type;
138
    }
139
140 2
    public function getPartialInput(string $className): InputObjectType
141
    {
142
        /** @var InputObjectType $type */
143 2
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'PartialInput', $this->partialInputTypeFactory);
144
145 2
        return $type;
146
    }
147
148 25
    public function getFilter(string $className): InputObjectType
149
    {
150
        /** @var InputObjectType $type */
151 25
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Filter', $this->filterTypeFactory);
152
153 25
        return $type;
154
    }
155
156 4
    public function getSorting(string $className): ListOfType
157
    {
158
        /** @var InputObjectType $type */
159 4
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'Sorting', $this->sortingTypeFactory);
160
161 4
        return Type::listOf(Type::nonNull($type));
162
    }
163
164
    /**
165
     * Returns a joinOn input type for the given entity.
166
     *
167
     * This is for internal use only.
168
     *
169
     * @param class-string $className the class name of an entity (`Post::class`)
170
     */
171 2
    public function getJoinOn(string $className): InputObjectType
172
    {
173
        /** @var InputObjectType $type */
174 2
        $type = $this->getViaFactory($className, 'JoinOn' . Utils::getTypeName($className), $this->joinOnTypeFactory);
175
176 2
        return $type;
177
    }
178
179
    /**
180
     * Returns a joins input type for the given entity.
181
     *
182
     * This is for internal use only.
183
     *
184
     * @param class-string $className the class name of an entity (`Post::class`)
185
     */
186 22
    public function getFilterGroupJoin(string $className): InputObjectType
187
    {
188
        /** @var InputObjectType $type */
189 22
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'FilterGroupJoin', $this->filterGroupJoinTypeFactory);
190
191 22
        return $type;
192
    }
193
194
    /**
195
     * Returns a condition input type for the given entity.
196
     *
197
     * This is for internal use only.
198
     *
199
     * @param class-string $className the class name of an entity (`Post::class`)
200
     */
201 25
    public function getFilterGroupCondition(string $className): InputObjectType
202
    {
203
        /** @var InputObjectType $type */
204 25
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'FilterGroupCondition', $this->filterGroupConditionTypeFactory);
205
206 25
        return $type;
207
    }
208
209 7
    public function getId(string $className): EntityIDType
210
    {
211
        /** @var EntityIDType $type */
212 7
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'ID', $this->entityIDTypeFactory);
213
214 7
        return $type;
215
    }
216
217
    /**
218
     * Returns an operator input type.
219
     *
220
     * This is for internal use only.
221
     *
222
     * @param class-string $className the class name of an operator (`EqualOperatorType::class`)
223
     */
224 19
    public function getOperator(string $className, LeafType $type): AbstractOperator
225
    {
226 19
        if (!is_a($className, AbstractOperator::class, true)) {
227 1
            throw new Exception('Expects a FQCN implementing `' . AbstractOperator::class . '`, but instead got: ' . $className);
228
        }
229
230 18
        $key = Utils::getOperatorTypeName($className, $type);
231
232 18
        if (!isset($this->types[$key])) {
233 18
            $instance = new $className($this, $type);
234 18
            $this->registerInstance($instance);
235
        }
236
237 18
        return $this->types[$key];
238
    }
239
240
    /**
241
     * Register the given type in our internal registry with its name.
242
     *
243
     * This is for internal use only. You should declare custom types via the constructor, not this method.
244
     */
245 111
    public function registerInstance(Type $instance): void
246
    {
247 111
        $this->types[$instance->name] = $instance;
248
    }
249
250
    /**
251
     * Checks if a className is a valid doctrine entity.
252
     */
253 52
    public function isEntity(string $className): bool
254
    {
255 52
        return class_exists($className) && !$this->entityManager->getMetadataFactory()->isTransient($className);
256
    }
257
258
    /**
259
     * Initialize internal types for common needs.
260
     */
261 111
    private function initializeInternalTypes(): void
262
    {
263 111
        $phpToGraphQLMapping = [
264
            // PHP types
265 111
            'id' => Type::id(),
266 111
            'bool' => Type::boolean(),
267 111
            'int' => Type::int(),
268 111
            'float' => Type::float(),
269 111
            'string' => Type::string(),
270
271
            // Doctrine types
272 111
            'boolean' => Type::boolean(),
273 111
            'integer' => Type::int(),
274 111
            'smallint' => Type::int(),
275 111
            'bigint' => Type::int(),
276 111
            'decimal' => Type::string(),
277 111
            'text' => Type::string(),
278
        ];
279
280 111
        $this->types = $phpToGraphQLMapping;
281 111
        $this->registerInstance(new LogicalOperatorType());
282 111
        $this->registerInstance(new JoinTypeType());
283 111
        $this->registerInstance(new SortingOrderType());
284
    }
285
286
    /**
287
     * Throw an exception if the class name is not Doctrine entity.
288
     */
289 52
    private function throwIfNotEntity(string $className): void
290
    {
291 52
        if (!$this->isEntity($className)) {
292 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.');
293
        }
294
    }
295
296 20
    public function createFilteredQueryBuilder(string $className, array $filter, array $sorting): QueryBuilder
297
    {
298 20
        return $this->filteredQueryBuilderFactory->create($className, $filter, $sorting);
299
    }
300
}
301