Dependency — one class needing another class to function
Concept
Dependency — when one class needs another class (or value) to function. Class A has a dependency on class B if A uses B to do its work.
Types of dependencies:
- Hard dependency (bad): Class A creates its own instance of B inside itself.
$this->logger = new FileLogger(). A is permanently coupled to FileLogger. - Soft dependency / Injected dependency (good): B is passed into A from outside. A depends on an interface, not a concrete class. Can be swapped.
Dependency Injection (DI): The practice of passing dependencies into a class rather than creating them inside. A class declares what it needs (via constructor parameters), and something else provides it.
Why injection is better:
- Testable: Pass a mock/stub in tests. Without DI, you can't control what FileLogger does in tests.
- Flexible: Swap FileLogger for NullLogger without changing A.
- Explicit: Dependencies are visible from the constructor signature. With internal creation, it's hidden.
Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions. OrderService shouldn't depend on MySQLOrderRepository — it should depend on OrderRepositoryInterface. The actual implementation is swapped in the service container.
Service Container: Laravel's container is a Dependency Injection Container (DIC). It knows how to construct objects and their dependencies. app(OrderService::class) — the container instantiates OrderService and injects its dependencies automatically.
Code Example
<?php
// ❌ HARD DEPENDENCY — tight coupling, untestable
class OrderService
{
private FileLogger $logger;
private MySQLOrderRepository $repo;
public function __construct()
{
// Creates its own dependencies — can't swap, can't test in isolation
$this->logger = new FileLogger('/var/log/orders.log');
$this->repo = new MySQLOrderRepository();
}
public function createOrder(array $data): Order
{
$order = $this->repo->save($data);
$this->logger->log('Order created: ' . $order->id);
return $order;
}
}
// ✅ DEPENDENCY INJECTION — loose coupling, testable
interface LoggerInterface {
public function log(string $msg): void;
}
interface OrderRepositoryInterface {
public function save(array $data): Order;
}
class OrderService
{
public function __construct(
private readonly OrderRepositoryInterface $repo, // depends on interface
private readonly LoggerInterface $logger, // depends on interface
) {}
public function createOrder(array $data): Order
{
$order = $this->repo->save($data);
$this->logger->log('Order created: ' . $order->id);
return $order;
}
}
// Concrete implementations (injected by the container)
class EloquentOrderRepository implements OrderRepositoryInterface
{
public function save(array $data): Order { return Order::create($data); }
}
class FileLogger implements LoggerInterface
{
public function log(string $msg): void { file_put_contents('/var/log/orders.log', $msg . "\n", FILE_APPEND); }
}
// Laravel service container — resolves dependencies automatically
// In AppServiceProvider:
$this->app->bind(OrderRepositoryInterface::class, EloquentOrderRepository::class);
$this->app->bind(LoggerInterface::class, FileLogger::class);
// Usage — container injects automatically
$service = app(OrderService::class);
// Container sees: OrderService needs OrderRepositoryInterface → creates EloquentOrderRepository
// OrderService needs LoggerInterface → creates FileLogger
// All wired up without manual instantiation
// Testing — inject test doubles
class OrderServiceTest extends \PHPUnit\Framework\TestCase
{
public function test_creates_order(): void
{
$mockRepo = $this->createMock(OrderRepositoryInterface::class);
$nullLogger = new class implements LoggerInterface { public function log(string $msg): void {} };
$mockRepo->expects($this->once())->method('save')->willReturn(new Order(['id' => 1]));
$service = new OrderService($mockRepo, $nullLogger); // inject test doubles!
$order = $service->createOrder(['total' => 100]);
$this->assertEquals(1, $order->id);
// No real DB, no real file system — test is fast and isolated
}
}