0

Bootstrapper vs Service Provider — two different boot phases

Intermediate5 min read·eng-14-018
interviewlaravel-src

Concept

Bootstrapper vs Service Provider — two distinct phases in the Laravel boot process. Both prepare the application, but at different levels.

Bootstrapper (internal): A low-level class that prepares the framework itself before any application code runs. Bootstrappers run early in the HTTP kernel's bootstrap() method. They're framework internals — you don't usually write your own.

Built-in bootstrappers (in order):

  1. LoadEnvironmentVariables — loads .env file.
  2. LoadConfiguration — merges all config/*.php files.
  3. HandleExceptions — registers the global exception handler.
  4. RegisterFacades — resolves the aliases array (Facade::class).
  5. RegisterProviders — discovers and registers all service providers.
  6. BootProviders — calls boot() on every registered service provider.

Service Provider: An application-level class that registers bindings and bootstraps services. This is where YOUR code hooks into the framework's lifecycle.

Two phases in Service Providers:

  • register(): Bind things into the service container. This runs early — other providers may not be booted yet. Only do bindings here, nothing that depends on other services.
  • boot(): Called after ALL providers are registered. Safe to depend on other providers' bindings. Register routes, event listeners, view composers, macros, validation rules, gates.

The key rule: register() = bind things. boot() = use things. Never call other services in register() — they may not be registered yet.

Service Provider discovery: Listed in config/app.php providers array. Laravel 11+ uses bootstrap/providers.php. Packages add themselves via Composer's extra.laravel.providers auto-discovery.

Code Example

php
<?php
// BOOTSTRAPPER — framework internal (you'd never write this, but understand it)
// vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php
class LoadConfiguration
{
    public function bootstrap(Application $app): void
    {
        $config = new Repository;
        // Loads every PHP file from config/ directory
        foreach ($this->getConfigurationFiles($app) as $key => $path) {
            $config->set($key, require $path);
        }
        $app->instance('config', $config);
        date_default_timezone_set($config->get('app.timezone', 'UTC'));
    }
}
// You don't interact with bootstrappers — they run automatically

// SERVICE PROVIDER — this is what YOU write
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class OrderServiceProvider extends ServiceProvider
{
    // REGISTER — only bind things into the container
    public function register(): void
    {
        // ✅ Correct: bind interfaces to implementations
        $this->app->bind(
            \App\Contracts\OrderRepositoryInterface::class,
            \App\Repositories\EloquentOrderRepository::class,
        );

        $this->app->singleton(
            \App\Services\PaymentGateway::class,
            fn($app) => new \App\Services\StripeGateway(config('services.stripe.key')),
        );

        // ❌ WRONG: don't call other services in register — they may not be ready
        // \Event::listen(...); // Event facade might not be registered yet
    }

    // BOOT — use things from the container, register behavior
    public function boot(): void
    {
        // ✅ Safe here — all providers are registered by now
        \Gate::define('manage-orders', fn(User $u) => $u->hasRole('manager'));

        \Event::listen(
            \App\Events\OrderPlaced::class,
            \App\Listeners\SendOrderConfirmation::class,
        );

        \Validator::extend('valid_phone', function ($attribute, $value) {
            return preg_match('/^\+?\d{10,15}$/', $value);
        });

        // Register macros
        \Illuminate\Support\Collection::macro('toAssoc', function(string $key) {
            return $this->keyBy($key);
        });

        // Load routes from a custom location
        $this->loadRoutesFrom(__DIR__ . '/../../routes/orders.php');
        $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations');
    }
}