Passed
Push — master ( ae602f...d7a1a9 )
by butschster
13:09 queued 04:03
created

AbstractKernel::bootstrapped()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 2
nc 2
nop 1
crap 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 410
    protected function __construct(
69
        protected readonly Container $container,
70
        protected readonly ExceptionHandlerInterface $exceptionHandler,
71
        protected readonly BootloadManagerInterface $bootloader,
72
        array $directories
73
    ) {
74 410
        $container->bindSingleton(ExceptionHandlerInterface::class, $exceptionHandler);
75 410
        $container->bindSingleton(ExceptionRendererInterface::class, $exceptionHandler);
76 410
        $container->bindSingleton(ExceptionReporterInterface::class, $exceptionHandler);
77 410
        $container->bindSingleton(ExceptionHandler::class, $exceptionHandler);
78 410
        $container->bindSingleton(KernelInterface::class, $this);
79
80 410
        $container->bindSingleton(self::class, $this);
81 410
        $container->bindSingleton(static::class, $this);
82
83 410
        $container->bindSingleton(
84 410
            DirectoriesInterface::class,
85 410
            new Directories($this->mapDirectories($directories))
86 410
        );
87
88 409
        $this->finalizer = new Finalizer();
89 409
        $container->bindSingleton(FinalizerInterface::class, $this->finalizer);
90
    }
91
92
    /**
93
     * Terminate the application.
94
     */
95 5
    public function __destruct()
96
    {
97 5
        $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 410
    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 410
        $exceptionHandler ??= ExceptionHandler::class;
115
116 410
        if (\is_string($exceptionHandler)) {
117 410
            $exceptionHandler = $container->make($exceptionHandler);
118
        }
119
120 410
        if ($handleErrors) {
121 39
            $exceptionHandler->register();
122
        }
123
124 410
        if (!$container->has(InitializerInterface::class)) {
125 409
            $container->bind(InitializerInterface::class, Initializer::class);
126
        }
127 410
        if (!$container->has(InvokerStrategyInterface::class)) {
128 409
            $container->bind(InvokerStrategyInterface::class, DefaultInvokerStrategy::class);
129
        }
130
131 410
        if ($bootloadManager instanceof Autowire) {
132 1
            $bootloadManager = $bootloadManager->resolve($container);
133
        }
134 410
        $bootloadManager ??= $container->make(StrategyBasedBootloadManager::class);
135 410
        \assert($bootloadManager instanceof BootloadManagerInterface);
136 410
        $container->bind(BootloadManagerInterface::class, $bootloadManager);
137
138 410
        return new static(
139 410
            $container,
140 410
            $exceptionHandler,
141 410
            $bootloadManager,
142 410
            $directories
143 410
        );
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 404
    public function run(?EnvironmentInterface $environment = null): ?self
159
    {
160 404
        $environment ??= new Environment();
161 404
        $this->container->bindSingleton(EnvironmentInterface::class, $environment);
162
163
        try {
164
            // will protect any against env overwrite action
165 404
            $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 404
                [EnvironmentInterface::class => $environment],
167 404
                function (): void {
168 404
                    $this->bootloader->bootload($this->defineSystemBootloaders());
169 404
                    $this->fireCallbacks($this->runningCallbacks);
170
171 404
                    $this->bootload();
172 404
                    $this->bootstrap();
173
174 404
                    $this->fireCallbacks($this->bootstrappedCallbacks);
175 404
                }
176 404
            );
177
        } catch (\Throwable $e) {
178
            $this->exceptionHandler->handleGlobalException($e);
179
180
            return null;
181
        }
182
183 404
        $this->getEventDispatcher()?->dispatch(new Bootstrapped($this));
184
185 404
        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 329
    public function booting(Closure ...$callbacks): void
212
    {
213 329
        foreach ($callbacks as $callback) {
214 329
            $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 404
    public function booted(Closure ...$callbacks): void
227
    {
228 404
        foreach ($callbacks as $callback) {
229 404
            $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 367
    public function bootstrapped(Closure ...$callbacks): void
243
    {
244 367
        foreach ($callbacks as $callback) {
245 367
            $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 370
    public function addDispatcher(DispatcherInterface $dispatcher): self
254
    {
255 370
        $this->dispatchers[] = $dispatcher;
256
257 370
        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 404
    protected function defineSystemBootloaders(): array
305
    {
306 404
        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 404
    protected function fireCallbacks(array &$callbacks): void
323
    {
324 404
        if ($callbacks === []) {
325 404
            return;
326
        }
327
328
        do {
329 404
            $this->container->invoke(\current($callbacks));
330 404
        } while (\next($callbacks));
331
332 404
        $callbacks = [];
333
    }
334
335
    /**
336
     * Bootload all registered classes using BootloadManager.
337
     */
338 404
    private function bootload(): void
339
    {
340 404
        $self = $this;
341 404
        $this->bootloader->bootload(
342 404
            $this->defineBootloaders(),
343 404
            [
344 404
                static function () use ($self): void {
345 404
                    $self->fireCallbacks($self->bootingCallbacks);
346 404
                },
347 404
            ]
348 404
        );
349
350 404
        $this->fireCallbacks($this->bootedCallbacks);
351
    }
352
353 404
    private function getEventDispatcher(): ?EventDispatcherInterface
354
    {
355 404
        return $this->container->has(EventDispatcherInterface::class)
356 2
            ? $this->container->get(EventDispatcherInterface::class)
357 404
            : null;
358
    }
359
}
360