Passed
Push — master ( 6714d8...c618ac )
by butschster
05:26 queued 16s
created

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

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

282
                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...
283 3
                    [DispatcherInterface::class => $dispatcher],
284 3
                    static function () use ($dispatcher, $eventDispatcher): mixed {
285 3
                        $eventDispatcher?->dispatch(new DispatcherFound($dispatcher));
286 3
                        return $dispatcher->serve();
287 3
                    }
288 3
                );
289
            }
290
        }
291
292 2
        $eventDispatcher?->dispatch(new DispatcherNotFound());
293
294 2
        throw new BootException('Unable to locate active dispatcher.');
295
    }
296
297
    /**
298
     * Bootstrap application. Must be executed before serve method.
299
     */
300
    abstract protected function bootstrap(): void;
301
302
    /**
303
     * Normalizes directory list and adds all required aliases.
304
     */
305
    abstract protected function mapDirectories(array $directories): array;
306
307
    /**
308
     * Get list of defined system bootloaders
309
     *
310
     * @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...
311
     */
312 419
    protected function defineSystemBootloaders(): array
313
    {
314 419
        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

314
        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...
315
    }
316
317
    /**
318
     * Get list of defined kernel bootloaders
319
     *
320
     * @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...
321
     */
322 107
    protected function defineBootloaders(): array
323
    {
324 107
        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

324
        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...
325
    }
326
327
    /**
328
     * Call the registered booting callbacks.
329
     */
330 419
    protected function fireCallbacks(array &$callbacks): void
331
    {
332 419
        if ($callbacks === []) {
333 419
            return;
334
        }
335
336
        do {
337 417
            $this->container->invoke(\current($callbacks));
338 417
        } while (\next($callbacks));
339
340 417
        $callbacks = [];
341
    }
342
343
    /**
344
     * Bootload all registered classes using BootloadManager.
345
     */
346 419
    private function bootload(array $bootloaders = []): void
347
    {
348 419
        $self = $this;
349 419
        $this->bootloader->bootload(
350 419
            $bootloaders,
351 419
            [
352 419
                static function () use ($self): void {
353 419
                    $self->fireCallbacks($self->bootingCallbacks);
354 419
                },
355 419
            ]
356 419
        );
357
358 419
        $this->fireCallbacks($this->bootedCallbacks);
359
    }
360
361 419
    private function getEventDispatcher(): ?EventDispatcherInterface
362
    {
363 419
        return $this->container->has(EventDispatcherInterface::class)
364 2
            ? $this->container->get(EventDispatcherInterface::class)
365 419
            : null;
366
    }
367
368 419
    private function initBootloaderRegistry(): BootloaderRegistryInterface
369
    {
370 419
        return new BootloaderRegistry($this->defineSystemBootloaders(), $this->defineBootloaders());
371
    }
372
}
373