Why auto-wiring is expensive and how to fix it.
Laravel's auto-wiring is beautiful for development. You just type-hint a dependency, and Laravel figures out what to give you. But behind the scenes, it uses PHP's Reflection API to read your constructor signatures — on every request.
Reflection is 10-100x slower than direct instantiation. In a typical Laravel request, the container may resolve 20-50 dependencies. That's hundreds of Reflection operations per request.
Cost per Reflection operation: ~0.1-1ms. With 50 operations per request = 5-50ms of pure overhead.
// Controller
class OrderController
{
public function __construct(
OrderService $orderService,
PaymentService $paymentService,
Logger $logger,
Cache $cache,
Mailer $mailer
) {}
}
// Resolved via container
$controller = app()->make(OrderController::class);
Cost per request: ~2-5ms (Reflection on 5 dependencies)
// Controller
class OrderController
{
public function __construct(
private OrderService $orderService,
private PaymentService $paymentService,
private Logger $logger,
private Cache $cache,
private Mailer $mailer
) {}
}
// Create manually
$controller = new OrderController(
new OrderService(),
new PaymentService(),
new Logger(),
app('cache'),
new Mailer()
);
Cost per request: ~0.05ms (no Reflection)
| Approach | Reflection ops per request | Time per request | 10k requests/day |
|---|---|---|---|
| Full Auto-Wiring (default) | ~200-500 | 20-50ms | 200-500 seconds CPU |
| Singletons + Explicit Binding | ~20-50 | 2-5ms | 20-50 seconds CPU |
| Manual Instantiation (ideal) | 0 | 0ms overhead | 0 seconds overhead |
You don't need to go full manual. There's a middle ground: Singletons + Explicit Binding.
If a service has no request-specific state, make it a singleton. It will be resolved once and reused for all requests.
// app/Providers/AppServiceProvider.php
public function register(): void
{
// BAD: Transient (new instance every time)
$this->app->bind(UserService::class, function ($app) {
return new UserService($app->make(Logger::class));
});
// GOOD: Singleton (one instance, reused)
$this->app->singleton(UserService::class, function ($app) {
return new UserService($app->make(Logger::class));
});
// BEST: Explicit binding with type hints
$this->app->when(UserController::class)
->needs(UserService::class)
->give(function ($app) {
return $app->make(UserService::class);
});
}
| Aspect | Transient (bind) | Singleton |
|---|---|---|
| Instantiated per request? | Yes — every time | No — once for all requests |
| Reflection cost per request? | Yes — on every resolution | Only on first resolution |
| Memory per request? | N instances | 1 instance |
| When to use | Services with request-specific state (e.g., CartService) | Stateless services (e.g., PaymentGateway, Mailer, Logger) |
Only use singletons for stateless services. If your service stores request-specific data (like a shopping cart), making it a singleton will cause data leakage between users!
Every time Laravel sees a type-hint it hasn't seen before, it uses Reflection to figure out what to inject.
When you explicitly tell Laravel what to inject, it doesn't need to reflect.
// app/Providers/AppServiceProvider.php
public function register(): void
{
// Instead of letting Laravel figure this out via Reflection:
// public function __construct(UserService $service, Logger $logger)
// Tell Laravel explicitly:
$this->app->when(UserController::class)
->needs(UserService::class)
->give(function ($app) {
return $app->make(UserService::class);
});
$this->app->when(UserController::class)
->needs(Logger::class)
->give(function ($app) {
return $app->make(Logger::class);
});
// For interfaces, always use explicit binding
$this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);
$this->app->bind(NotificationInterface::class, SmsNotification::class);
}
Laravel skips Reflection entirely for explicitly bound dependencies. It goes directly to the closure you provided.
foreach ($users as $user) {
$service = app()->make(ProcessorService::class); // 💀 Reflection N times!
$service->process($user);
}
If you have 10,000 users, this runs Reflection 10,000 times. Your server will cry.
$service = app()->make(ProcessorService::class); // ✅ Once
foreach ($users as $user) {
$service->process($user);
}
foreach ($users as $user) {
$service = app()->make(Service::class);
// Reflection 10,000 times
}
// Time: 5-10 seconds
$service = app()->make(Service::class);
foreach ($users as $user) {
$service->process($user);
}
// Time: 0.5-1 second
The service container is resolved ONCE when the application boots, not per request. Reflection cost drops to near zero.
With Octane, memory leaks become permanent. If you have a singleton that accumulates data, it will keep growing until the server restarts. Always ensure your singletons are truly stateless.
| Pattern | Reflection per Request | Speed | When to Use |
|---|---|---|---|
| Full Auto-Wiring (default) | Many (200-500) | Slow | Development only |
| Transient (bind) | Per resolution | Slow | Request-specific state |
| Singleton | Once total | Fast | Stateless services |
| Explicit Binding | None (direct) | Fastest | Critical paths, interfaces |
| Octane (persistent app) | Once ever | Lightning | Production with Swoole/RoadRunner |
Topic 8: Middleware Selectivity — Why every request runs 10 middleware classes. How to create specialized middleware groups and save 5-10ms per request.