Passed
Push — master ( ec51ae...10f5e3 )
by Adrien
12:18
created

Types::getOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
cc 1
nc 1
nop 1
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;
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);
0 ignored issues
show
Bug introduced by
The property objectTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
69 111
        $this->inputTypeFactory = new InputTypeFactory($this, $entityManager);
0 ignored issues
show
Bug introduced by
The property inputTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
70 111
        $this->partialInputTypeFactory = new PartialInputTypeFactory($this, $entityManager);
0 ignored issues
show
Bug introduced by
The property partialInputTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
71 111
        $this->sortingTypeFactory = new SortingTypeFactory($this, $entityManager);
0 ignored issues
show
Bug introduced by
The property sortingTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
72 111
        $this->entityIDTypeFactory = new EntityIDTypeFactory($this, $entityManager);
0 ignored issues
show
Bug introduced by
The property entityIDTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
73 111
        $this->filterGroupJoinTypeFactory = new FilterGroupJoinTypeFactory($this, $entityManager);
0 ignored issues
show
Bug introduced by
The property filterGroupJoinTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
74 111
        $this->filterGroupConditionTypeFactory = new FilterGroupConditionTypeFactory($this, $entityManager);
0 ignored issues
show
Bug introduced by
The property filterGroupConditionTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
75 111
        $this->filteredQueryBuilderFactory = new FilteredQueryBuilderFactory($this, $entityManager, $this->sortingTypeFactory);
0 ignored issues
show
Bug introduced by
The property filteredQueryBuilderFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
76 111
        $this->filterTypeFactory = new FilterTypeFactory($this, $entityManager, $this->filterGroupJoinTypeFactory, $this->filterGroupConditionTypeFactory);
0 ignored issues
show
Bug introduced by
The property filterTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
77 111
        $this->joinOnTypeFactory = new JoinOnTypeFactory($this, $entityManager, $this->filterGroupJoinTypeFactory, $this->filterGroupConditionTypeFactory);
0 ignored issues
show
Bug introduced by
The property joinOnTypeFactory is declared read-only in GraphQL\Doctrine\Types.
Loading history...
78
79 111
        $entityManager->getConfiguration()->newDefaultAnnotationDriver();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Configurati...faultAnnotationDriver() has been deprecated: Use {@see ORMSetup::createDefaultAnnotationDriver()} instead. ( Ignorable by Annotation )

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

79
        /** @scrutinizer ignore-deprecated */ $entityManager->getConfiguration()->newDefaultAnnotationDriver();

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...
80 111
        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. Annotations will be autoloaded in 2.0. ( Ignorable by Annotation )

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

80
        /** @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...
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 8
    public function getId(string $className): EntityIDType
210
    {
211
        /** @var EntityIDType $type */
212 8
        $type = $this->getViaFactory($className, Utils::getTypeName($className) . 'ID', $this->entityIDTypeFactory);
213
214 8
        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