Setter injection and a service locator both let a class acquire a collaborator at the moment it happens to need one, so nothing about the class announces how many it has. Requiring them in the constructor puts the whole list in a single signature, where seven parameters look like what they are.
// nothing here says how much this class depends on
class OrderController extends Controller
{
public function complete($id)
{
$mailer = $this->registry->get('mailer');
$stock = $this->registry->get('stock');
$invoice = $this->registry->get('invoices');
// ...
}
}
// the same dependencies, stated
class OrderController extends Controller
{
private $orders;
private $mailer;
private $stock;
private $invoices;
private $log;
private $events;
private $config;
public function __construct(
OrderRepository $orders,
Mailer $mailer,
StockLevels $stock,
InvoiceWriter $invoices,
Logger $log,
EventDispatcher $events,
Config $config
) { /* ... */ }
}
The second version is not worse than the first. It is the first, written down. Seven constructor parameters is the signal, and the useful response is to ask which of them travel together: $invoices, $mailer and $events are usually three steps of one operation that wants to be its own class, and once it is, the controller takes two arguments. Genuinely optional dependencies are the honest exception and belong in setters, with a null object as the default so nothing has to check. What insisting on constructors costs is that construction becomes tedious before a container is worth adding — a fair price for a design problem that would otherwise stay invisible until somebody tried to test the class.