Dependency Injection é simples, tão ridiculamente simples que pode ser considerada apenas bom senso. Dá uma olhada nesse exemplo danado de uma classe mal projetada:
<?php
$accounts = new Accounts('localhost', 'root', '', 'acc');
class Accounts
{
protected $db;
public function __construct($dbHost, $dbUser, $dbPass, $dbName) {
$this->db = new PDO("mysql:host=$dbHost;dbname=$dbName", $dbUser, $dbPass);
}
/* ... */
}
Consegue enxergar o problema aí? É normal haverem dependências entre as classes, mas nesse caso a PDO está sendo instanciada dentro da Accounts, o que leva a uma série de problemas em potencial:
- Imagine que você precisa configurar, opcionalmente, a PDO para trabalhar ou não com disparo de exceções.
- Você precisa trocar o driver de mysql para sqlite.
Para resolver esses dois problemas, você poderia fazer algumas alterações:
<?php
$accounts = new Accounts('localhost', 'root', '', 'acc', 'mysql', true);
class Accounts
{
protected $db;
public function __construct($dbHost, $dbUser, $dbPass, $dbName, $dbDriver='mysql', $throwExceptions=false) {
$this->db = new PDO("$dbDriver:host=$dbHost;dbname=$dbName", $dbUser, $dbPass);
if ($throwExceptions) {
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
}
/* ... */
}
Isso é ruim porque todos os requisitos para essa alteração giravam em torno da PDO, mas quem foi alterada foi a Accounts, que não tem nada a ver com a história. A PDO É OS POLÍTICO E A ACCOUNTS É O POVO BRASILEIRO SOFREDOR.
Sou totalmente a favor de uma IDE que emite um curto porém forte choque elétrico em programadores que criam construtores com muitos parâmetros, e um boost extra na voltagem para aqueles que colocam mais de um parâmetro opcional. A coisa mais melequenta que existe é ficar testando parâmetros, valores padrão, ordzZZZzzzzZ. Projete seus construtores para serem simples e o universo conspirará a favor do seu software.
Usando Dependency Injection, você escreveria o código acima assim:
<?php
$db = new PDO('mysql:host=localhost;dbname=acc', 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$accounts = new Accounts($db);
class Accounts
{
protected $db;
public function __construct(PDO $db) {
$this->db = $db;
}
/* ... */
}
Containers
Agora você moveu a parte tosca do código, que é instanciar e configurar a PDO, para fora da classe Accounts, que não tem nada a ver com isso. O problema é que agora você tem que escrever três linhas pra fazer o que antes era feito com apenas uma. Downers.
Pra resolver esse probleminha, você pode gerenciar as dependências usando um Container (coloquei até uma outra dependência aí dentro, pra ilustrar):
<?php
$statistics = AccountsContainer::getAccountsStats();
abstract class AccountsContainer
{
public static function getDb()
{
$db = new PDO('mysql:host=localhost;dbname=acc', 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $db;
}
public static function getAccounts()
{
return new Accounts(self::getDb());
}
public static function getAccountsStats()
{
return new Statistics(self::getAccounts());
}
}
Agora três objetos distintos que dependem uns dos outros podem ser configurados independentemente e o código fica mais organizado. O container sabe quem depende de quem, você pede um objeto e ele resolve todas as dependências.
Você pode implementar containers como bem entender. O Symfony Dependency Injection Container por exemplo permite você fazer MISÉEERIAS, com o preço de ser complexo pra caralho. Você pode descrever as dependências usando um arquivo de configuração XML, YAML ou LOLCODE por exemplo.
Mas fica a dica: você só vai usar containers configuráveis e complexos em projetos gigantemente enormes. Pra coisas simples, comece simples. O que importa é o conceito.
Abraços encapsulados pra vocês.