GraphQLBootloader::init()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 6
nc 1
nop 4
dl 0
loc 15
ccs 8
cts 8
cp 1
crap 1
rs 10
c 1
b 0
f 1
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\ObjectFieldResolver;
12
use Andi\GraphQL\ObjectFieldResolver\ObjectFieldResolverInterface;
13
use Andi\GraphQL\Spiral\Command\ConfigCommand;
14
use Andi\GraphQL\Spiral\Common\SchemaWarmupper;
15
use Andi\GraphQL\Spiral\Config\GraphQLConfig;
16
use Andi\GraphQL\Spiral\Common\ValueResolver;
17
use Andi\GraphQL\Spiral\Listener\AdditionalFieldListener;
18
use Andi\GraphQL\Spiral\Listener\AttributedMutationFieldListener;
19
use Andi\GraphQL\Spiral\Listener\AttributedQueryFieldListener;
20
use Andi\GraphQL\Spiral\Listener\AttributedTypeLoaderListener;
21
use Andi\GraphQL\Spiral\Listener\MutationFieldListener;
22
use Andi\GraphQL\Spiral\Listener\QueryFieldListener;
23
use Andi\GraphQL\Spiral\Listener\TypeLoaderListener;
24
use Andi\GraphQL\Spiral\Middleware\GraphQLMiddleware;
25
use Andi\GraphQL\Type\MutationType;
26
use Andi\GraphQL\Type\QueryType;
27
use Andi\GraphQL\TypeRegistry;
28
use Andi\GraphQL\TypeRegistryInterface;
29
use Andi\GraphQL\TypeResolver\TypeResolver;
30
use Andi\GraphQL\TypeResolver\TypeResolverInterface;
31
use GraphQL\Server\ServerConfig;
32
use GraphQL\Server\StandardServer;
33
use GraphQL\Type\Definition as Webonyx;
34
use GraphQL\Type\Schema;
35
use GraphQL\Type\SchemaConfig;
36
use Psr\Container\ContainerInterface;
37
use Spiral\Boot\AbstractKernel;
38
use Spiral\Boot\Bootloader\Bootloader;
39
use Spiral\Boot\EnvironmentInterface;
40
use Spiral\Bootloader\Http\HttpBootloader;
41
use Spiral\Config\ConfiguratorInterface;
42
use Spiral\Console\Bootloader\ConsoleBootloader;
43
use Spiral\Core\InvokerInterface;
44
use Spiral\Core\ScopeInterface;
45
use Spiral\Tokenizer\TokenizerListenerRegistryInterface;
46
47
final class GraphQLBootloader extends Bootloader
48
{
49
    protected const SINGLETONS = [
50
        StandardServer::class => [self::class, 'buildStandardServer'],
51
        ServerConfig::class => [self::class, 'buildServerConfig'],
52
        Schema::class => [self::class, 'buildSchema'],
53
        SchemaConfig::class => [self::class, 'buildSchemaConfig'],
54
55
        TypeRegistryInterface::class => TypeRegistry::class,
56
        TypeRegistry::class => [self::class, 'buildTypeRegistry'],
57
58
        TypeResolverInterface::class => TypeResolver::class,
59
        ObjectFieldResolverInterface::class => ObjectFieldResolver::class,
60
        InputObjectFieldResolverInterface::class => InputObjectFieldResolver::class,
61
        ArgumentResolverInterface::class => ArgumentResolver::class,
62
63
        TypeResolver::class => [self::class, 'buildTypeResolver'],
64
        ObjectFieldResolver::class => [self::class, 'buildObjectFieldResolver'],
65
        InputObjectFieldResolver::class => [self::class, 'buildInputObjectFieldResolver'],
66
        ArgumentResolver::class => [self::class, 'buildArgumentResolver'],
67
    ];
68
69 7
    public function __construct(
70
        private readonly ConfiguratorInterface $configurator,
71
    ) {
72 7
    }
73
74 7
    public function init(
75
        AbstractKernel $kernel,
76
        EnvironmentInterface $env,
77
        HttpBootloader $http,
78
        ConsoleBootloader $console,
79
    ): void {
80 7
        $this->configurator->setDefaults(GraphQLConfig::CONFIG, [
81 7
            'url' => $env->get('GRAPHQL_URL', '/api/graphql'),
82 7
        ]);
83
84 7
        $http->addMiddleware(GraphQLMiddleware::class);
85
86 7
        $console->addCommand(ConfigCommand::class);
87 7
        $kernel->bootstrapped(static function (Schema $schema): void {
88 7
            SchemaWarmupper::warmup($schema);
89 7
        });
90
    }
91
92 7
    public function boot(
93
        GraphQLConfig $config,
94
        TypeRegistryInterface $typeRegistry,
95
        TypeResolverInterface $typeResolver,
96
        TokenizerListenerRegistryInterface $listenerRegistry,
97
        AttributedTypeLoaderListener $attributedTypeLoaderListener,
98
        TypeLoaderListener $typeLoaderListener,
99
        QueryFieldListener $queryFieldListener,
100
        MutationFieldListener $mutationFieldListener,
101
        AttributedQueryFieldListener $attributedQueryFieldListener,
102
        AttributedMutationFieldListener $attributedMutationFieldListener,
103
        AdditionalFieldListener $additionalFieldListener,
104
    ): void {
105 7
        $this->registerQueryType($config->getQueryType(), $typeRegistry, $typeResolver);
106 7
        $this->registerMutationType($config->getMutationType(), $typeRegistry, $typeResolver);
107 7
        $this->registerAdditionalTypes($config->getAdditionalTypes(), $typeRegistry, $typeResolver);
108
109 7
        $listenerRegistry->addListener($attributedTypeLoaderListener);
110 7
        $listenerRegistry->addListener($typeLoaderListener);
111 7
        $listenerRegistry->addListener($queryFieldListener);
112 7
        $listenerRegistry->addListener($mutationFieldListener);
113 7
        $listenerRegistry->addListener($attributedQueryFieldListener);
114 7
        $listenerRegistry->addListener($attributedMutationFieldListener);
115 7
        $listenerRegistry->addListener($additionalFieldListener);
116
    }
117
118 7
    private function registerQueryType(
119
        string $class,
120
        TypeRegistryInterface $typeRegistry,
121
        TypeResolverInterface $typeResolver,
122
    ): void {
123 7
        if ($typeRegistry->has($class)) {
124
            return;
125
        }
126
127 7
        if (GraphQLConfig::DEFAULT_QUERY_TYPE === $class) {
128 7
            $class = QueryType::class;
129
        }
130
131 7
        $queryType = $typeResolver->resolve($class);
132 7
        \assert($queryType instanceof Webonyx\NamedType);
133 7
        $typeRegistry->register($queryType, $class);
134
    }
135
136 7
    private function registerMutationType(
137
        ?string $class,
138
        TypeRegistryInterface $typeRegistry,
139
        TypeResolverInterface $typeResolver,
140
    ): void {
141 7
        if (null === $class || $typeRegistry->has($class)) {
142 6
            return;
143
        }
144
145 1
        if (GraphQLConfig::DEFAULT_MUTATION_TYPE === $class) {
146 1
            $class = MutationType::class;
147
        }
148
149 1
        $mutationType = $typeResolver->resolve($class);
150 1
        \assert($mutationType instanceof Webonyx\NamedType);
151 1
        $typeRegistry->register($mutationType, $class);
152
    }
153
154 7
    private function registerAdditionalTypes(
155
        array $types,
156
        TypeRegistryInterface $typeRegistry,
157
        TypeResolverInterface $typeResolver,
158
    ): void {
159 7
        foreach ($types as $name => $aliases) {
160 1
            $aliases = (array) $aliases;
161 1
            if (\is_int($name)) {
162 1
                $name = \array_shift($aliases);
163
            }
164
165 1
            $type = $typeResolver->resolve($name);
166 1
            \assert($type instanceof Webonyx\NamedType);
167 1
            $typeRegistry->register($type, $name, ...$aliases);
168
        }
169
    }
170
171 1
    private function buildStandardServer(ServerConfig $config): StandardServer
172
    {
173 1
        return new StandardServer($config);
174
    }
175
176 3
    private function buildServerConfig(
177
        Schema $schema,
178
        GraphQLConfig $config,
179
        ContainerInterface $container,
180
        ScopeInterface $scope,
181
        InvokerInterface $invoker,
182
    ): ServerConfig {
183 3
        $serverConfig = (new ServerConfig())->setSchema($schema);
184
185 3
        if ($rootValueName = $config->getRootValue()) {
186 2
            $rootValue = $container->get($rootValueName);
187 2
            $rootValueFn = \is_callable($rootValue)
188 1
                ? $rootValue
189 1
                : static fn () => $rootValue;
190
191 2
            $serverConfig->setRootValue(new ValueResolver($scope, $invoker, $rootValueFn));
192
        }
193
194 3
        if ($contextName = $config->getContext()) {
195 2
            $context = $container->get($contextName);
196 2
            $contextFn = \is_callable($context)
197 1
                ? $context
198 1
                : static fn () => $context;
199
200 2
            $serverConfig->setContext(new ValueResolver($scope, $invoker, $contextFn));
201
        }
202
203 3
        $serverConfig->setDebugFlag($config->getDebugFlag());
204
205 3
        return $serverConfig;
206
    }
207
208 7
    private function buildSchema(SchemaConfig $config): Schema
209
    {
210 7
        return new Schema($config);
211
    }
212
213 7
    private function buildSchemaConfig(
214
        GraphQLConfig $config,
215
        TypeRegistryInterface $typeRegistry,
216
    ): SchemaConfig {
217 7
        $schemaConfig = new SchemaConfig();
218 7
        $schemaConfig->setTypeLoader($typeRegistry->get(...));
219 7
        $schemaConfig->setTypes($typeRegistry->getTypes(...));
220
221 7
        $schemaConfig->setQuery($typeRegistry->get($config->getQueryType()));
222
223 7
        $mutationType = $config->getMutationType();
224 7
        if (null !== $mutationType && $typeRegistry->has($mutationType)) {
225 1
            $schemaConfig->setMutation($typeRegistry->get($mutationType));
226
        }
227
228 7
        return $schemaConfig;
229
    }
230
231 7
    private function buildTypeRegistry(): TypeRegistry
232
    {
233 7
        return new TypeRegistry();
234
    }
235
236 7
    private function buildTypeResolver(
237
        ContainerInterface $container,
238
        GraphQLConfig $config,
239
    ): TypeResolver {
240 7
        $typeResolver = new TypeResolver();
241
242 7
        foreach ($config->getTypeResolverMiddlewares() as $name => $priority) {
243 7
            $typeResolver->pipe($container->get($name), $priority);
244
        }
245
246 7
        return $typeResolver;
247
    }
248
249 7
    private function buildObjectFieldResolver(
250
        ContainerInterface $container,
251
        GraphQLConfig $config,
252
    ): ObjectFieldResolver {
253 7
        $objectFieldResolver = new ObjectFieldResolver();
254
255 7
        foreach ($config->getObjectFieldResolverMiddlewares() as $name => $priority) {
256 7
            $objectFieldResolver->pipe($container->get($name), $priority);
257
        }
258
259 7
        return $objectFieldResolver;
260
    }
261
262 7
    private function buildInputObjectFieldResolver(
263
        ContainerInterface $container,
264
        GraphQLConfig $config,
265
    ): InputObjectFieldResolver {
266 7
        $inputObjectFieldResolver = new InputObjectFieldResolver();
267
268 7
        foreach ($config->getInputObjectFieldResolverMiddlewares() as $name => $priority) {
269 7
            $inputObjectFieldResolver->pipe($container->get($name), $priority);
270
        }
271
272 7
        return $inputObjectFieldResolver;
273
    }
274
275 7
    private function buildArgumentResolver(
276
        ContainerInterface $container,
277
        GraphQLConfig $config,
278
    ): ArgumentResolver {
279 7
        $argumentResolver = new ArgumentResolver();
280
281 7
        foreach ($config->getArgumentResolverMiddlewares() as $name => $priority) {
282 7
            $argumentResolver->pipe($container->get($name), $priority);
283
        }
284
285 7
        return $argumentResolver;
286
    }
287
}
288