Mastering Dependency Injection with Laravel's Service Container
Laravel's service container is a powerful tool for managing class dependencies and performing dependency injection. It's at the heart of many Laravel features and can significantly improve your application's flexibility and testability. Let's explore how to leverage the service container in your Laravel applications.
Basic Binding
At its simplest, the service container is used to bind interfaces to implementations:
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
This binding tells Laravel to use StripePaymentGateway
whenever a PaymentGateway
is requested.
Resolving from the Container
You can resolve instances from the container in several ways:
// Using the app() helper
$payment = app(PaymentGateway::class);
// Using the container directly
$payment = $this->app->make(PaymentGateway::class);
// Type-hinting in constructor or method injection
public function __construct(PaymentGateway $payment)
{
$this->payment = $payment;
}
Singleton Bindings
If you want the same instance to be returned every time, use singleton bindings:
$this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
Binding Instances
You can bind an existing instance into the container:
$payment = new StripePaymentGateway('secret-key');
$this->app->instance(PaymentGateway::class, $payment);
Contextual Binding
Sometimes you might want different implementations based on the class requesting the dependency:
$this->app->when(PremiumSubscription::class)
->needs(PaymentGateway::class)
->give(StripePaymentGateway::class);
$this->app->when(BasicSubscription::class)
->needs(PaymentGateway::class)
->give(PayPalPaymentGateway::class);
Binding Primitives
You can even bind primitives when a class is resolved:
$this->app->when(StripePaymentGateway::class)
->needs('$apiKey')
->give(config('services.stripe.secret'));
Tagging
The container allows you to tag related bindings:
$this->app->bind(StripePaymentGateway::class, function () {
// ...
});
$this->app->bind(PayPalPaymentGateway::class, function () {
// ...
});
$this->app->tag([StripePaymentGateway::class, PayPalPaymentGateway::class], 'gateways');
// Resolve all tagged instances
$gateways = $this->app->tagged('gateways');
Extending Bindings
You can extend existing bindings:
$this->app->extend(PaymentGateway::class, function ($service, $app) {
return new PaymentGatewayDecorator($service);
});
Real-World Example
Here's how you might use the service container in a payment processing scenario:
interface PaymentGateway
{
public function charge($amount);
}
class StripePaymentGateway implements PaymentGateway
{
public function charge($amount) { /* ... */ }
}
class PayPalPaymentGateway implements PaymentGateway
{
public function charge($amount) { /* ... */ }
}
// In a service provider
public function register()
{
$this->app->bind(PaymentGateway::class, function ($app) {
return $app->make(config('services.payment.driver'));
});
}
// In a controller
public function processPayment(PaymentGateway $gateway)
{
$gateway->charge(100);
}
Laravel's service container is a fundamental tool for building flexible, testable applications. By leveraging dependency injection and the service container, you can create more modular code, easily swap implementations, and greatly simplify testing.
If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!