Laravel’s dependency injection, the Service Container, and related concepts
Let’s dig into Laravel’s dependency injection, the Service Container, and related concepts.
Dependency Injection (DI)
At its core, dependency injection is a technique whereby one object supplies the dependencies of another object. A “dependency” is just an object that can be used (a service). Injecting it means passing it to a dependent object.
In the context of Laravel (which is a PHP framework), DI helps in managing class dependencies. When a class depends on another class to perform its operations, instead of hard-coding that dependency, it’s provided (injected) at runtime. This makes the system more modular, easier to test, and adhere to the SOLID principles of object-oriented design.
Example: Basic Dependency Injection
class OrderController {
protected $orderService;
public function __construct(OrderService $orderService) {
$this->orderService = $orderService;
}
}
Here, OrderController
depends on OrderService
. Instead of creating an instance of OrderService
inside OrderController
, an instance is injected through the constructor. Laravel automatically resolves such dependencies via the Service Container.
The Service Container
The Service Container is a powerful tool for managing class dependencies and performing dependency injection in Laravel. It’s essentially a map for creating objects, where you tell Laravel how to create objects when they are needed.
Example: Binding into the Service Container
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
This tells Laravel: “When someone asks for the HelpSpot\API
, create it like this.” When a class type-hints HelpSpot\API
, Laravel will inject the object created by that closure.
Interfaces and Binding
Interfaces define a contract for classes. You might bind an interface to a given implementation in the Service Container, so when a class depends on that interface, Laravel will inject the bound implementation.
interface PaymentGateway {
public function charge($amount);
}
class StripePaymentGateway implements PaymentGateway {
public function charge($amount) {
// Charging through Stripe...
}
}
// In a ServiceProvider:
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
Now, when a class type-hints PaymentGateway
, Laravel knows to inject an instance of StripePaymentGateway
.
Combining DI with Regular Parameters
You can mix dependency injection and normal parameters in methods (not in constructors, for constructors only DI). Use DI in the constructor to inject services and use method parameters for runtime values.
class OrderController {
protected $orderService;
public function __construct(OrderService $orderService) {
$this->orderService = $orderService;
}
public function show(Order $order, $id) {
// $order is resolved via DI, $id is a regular parameter
}
}
Here’s how you might define a route to handle calls to the show
method:
use App\Http\Controllers\OrderController;
Route::get('order/{id}', [OrderController::class, 'show']);