Bootloaders are the central piece in Spiral Framework and your application. These objects are responsible for Container configuration, default configuration, etc.
Bootloaders are only executed once while loading your application. Since the app will stay in memory for long - you can add as much code to your bootloaders as you want. It will not cause any performance effect on runtime.
You can create simple Bootloader by extending Spiral\Boot\Bootloader\Bootloader
class:
namespace App\Bootloader;
use Spiral\Boot\Bootloader\Bootloader;
class MyBootloader extends Bootloader
{
}
Every Bootloader must be activated in your application kernel. Add the class reference into LOAD
or APP
lists of
your App\App
class:
namespace App;
use Spiral\Framework\Kernel;
use App\Bootloader\RoutesBootloader;
use App\Bootloader\LoggingBootloader;
use App\Bootloader\MyBootloader;
class App extends Kernel
{
protected const LOAD = [
// ...
];
protected const APP = [
RoutesBootloader::class,
LoggingBootloader::class,
MyBootloader::class
];
}
Currently, your Bootloader doesn't do anything. A little bit later, we will add some functionality to it.
Bootloaders provide two methods init
and boot
that are executed when the application is initialized.
This method is executed first. It's recommended to set default values for configuration files. Modify configuration
files using special bootloader methods. Execute other logic that doesn't require reading configuration files
and doesn't depend on code execution in the init
and boot
methods of other bootloaders.
In this method, you can add initialization callbacks, configure container bindings if this does not require
access to the application configuration.
use Spiral\Boot\AbstractKernel;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Boot\EnvironmentInterface;
use Spiral\Config\ConfiguratorInterface;
use Spiral\Queue\Config\QueueConfig;
final class QueueBootloader extends Bootloader
{
public function __construct(
private readonly ConfiguratorInterface $config
) {
}
public function init(
EnvironmentInterface $env,
AbstractKernel $kernel
): void {
$this->config->setDefaults(
QueueConfig::CONFIG,
[
'default' => $env->get('QUEUE_CONNECTION', 'sync'),
// ...
]
);
$kernel->booting(function () {
// ...
});
}
}
This method is executed after executing method init
in all bootloaders. It can be used if you need the result
of the init
methods in all bootloaders. For example, compiled configuration files.
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Config\ConfiguratorInterface;
use Spiral\Session\Config\SessionConfig;
final class SessionBootloader extends Bootloader
{
public function boot(
ConfiguratorInterface $config,
CookiesBootloader $cookies
): void {
$session = $config->getConfig(SessionConfig::CONFIG);
$cookies->whitelistCookie($session['cookie']);
}
}
Note
APP
bootloader namespace is always loaded afterLOAD
, keep domain-specific bootloaders in it.
The most common use-case of bootloaders is to configure a DI container, for example, we might want to bind multiple implementations to their interfaces or construct some service.
We can use the method init
or boot
for these purposes. The method supports method injection, so we can request any services we
need:
namespace App\Bootloader;
use Spiral\Core\Container;
use App\MyClassInterface;
use App\MyClass;
use App\MyService;
class MyBootloader extends Bootloader
{
public function boot(Container $container): void
{
$container->bind(MyClassInterface::class, MyClass::class);
$container->bindSingleton(MyService::class, function(MyClass $myClass) {
return new MyService($myClass);
});
}
}
Bootloaders provide the ability to simplify container binding definition using constants BINDINGS
and SINGLETONS
.
namespace App\Bootloader;
use Spiral\Core\Container;
use App\MyClassInterface;
use App\MyClass;
use App\MyService;
class MyBootloader extends Bootloader
{
const BINDINGS = [
MyInterface::class => MyClass::class
];
public function boot(Container $container): void
{
$container->bindSingleton(MyService::class, function(MyClass $myClass) {
return new MyService($myClass);
});
}
}
You can also replace factory closures with factory methods:
namespace App\Bootloader;
use App\MyClassInterface;
use App\MyClass;
use App\MyService;
class MyBootloader extends Bootloader
{
const BINDINGS = [
MyInterface::class => MyClass::class
];
const SINGLETONS = [
MyService::class => [self::class, 'myService']
];
protected function myService(MyClass $myClass): MyService
{
return new MyService($myClass);
}
}
Another common use case of bootloaders is to configure the framework before the application launch. For example, we can declare a new route for our application or module:
namespace App\Bootloader;
use Spiral\Router\RouterInterface;
use Spiral\Router\Target\Controller;
use Spiral\Router\Route;
class MyBootloader extends Bootloader
{
public function boot(RouterInterface $router): void
{
$router->setRoute(
'my-route',
new Route('/<action>', new Controller(MyController::class))
);
}
}
Note
In the exact same way, you can mount middleware, change tokenizer directories, and much more.
Some framework bootloaders can be used as a simple path to configure application settings. For example, we can
use Spiral\Bootloader\Http\HttpBootloader
to add global PSR-15 middleware:
namespace App\Bootloader;
use Spiral\Bootloader\Http\HttpBootloader;
use App\Middleware\MyMiddleware;
class MyBootloader extends Bootloader
{
public function boot(HttpBootloader $http): void
{
$http->addMiddleware(MyMiddleware::class);
}
}
If you want to ensure that HttpBootloader
has always been initiated before MyBootloader
, use constant DEPENDENCIES
:
namespace App\Bootloader;
use Spiral\Bootloader\Http\HttpBootloader;
use App\Middleware\MyMiddleware;
class MyBootloader extends Bootloader
{
const DEPENDENCIES = [
HttpBootloader::class
];
public function boot(HttpBootloader $http): void
{
$http->addMiddleware(MyMiddleware::class);
}
}
Note
that you are only able to use bootloaders to configure your components during the bootstrap phase (a.k.a. via another bootloader). The framework would not allow you to change any configuration value after component initialization.
You can control the bootload process using Bootloader itself, simply request Spiral\Boot\BootloadManager
:
namespace App\Bootloader;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Boot\BootloadManager;
use Spiral\Bootloader\DebugBootloader;
use Spiral\Boot\EnvironmentInterface;
class AppBootloader extends Bootloader
{
public function boot(BootloadManager $bootloadManager, EnvironmentInterface $env): void
{
if ($env->get('DEBUG')) {
$bootloadManager->bootload([
DebugBootloader::class
]);
}
}
}