Passed
Pull Request — master (#963)
by butschster
09:56
created

AbstractKernel   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Test Coverage

Coverage 97.25%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 30
eloc 95
dl 0
loc 327
ccs 106
cts 109
cp 0.9725
rs 10
c 5
b 1
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 1
A create() 0 36 6
A bootstrapped() 0 4 2
A running() 0 4 2
A booted() 0 4 2
A bootload() 0 13 1
A fireCallbacks() 0 11 3
A defineSystemBootloaders() 0 3 1
A addDispatcher() 0 5 1
A getEventDispatcher() 0 5 2
A booting() 0 4 2
A serve() 0 20 3
A __destruct() 0 3 1
A defineBootloaders() 0 3 1
A run() 0 28 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Boot;
6
7
use Closure;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Spiral\Boot\Bootloader\CoreBootloader;
10
use Spiral\Boot\BootloadManager\StrategyBasedBootloadManager;
11
use Spiral\Boot\BootloadManager\DefaultInvokerStrategy;
12
use Spiral\Boot\BootloadManager\Initializer;
13
use Spiral\Boot\BootloadManager\InitializerInterface;
14
use Spiral\Boot\BootloadManager\InvokerStrategyInterface;
15
use Spiral\Boot\Event\Bootstrapped;
16
use Spiral\Boot\Event\DispatcherFound;
17
use Spiral\Boot\Event\DispatcherNotFound;
18
use Spiral\Boot\Event\Serving;
19
use Spiral\Boot\Exception\BootException;
20
use Spiral\Core\Container\Autowire;
21
use Spiral\Core\Container;
22
use Spiral\Exceptions\ExceptionHandler;
23
use Spiral\Exceptions\ExceptionHandlerInterface;
24
use Spiral\Exceptions\ExceptionRendererInterface;
25
use Spiral\Exceptions\ExceptionReporterInterface;
26
27
/**
28
 * Core responsible for application initialization, bootloading of all required services,
29
 * environment and directory management, exception handling.
30
 */
31
abstract class AbstractKernel implements KernelInterface
32
{
33
    /**
34
     * Defines list of bootloaders to be used for core initialisation and all system components.
35
     *
36
     * @deprecated Use {@see defineSystemBootloaders()} method instead. Will be removed in v4.0
37
     */
38
    protected const SYSTEM = [CoreBootloader::class];
39
40
    /**
41
     * List of bootloaders to be called on application initialization (before `serve` method).
42
     * This constant must be redefined in child application.
43
     *
44
     * @deprecated Use {@see defineBootloaders()} method instead. Will be removed in v4.0
45
     */
46
    protected const LOAD = [];
47
48
    protected FinalizerInterface $finalizer;
49
50
    /** @var DispatcherInterface[] */
51
    protected array $dispatchers = [];
52
53
    /** @var array<Closure> */
54
    private array $runningCallbacks = [];
55
56
    /** @var array<Closure> */
57
    private array $bootingCallbacks = [];
58
59
    /** @var array<Closure> */
60
    private array $bootedCallbacks = [];
61
62
    /** @var array<Closure>  */
63
    private array $bootstrappedCallbacks = [];
64
65
    /**
66
     * @throws \Throwable
67
     */
68 404
    protected function __construct(
69
        protected readonly Container $container,
70
        protected readonly ExceptionHandlerInterface $exceptionHandler,
71
        protected readonly BootloadManagerInterface $bootloader,
72
        array $directories
73
    ) {
74 404
        $container->bindSingleton(ExceptionHandlerInterface::class, $exceptionHandler);
75 404
        $container->bindSingleton(ExceptionRendererInterface::class, $exceptionHandler);
76 404
        $container->bindSingleton(ExceptionReporterInterface::class, $exceptionHandler);
77 404
        $container->bindSingleton(ExceptionHandler::class, $exceptionHandler);
78 404
        $container->bindSingleton(KernelInterface::class, $this);
79
80 404
        $container->bindSingleton(self::class, $this);
81 404
        $container->bindSingleton(static::class, $this);
82
83 404
        $container->bindSingleton(
84 404
            DirectoriesInterface::class,
85 404
            new Directories($this->mapDirectories($directories))
86 404
        );
87
88 403
        $this->finalizer = new Finalizer();
89 403
        $container->bindSingleton(FinalizerInterface::class, $this->finalizer);
90
    }
91
92
    /**
93
     * Terminate the application.
94
     */
95 6
    public function __destruct()
96
    {
97 6
        $this->finalizer->finalize(true);
98
    }
99
100
    /**
101
     * Create an application instance.
102
     *
103
     * @param class-string<ExceptionHandlerInterface>|ExceptionHandlerInterface $exceptionHandler
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<ExceptionHa...ceptionHandlerInterface at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<ExceptionHandlerInterface>|ExceptionHandlerInterface.
Loading history...
104
     *
105
     * @throws \Throwable
106
     */
107 404
    final public static function create(
108
        array $directories,
109
        bool $handleErrors = true,
110
        ExceptionHandlerInterface|string|null $exceptionHandler = null,
111
        Container $container = new Container(),
112
        BootloadManagerInterface|Autowire|null $bootloadManager = null
113
    ): static {
114 404
        $exceptionHandler ??= ExceptionHandler::class;
115
116 404
        if (\is_string($exceptionHandler)) {
117 404
            $exceptionHandler = $container->make($exceptionHandler);
118
        }
119
120 404
        if ($handleErrors) {
121 39
            $exceptionHandler->register();
122
        }
123
124 404
        if (!$container->has(InitializerInterface::class)) {
125 403
            $container->bind(InitializerInterface::class, Initializer::class);
126
        }
127 404
        if (!$container->has(InvokerStrategyInterface::class)) {
128 403
            $container->bind(InvokerStrategyInterface::class, DefaultInvokerStrategy::class);
129
        }
130
131 404
        if ($bootloadManager instanceof Autowire) {
132 1
            $bootloadManager = $bootloadManager->resolve($container);
133
        }
134 404
        $bootloadManager ??= $container->make(StrategyBasedBootloadManager::class);
135 404
        \assert($bootloadManager instanceof BootloadManagerInterface);
136 404
        $container->bind(BootloadManagerInterface::class, $bootloadManager);
137
138 404
        return new static(
139 404
            $container,
140 404
            $exceptionHandler,
141 404
            $bootloadManager,
142 404
            $directories
143 404
        );
144
    }
145
146
    /**
147
     * Run the application with given Environment
148
     *
149
     * $app = App::create([...]);
150
     * $app->booting(...);
151
     * $app->booted(...);
152
     * $app->bootstrapped(...);
153
     * $app->run(new Environment([
154
     *     'APP_ENV' => 'production'
155
     * ]));
156
     *
157
     */
158 398
    public function run(?EnvironmentInterface $environment = null): ?self
159
    {
160 398
        $environment ??= new Environment();
161 398
        $this->container->bindSingleton(EnvironmentInterface::class, $environment);
162
163
        try {
164
            // will protect any against env overwrite action
165 398
            $this->container->runScope(
0 ignored issues
show
Deprecated Code introduced by
The function Spiral\Core\Container::runScope() has been deprecated: use {@see runScoped()} instead. ( Ignorable by Annotation )

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

165
            /** @scrutinizer ignore-deprecated */ $this->container->runScope(

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...
166 398
                [EnvironmentInterface::class => $environment],
167 398
                function (): void {
168 398
                    $this->bootloader->bootload($this->defineSystemBootloaders());
169 398
                    $this->fireCallbacks($this->runningCallbacks);
170
171 398
                    $this->bootload();
172 398
                    $this->bootstrap();
173
174 398
                    $this->fireCallbacks($this->bootstrappedCallbacks);
175 398
                }
176 398
            );
177
        } catch (\Throwable $e) {
178
            $this->exceptionHandler->handleGlobalException($e);
179
180
            return null;
181
        }
182
183 398
        $this->getEventDispatcher()?->dispatch(new Bootstrapped($this));
184
185 398
        return $this;
186
    }
187
188
    /**
189
     * Register a new callback, that will be fired before framework run.
190
     * (After SYSTEM bootloaders, before bootloaders in LOAD section)
191
     *
192
     * $kernel->running(static function(KernelInterface $kernel) {
193
     *     $kernel->getContainer()->...
194
     * });
195
     */
196 1
    public function running(Closure ...$callbacks): void
197
    {
198 1
        foreach ($callbacks as $callback) {
199 1
            $this->runningCallbacks[] = $callback;
200
        }
201
    }
202
203
    /**
204
     * Register a new callback, that will be fired before framework bootloaders boot.
205
     * (Before all framework bootloaders in LOAD section will be booted)
206
     *
207
     * $kernel->booting(static function(KernelInterface $kernel) {
208
     *     $kernel->getContainer()->...
209
     * });
210
     */
211 323
    public function booting(Closure ...$callbacks): void
212
    {
213 323
        foreach ($callbacks as $callback) {
214 323
            $this->bootingCallbacks[] = $callback;
215
        }
216
    }
217
218
    /**
219
     * Register a new callback, that will be fired after framework bootloaders booted.
220
     * (After booting all framework bootloaders in LOAD section)
221
     *
222
     * $kernel->booted(static function(KernelInterface $kernel) {
223
     *     $kernel->getContainer()->...
224
     * });
225
     */
226 398
    public function booted(Closure ...$callbacks): void
227
    {
228 398
        foreach ($callbacks as $callback) {
229 398
            $this->bootedCallbacks[] = $callback;
230
        }
231
    }
232
233
234
    /**
235
     * Register a new callback, that will be fired after framework bootstrapped.
236
     * (Before serving)
237
     *
238
     * $kernel->bootstrapped(static function(KernelInterface $kernel) {
239
     *     $kernel->getContainer()->...
240
     * });
241
     */
242 361
    public function bootstrapped(Closure ...$callbacks): void
243
    {
244 361
        foreach ($callbacks as $callback) {
245 361
            $this->bootstrappedCallbacks[] = $callback;
246
        }
247
    }
248
249
    /**
250
     * Add new dispatcher. This method must only be called before method `serve`
251
     * will be invoked.
252
     */
253 364
    public function addDispatcher(DispatcherInterface $dispatcher): self
254
    {
255 364
        $this->dispatchers[] = $dispatcher;
256
257 364
        return $this;
258
    }
259
260
    /**
261
     * Start application and serve user requests using selected dispatcher or throw
262
     * an exception.
263
     *
264
     * @throws BootException
265
     * @throws \Throwable
266
     */
267 5
    public function serve(): mixed
268
    {
269 5
        $eventDispatcher = $this->getEventDispatcher();
270 5
        $eventDispatcher?->dispatch(new Serving());
271
272 5
        foreach ($this->dispatchers as $dispatcher) {
273 3
            if ($dispatcher->canServe()) {
274 3
                return $this->container->runScope(
0 ignored issues
show
Deprecated Code introduced by
The function Spiral\Core\Container::runScope() has been deprecated: use {@see runScoped()} instead. ( Ignorable by Annotation )

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

274
                return /** @scrutinizer ignore-deprecated */ $this->container->runScope(

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...
275 3
                    [DispatcherInterface::class => $dispatcher],
276 3
                    static function () use ($dispatcher, $eventDispatcher): mixed {
277 3
                        $eventDispatcher?->dispatch(new DispatcherFound($dispatcher));
278 3
                        return $dispatcher->serve();
279 3
                    }
280 3
                );
281
            }
282
        }
283
284 2
        $eventDispatcher?->dispatch(new DispatcherNotFound());
285
286 2
        throw new BootException('Unable to locate active dispatcher.');
287
    }
288
289
    /**
290
     * Bootstrap application. Must be executed before serve method.
291
     */
292
    abstract protected function bootstrap(): void;
293
294
    /**
295
     * Normalizes directory list and adds all required aliases.
296
     */
297
    abstract protected function mapDirectories(array $directories): array;
298
299
    /**
300
     * Get list of defined system bootloaders
301
     *
302
     * @return array<int, class-string>|array<class-string, array<non-empty-string, mixed>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<int, class-string>...n-empty-string, mixed>> at position 4 could not be parsed: Unknown type name 'class-string' at position 4 in array<int, class-string>|array<class-string, array<non-empty-string, mixed>>.
Loading history...
303
     */
304 398
    protected function defineSystemBootloaders(): array
305
    {
306 398
        return static::SYSTEM;
0 ignored issues
show
Deprecated Code introduced by
The constant Spiral\Boot\AbstractKernel::SYSTEM has been deprecated: Use {@see defineSystemBootloaders()} method instead. Will be removed in v4.0 ( Ignorable by Annotation )

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

306
        return /** @scrutinizer ignore-deprecated */ static::SYSTEM;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
307
    }
308
309
    /**
310
     * Get list of defined kernel bootloaders
311
     *
312
     * @return array<int, class-string>|array<class-string, array<non-empty-string, mixed>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<int, class-string>...n-empty-string, mixed>> at position 4 could not be parsed: Unknown type name 'class-string' at position 4 in array<int, class-string>|array<class-string, array<non-empty-string, mixed>>.
Loading history...
313
     */
314 102
    protected function defineBootloaders(): array
315
    {
316 102
        return static::LOAD;
0 ignored issues
show
Deprecated Code introduced by
The constant Spiral\Boot\AbstractKernel::LOAD has been deprecated: Use {@see defineBootloaders()} method instead. Will be removed in v4.0 ( Ignorable by Annotation )

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

316
        return /** @scrutinizer ignore-deprecated */ static::LOAD;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
317
    }
318
319
    /**
320
     * Call the registered booting callbacks.
321
     */
322 398
    protected function fireCallbacks(array &$callbacks): void
323
    {
324 398
        if ($callbacks === []) {
325 398
            return;
326
        }
327
328
        do {
329 398
            $this->container->invoke(\current($callbacks));
330 398
        } while (\next($callbacks));
331
332 398
        $callbacks = [];
333
    }
334
335
    /**
336
     * Bootload all registered classes using BootloadManager.
337
     */
338 398
    private function bootload(): void
339
    {
340 398
        $self = $this;
341 398
        $this->bootloader->bootload(
342 398
            $this->defineBootloaders(),
343 398
            [
344 398
                static function () use ($self): void {
345 398
                    $self->fireCallbacks($self->bootingCallbacks);
346 398
                },
347 398
            ]
348 398
        );
349
350 398
        $this->fireCallbacks($this->bootedCallbacks);
351
    }
352
353 398
    private function getEventDispatcher(): ?EventDispatcherInterface
354
    {
355 398
        return $this->container->has(EventDispatcherInterface::class)
356 2
            ? $this->container->get(EventDispatcherInterface::class)
357 398
            : null;
358
    }
359
}
360