Laravel's service container is the heart of the framework, managing class dependencies and performing dependency injection. Understanding it transforms how you architect applications. At ZIRA Software, mastering the container has enabled us to build highly testable, loosely coupled systems.
What is Dependency Injection?
Without DI:
class OrderProcessor
{
protected $paymentGateway;
public function __construct()
{
$this->paymentGateway = new StripeGateway(); // Tight coupling!
}
}
With DI:
class OrderProcessor
{
protected $paymentGateway;
public function __construct(PaymentGatewayInterface $paymentGateway)
{
$this->paymentGateway = $paymentGateway; // Loose coupling!
}
}
Basic Binding
Simple binding:
app()->bind('App\Services\PaymentService', function ($app) {
return new PaymentService(config('services.stripe.key'));
});
Singleton:
app()->singleton('App\Services\CacheService', function ($app) {
return new CacheService($app->make('cache'));
});
Instance:
$service = new PaymentService();
app()->instance('App\Services\PaymentService', $service);
Resolving
Manual:
$service = app()->make('App\Services\PaymentService');
$service = app('App\Services\PaymentService');
$service = resolve('App\Services\PaymentService');
Automatic (Constructor Injection):
class OrderController extends Controller
{
protected $orderService;
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService; // Auto-resolved!
}
}
Interface Binding
// Service provider
$this->app->bind(
'App\Contracts\PaymentGatewayInterface',
'App\Services\StripeGateway'
);
// Usage - Laravel resolves automatically
class OrderProcessor
{
public function __construct(PaymentGatewayInterface $gateway)
{
$this->gateway = $gateway; // Gets StripeGateway instance
}
}
Contextual Binding
Different implementations per context:
$this->app->when('App\Http\Controllers\PaymentController')
->needs('App\Contracts\PaymentGatewayInterface')
->give('App\Services\StripeGateway');
$this->app->when('App\Console\Commands\ProcessRefunds')
->needs('App\Contracts\PaymentGatewayInterface')
->give('App\Services\PayPalGateway');
Service Providers
Best practice - use service providers:
php artisan make:provider PaymentServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('payment', function ($app) {
return new PaymentService(
$app->make('App\Contracts\PaymentGatewayInterface')
);
});
$this->app->bind(
'App\Contracts\PaymentGatewayInterface',
'App\Services\StripeGateway'
);
}
public function boot()
{
// Boot logic here
}
}
Testing Benefits
Easy mocking:
public function test_order_processes_payment()
{
$gateway = Mockery::mock('App\Contracts\PaymentGatewayInterface');
$gateway->shouldReceive('charge')->once()->andReturn(true);
$this->app->instance('App\Contracts\PaymentGatewayInterface', $gateway);
$processor = app(OrderProcessor::class);
$result = $processor->process($order);
$this->assertTrue($result);
}
Conclusion
The service container and dependency injection are fundamental to Laravel mastery. They enable testable, maintainable applications through loose coupling. At ZIRA Software, proper DI has dramatically improved our codebase quality.
Need help architecting Laravel applications with proper dependency injection? Contact ZIRA Software to discuss software architecture and design patterns.