register() vs boot() — timing and what belongs where
Intermediate5 min read·lv-03-002
interviewlaravel-src
Concept
The register() and boot() methods in a service provider have strict rules about what they're allowed to do. Violating these rules causes subtle bugs that only manifest in edge cases or under certain loading orders.
register() — Container bindings ONLY:
- Bind interfaces to implementations.
- Register singletons, scoped bindings, instances.
- DO NOT call other services: no
app()->make(), noEvent::listen(), noRoute::get(). - Why: When
register()runs for provider X, other providers may not have registered their bindings yet. Accessing a service that depends on another provider's binding can fail.
boot() — Everything else:
- Safe to call any service because all
register()methods have already run. - Register routes (
$this->loadRoutesFrom()). - Register event listeners.
- Publish configuration, migrations, views.
- Register Blade directives and components.
- Extend models, macros, etc.
Common mistake: Trying to resolve a service inside register():
php
public function register(): void
{
$logger = app()->make(LoggerInterface::class); // WRONG — may not exist yet
$this->app->bind(MyService::class, fn() => new MyService($logger));
}Fix: Wrap in a closure — resolution is deferred to when the binding is actually needed:
php
public function register(): void
{
$this->app->bind(MyService::class, fn($app) => new MyService(
$app->make(LoggerInterface::class) // resolved at usage time, not registration time
));
}Code Example
php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Event;
class OrderServiceProvider extends ServiceProvider
{
/**
* REGISTER — bindings only.
* Closure form defers resolution to usage time.
*/
public function register(): void
{
$this->app->singleton(\App\Services\OrderService::class, function($app) {
// $app->make() calls here are safe — they're inside a closure
// resolved lazily when OrderService is first needed
return new \App\Services\OrderService(
$app->make(\App\Repositories\OrderRepository::class),
$app->make(\App\Services\InventoryService::class),
);
});
}
/**
* BOOT — everything else.
* All providers have been registered — safe to use any service.
*/
public function boot(): void
{
// Routes
$this->loadRoutesFrom(__DIR__ . '/../../routes/orders.php');
// Event listeners
Event::listen(
\App\Events\OrderPlaced::class,
\App\Listeners\SendOrderConfirmation::class,
);
// Gate policy
Gate::policy(\App\Models\Order::class, \App\Policies\OrderPolicy::class);
// Blade directive
\Illuminate\Support\Facades\Blade::directive('orderStatus', function($expression) {
return "<?php echo App\Helpers\OrderHelper::statusBadge($expression); ?>";
});
}
}