Passed
Pull Request — master (#15)
by
unknown
03:03
created

GraphQLBootloader::buildSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Andi\GraphQL\Spiral\Bootloader;
6
7
use Andi\GraphQL\ArgumentResolver\ArgumentResolver;
8
use Andi\GraphQL\ArgumentResolver\ArgumentResolverInterface;
9
use Andi\GraphQL\InputObjectFieldResolver\InputObjectFieldResolver;
10
use Andi\GraphQL\InputObjectFieldResolver\InputObjectFieldResolverInterface;
11
use Andi\GraphQL\ObjectFieldResolver\Middleware\AbstractObjectFieldByReflectionMethodMiddleware;
12
use Andi\GraphQL\ObjectFieldResolver\Middleware\AbstractOuterObjectFieldByReflectionMethodMiddleware;
13
use Andi\GraphQL\ObjectFieldResolver\Middleware\AdditionalFieldByReflectionMethodMiddleware;
14
use Andi\GraphQL\ObjectFieldResolver\Middleware\InterfaceFieldByReflectionMethodMiddleware;
15
use Andi\GraphQL\ObjectFieldResolver\Middleware\MutationFieldByReflectionMethodMiddleware;
16
use Andi\GraphQL\ObjectFieldResolver\Middleware\ObjectFieldByReflectionMethodMiddleware;
17
use Andi\GraphQL\ObjectFieldResolver\Middleware\QueryFieldByReflectionMethodMiddleware;
18
use Andi\GraphQL\ObjectFieldResolver\ObjectFieldResolver;
19
use Andi\GraphQL\ObjectFieldResolver\ObjectFieldResolverInterface;
20
use Andi\GraphQL\Spiral\Command\ConfigCommand;
21
use Andi\GraphQL\Spiral\Common\SchemaWarmupper;
22
use Andi\GraphQL\Spiral\Config\GraphQLConfig;
23
use Andi\GraphQL\Spiral\Common\ValueResolver;
24
use Andi\GraphQL\Spiral\Listener\AdditionalFieldListener;
25
use Andi\GraphQL\Spiral\Listener\AttributedMutationFieldListener;
26
use Andi\GraphQL\Spiral\Listener\AttributedQueryFieldListener;
27
use Andi\GraphQL\Spiral\Listener\AttributedTypeLoaderListener;
28
use Andi\GraphQL\Spiral\Listener\MutationFieldListener;
29
use Andi\GraphQL\Spiral\Listener\QueryFieldListener;
30
use Andi\GraphQL\Spiral\Listener\TypeLoaderListener;
31
use Andi\GraphQL\Spiral\Middleware\GraphQLMiddleware;
32
use Andi\GraphQL\Type\MutationType;
33
use Andi\GraphQL\Type\QueryType;
34
use Andi\GraphQL\TypeRegistry;
35
use Andi\GraphQL\TypeRegistryInterface;
36
use Andi\GraphQL\TypeResolver\TypeResolver;
37
use Andi\GraphQL\TypeResolver\TypeResolverInterface;
38
use GraphQL\Server\ServerConfig;
39
use GraphQL\Server\StandardServer;
40
use GraphQL\Type\Definition as Webonyx;
41
use GraphQL\Type\Schema;
42
use GraphQL\Type\SchemaConfig;
43
use Psr\Container\ContainerInterface;
44
use Spiral\Attributes\ReaderInterface;
45
use Spiral\Boot\AbstractKernel;
46
use Spiral\Boot\Bootloader\Bootloader;
47
use Spiral\Boot\EnvironmentInterface;
48
use Spiral\Bootloader\Http\HttpBootloader;
49
use Spiral\Config\ConfiguratorInterface;
50
use Spiral\Console\Bootloader\ConsoleBootloader;
51
use Spiral\Core\Internal\Proxy;
52
use Spiral\Core\InvokerInterface;
53
use Spiral\Core\ScopeInterface;
54
use Spiral\Tokenizer\TokenizerListenerRegistryInterface;
55
56
final class GraphQLBootloader extends Bootloader
57
{
58
    protected const SINGLETONS = [
59
        StandardServer::class => [self::class, 'buildStandardServer'],
60
        ServerConfig::class => [self::class, 'buildServerConfig'],
61
        Schema::class => [self::class, 'buildSchema'],
62
        SchemaConfig::class => [self::class, 'buildSchemaConfig'],
63
64
        TypeRegistryInterface::class => TypeRegistry::class,
65
        TypeRegistry::class => [self::class, 'buildTypeRegistry'],
66
67
        TypeResolverInterface::class => TypeResolver::class,
68
        ObjectFieldResolverInterface::class => ObjectFieldResolver::class,
69
        InputObjectFieldResolverInterface::class => InputObjectFieldResolver::class,
70
        ArgumentResolverInterface::class => ArgumentResolver::class,
71
72
        TypeResolver::class => [self::class, 'buildTypeResolver'],
73
        ObjectFieldResolver::class => [self::class, 'buildObjectFieldResolver'],
74
        InputObjectFieldResolver::class => [self::class, 'buildInputObjectFieldResolver'],
75
        ArgumentResolver::class => [self::class, 'buildArgumentResolver'],
76
    ];
77
78
    protected const BINDINGS = [
79
        QueryFieldByReflectionMethodMiddleware::class => [self::class, 'buildQueryFieldByReflectionMethodMiddleware'],
80
        MutationFieldByReflectionMethodMiddleware::class => [self::class, 'buildMutationFieldByReflectionMethodMiddleware'],
81
        AdditionalFieldByReflectionMethodMiddleware::class => [self::class, 'buildAdditionalFieldByReflectionMethodMiddleware'],
82
83
        ObjectFieldByReflectionMethodMiddleware::class => [self::class, 'buildObjectFieldByReflectionMethodMiddleware'],
84
        InterfaceFieldByReflectionMethodMiddleware::class => [self::class, 'buildInterfaceFieldByReflectionMethodMiddleware'],
85
    ];
86
87 7
    public function __construct(
88
        private readonly ConfiguratorInterface $configurator,
89
    ) {
90 7
    }
91
92 7
    public function init(
93
        AbstractKernel $kernel,
94
        EnvironmentInterface $env,
95
        HttpBootloader $http,
96
        ConsoleBootloader $console,
97
    ): void {
98 7
        $this->configurator->setDefaults(GraphQLConfig::CONFIG, [
99 7
            'url' => $env->get('GRAPHQL_URL', '/api/graphql'),
100 7
        ]);
101
102 7
        $http->addMiddleware(GraphQLMiddleware::class);
103
104 7
        $console->addCommand(ConfigCommand::class);
105 7
        $kernel->bootstrapped(static function (Schema $schema): void {
106 7
            SchemaWarmupper::warmup($schema);
107 7
        });
108
    }
109
110 7
    public function boot(
111
        GraphQLConfig $config,
112
        TypeRegistryInterface $typeRegistry,
113
        TypeResolverInterface $typeResolver,
114
        TokenizerListenerRegistryInterface $listenerRegistry,
115
        AttributedTypeLoaderListener $attributedTypeLoaderListener,
116
        TypeLoaderListener $typeLoaderListener,
117
        QueryFieldListener $queryFieldListener,
118
        MutationFieldListener $mutationFieldListener,
119
        AttributedQueryFieldListener $attributedQueryFieldListener,
120
        AttributedMutationFieldListener $attributedMutationFieldListener,
121
        AdditionalFieldListener $additionalFieldListener,
122
    ): void {
123 7
        $this->registerQueryType($config->getQueryType(), $typeRegistry, $typeResolver);
124 7
        $this->registerMutationType($config->getMutationType(), $typeRegistry, $typeResolver);
125 7
        $this->registerAdditionalTypes($config->getAdditionalTypes(), $typeRegistry, $typeResolver);
126
127 7
        $listenerRegistry->addListener($attributedTypeLoaderListener);
128 7
        $listenerRegistry->addListener($typeLoaderListener);
129 7
        $listenerRegistry->addListener($queryFieldListener);
130 7
        $listenerRegistry->addListener($mutationFieldListener);
131 7
        $listenerRegistry->addListener($attributedQueryFieldListener);
132 7
        $listenerRegistry->addListener($attributedMutationFieldListener);
133 7
        $listenerRegistry->addListener($additionalFieldListener);
134
    }
135
136 7
    private function registerQueryType(
137
        string $class,
138
        TypeRegistryInterface $typeRegistry,
139
        TypeResolverInterface $typeResolver,
140
    ): void {
141 7
        if ($typeRegistry->has($class)) {
142
            return;
143
        }
144
145 7
        if (GraphQLConfig::DEFAULT_QUERY_TYPE === $class) {
146 7
            $class = QueryType::class;
147
        }
148
149 7
        $queryType = $typeResolver->resolve($class);
150 7
        \assert($queryType instanceof Webonyx\NamedType);
151 7
        $typeRegistry->register($queryType, $class);
152
    }
153
154 7
    private function registerMutationType(
155
        ?string $class,
156
        TypeRegistryInterface $typeRegistry,
157
        TypeResolverInterface $typeResolver,
158
    ): void {
159 7
        if (null === $class || $typeRegistry->has($class)) {
160 6
            return;
161
        }
162
163 1
        if (GraphQLConfig::DEFAULT_MUTATION_TYPE === $class) {
164 1
            $class = MutationType::class;
165
        }
166
167 1
        $mutationType = $typeResolver->resolve($class);
168 1
        \assert($mutationType instanceof Webonyx\NamedType);
169 1
        $typeRegistry->register($mutationType, $class);
170
    }
171
172 7
    private function registerAdditionalTypes(
173
        array $types,
174
        TypeRegistryInterface $typeRegistry,
175
        TypeResolverInterface $typeResolver,
176
    ): void {
177 7
        foreach ($types as $name => $aliases) {
178 1
            $aliases = (array)$aliases;
179 1
            if (\is_int($name)) {
180 1
                $name = \array_shift($aliases);
181
            }
182
183 1
            $type = $typeResolver->resolve($name);
184 1
            \assert($type instanceof Webonyx\NamedType);
185 1
            $typeRegistry->register($type, $name, ...$aliases);
186
        }
187
    }
188
189 1
    private function buildStandardServer(ServerConfig $config): StandardServer
190
    {
191 1
        return new StandardServer($config);
192
    }
193
194 3
    private function buildServerConfig(
195
        Schema $schema,
196
        GraphQLConfig $config,
197
        ContainerInterface $container,
198
    ): ServerConfig {
199 3
        $serverConfig = (new ServerConfig())->setSchema($schema);
200
201 3
        $scope = Proxy::create(
202 3
            new \ReflectionClass(ScopeInterface::class),
203 3
            null,
204 3
            new \Spiral\Core\Attribute\Proxy(),
205 3
        );
206 3
        $invoker = Proxy::create(
207 3
            new \ReflectionClass(InvokerInterface::class),
208 3
            null,
209 3
            new \Spiral\Core\Attribute\Proxy(),
210 3
        );
211
212 3
        if ($rootValueName = $config->getRootValue()) {
213 2
            $rootValue = $container->get($rootValueName);
214 2
            $rootValueFn = \is_callable($rootValue)
215 1
                ? $rootValue
216 1
                : static fn() => $rootValue;
217
218 2
            $serverConfig->setRootValue(new ValueResolver($scope, $invoker, $rootValueFn));
219
        }
220
221 3
        if ($contextName = $config->getContext()) {
222 2
            $context = $container->get($contextName);
223 2
            $contextFn = \is_callable($context)
224 1
                ? $context
225 1
                : static fn() => $context;
226
227 2
            $serverConfig->setContext(new ValueResolver($scope, $invoker, $contextFn));
228
        }
229
230 3
        $serverConfig->setDebugFlag($config->getDebugFlag());
231
232 3
        return $serverConfig;
233
    }
234
235 7
    private function buildSchema(SchemaConfig $config): Schema
236
    {
237 7
        return new Schema($config);
238
    }
239
240 7
    private function buildSchemaConfig(
241
        GraphQLConfig $config,
242
        TypeRegistryInterface $typeRegistry,
243
    ): SchemaConfig {
244 7
        $schemaConfig = new SchemaConfig();
245 7
        $schemaConfig->setTypeLoader($typeRegistry->get(...));
246 7
        $schemaConfig->setTypes($typeRegistry->getTypes(...));
247
248 7
        $schemaConfig->setQuery($typeRegistry->get($config->getQueryType()));
249
250 7
        $mutationType = $config->getMutationType();
251 7
        if (null !== $mutationType && $typeRegistry->has($mutationType)) {
252 1
            $schemaConfig->setMutation($typeRegistry->get($mutationType));
253
        }
254
255 7
        return $schemaConfig;
256
    }
257
258 7
    private function buildTypeRegistry(): TypeRegistry
259
    {
260 7
        return new TypeRegistry();
261
    }
262
263 7
    private function buildTypeResolver(
264
        ContainerInterface $container,
265
        GraphQLConfig $config,
266
    ): TypeResolver {
267 7
        $typeResolver = new TypeResolver();
268
269 7
        foreach ($config->getTypeResolverMiddlewares() as $name => $priority) {
270 7
            $typeResolver->pipe($container->get($name), $priority);
271
        }
272
273 7
        return $typeResolver;
274
    }
275
276 7
    private function buildObjectFieldResolver(
277
        ContainerInterface $container,
278
        GraphQLConfig $config,
279
    ): ObjectFieldResolver {
280 7
        $objectFieldResolver = new ObjectFieldResolver();
281
282 7
        foreach ($config->getObjectFieldResolverMiddlewares() as $name => $priority) {
283 7
            $objectFieldResolver->pipe($container->get($name), $priority);
284
        }
285
286 7
        return $objectFieldResolver;
287
    }
288
289 7
    private function buildInputObjectFieldResolver(
290
        ContainerInterface $container,
291
        GraphQLConfig $config,
292
    ): InputObjectFieldResolver {
293 7
        $inputObjectFieldResolver = new InputObjectFieldResolver();
294
295 7
        foreach ($config->getInputObjectFieldResolverMiddlewares() as $name => $priority) {
296 7
            $inputObjectFieldResolver->pipe($container->get($name), $priority);
297
        }
298
299 7
        return $inputObjectFieldResolver;
300
    }
301
302 7
    private function buildArgumentResolver(
303
        ContainerInterface $container,
304
        GraphQLConfig $config,
305
    ): ArgumentResolver {
306 7
        $argumentResolver = new ArgumentResolver();
307
308 7
        foreach ($config->getArgumentResolverMiddlewares() as $name => $priority) {
309 7
            $argumentResolver->pipe($container->get($name), $priority);
310
        }
311
312 7
        return $argumentResolver;
313
    }
314
315 7
    private function buildQueryFieldByReflectionMethodMiddleware(
316
        ReaderInterface $reader,
317
        TypeRegistryInterface $typeRegistry,
318
        ArgumentResolverInterface $argumentResolver,
319
    ): QueryFieldByReflectionMethodMiddleware {
320 7
        return $this->buildAbstractOuterObjectFieldByReflectionMethodMiddleware(
321 7
            QueryFieldByReflectionMethodMiddleware::class,
322 7
            $reader,
323 7
            $typeRegistry,
324 7
            $argumentResolver,
325 7
        );
326
    }
327
328 7
    private function buildMutationFieldByReflectionMethodMiddleware(
329
        ReaderInterface $reader,
330
        TypeRegistryInterface $typeRegistry,
331
        ArgumentResolverInterface $argumentResolver,
332
    ): MutationFieldByReflectionMethodMiddleware {
333 7
        return $this->buildAbstractOuterObjectFieldByReflectionMethodMiddleware(
334 7
            MutationFieldByReflectionMethodMiddleware::class,
335 7
            $reader,
336 7
            $typeRegistry,
337 7
            $argumentResolver,
338 7
        );
339
    }
340
341 7
    private function buildAdditionalFieldByReflectionMethodMiddleware(
342
        ReaderInterface $reader,
343
        TypeRegistryInterface $typeRegistry,
344
        ArgumentResolverInterface $argumentResolver,
345
    ): AdditionalFieldByReflectionMethodMiddleware {
346 7
        return $this->buildAbstractOuterObjectFieldByReflectionMethodMiddleware(
347 7
            AdditionalFieldByReflectionMethodMiddleware::class,
348 7
            $reader,
349 7
            $typeRegistry,
350 7
            $argumentResolver,
351 7
        );
352
    }
353
354
    /**
355
     * @param class-string<AbstractOuterObjectFieldByReflectionMethodMiddleware> $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<AbstractOut...ectionMethodMiddleware> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<AbstractOuterObjectFieldByReflectionMethodMiddleware>.
Loading history...
356
     */
357 7
    private function buildAbstractOuterObjectFieldByReflectionMethodMiddleware(
358
        string $class,
359
        ReaderInterface $reader,
360
        TypeRegistryInterface $typeRegistry,
361
        ArgumentResolverInterface $argumentResolver,
362
    ): AbstractOuterObjectFieldByReflectionMethodMiddleware {
363 7
        $scope = Proxy::create(
364 7
            new \ReflectionClass(ScopeInterface::class),
365 7
            null,
366 7
            new \Spiral\Core\Attribute\Proxy(),
367 7
        );
368 7
        $invoker = Proxy::create(
369 7
            new \ReflectionClass(InvokerInterface::class),
370 7
            null,
371 7
            new \Spiral\Core\Attribute\Proxy(),
372 7
        );
373
374 7
        return new $class(
375 7
            $reader,
376 7
            $typeRegistry,
377 7
            $argumentResolver,
378 7
            $scope,
379 7
            $invoker,
380 7
        );
381
    }
382
383 7
    private function buildObjectFieldByReflectionMethodMiddleware(
384
        ReaderInterface $reader,
385
        TypeRegistryInterface $typeRegistry,
386
        ArgumentResolverInterface $argumentResolver,
387
    ): ObjectFieldByReflectionMethodMiddleware {
388 7
        return $this->buildAbstractObjectFieldByReflectionMethodMiddleware(
389 7
            ObjectFieldByReflectionMethodMiddleware::class,
390 7
            $reader,
391 7
            $typeRegistry,
392 7
            $argumentResolver,
393 7
        );
394
    }
395
396
397 7
    private function buildInterfaceFieldByReflectionMethodMiddleware(
398
        ReaderInterface $reader,
399
        TypeRegistryInterface $typeRegistry,
400
        ArgumentResolverInterface $argumentResolver,
401
    ): InterfaceFieldByReflectionMethodMiddleware {
402 7
        return $this->buildAbstractObjectFieldByReflectionMethodMiddleware(
403 7
            InterfaceFieldByReflectionMethodMiddleware::class,
404 7
            $reader,
405 7
            $typeRegistry,
406 7
            $argumentResolver,
407 7
        );
408
    }
409
410
    /**
411
     * @param class-string<AbstractObjectFieldByReflectionMethodMiddleware> $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<AbstractObj...ectionMethodMiddleware> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<AbstractObjectFieldByReflectionMethodMiddleware>.
Loading history...
412
     */
413 7
    private function buildAbstractObjectFieldByReflectionMethodMiddleware(
414
        string $class,
415
        ReaderInterface $reader,
416
        TypeRegistryInterface $typeRegistry,
417
        ArgumentResolverInterface $argumentResolver,
418
    ): AbstractObjectFieldByReflectionMethodMiddleware {
419 7
        $scope = Proxy::create(
420 7
            new \ReflectionClass(ScopeInterface::class),
421 7
            null,
422 7
            new \Spiral\Core\Attribute\Proxy(),
423 7
        );
424 7
        $invoker = Proxy::create(
425 7
            new \ReflectionClass(InvokerInterface::class),
426 7
            null,
427 7
            new \Spiral\Core\Attribute\Proxy(),
428 7
        );
429
430 7
        return new $class(
431 7
            $reader,
432 7
            $typeRegistry,
433 7
            $argumentResolver,
434 7
            $scope,
435 7
            $invoker,
436 7
        );
437
    }
438
}
439