É uma injeção de dependência e é uma prática ruim?

Eu tenho uma pequena estrutura e eu codifiquei assim. Não tenho certeza se é chamado de injeção de dependência ou não. Não sei se é como um padrão de design ou não. Eu também não sei e me pergunto se passar $this como param é uma má prática.

Veja isso; (Não é um exemplo de trabalho, apenas escreveu esses códigos no navegador para explicação.)

 /* This is engine model */ require_once('Database.class.php'); require_once('Image.class.php'); require_once('Misc.class.php'); require_once('BBCode.class.php'); class FrameWork_Engine_Model { public $database, $config, $misc, $bbcode, $controller, $image; function __construct($config) { $this->database = new Database($configParams); $this->image = new Image($this); $this->misc = new Misc($this); $this->bbcode = new BBCode($this); $this->controller = new Controller($this); //here I call Register controller depending on routing, in this case, register controller. } ... } 

  /* This is register controller */ class Register extends Base_Controller { /*I can access anything over Engine Model in my controllers */ $this->engine->database->query(); //I access database model $this->engine->bbcode->tag('you'); //I access bbcode model $this->engine->image->sanitizeUploadedFile(); //I access image model //etc. I can access others models like this. } 

Basicamente, meus controladores podem acessar qualquer modelo via modelo de mecanismo. Eu acredito que a dependency injection is all about injecting dependencies into controllers? Como, meu controlador de registro precisa de um modelo de database, modelo de roteamento e modelo de modelo para funcionar. Aqui tem tudo o que depende. Estou enganado?

Diante disso, minhas perguntas são:

  1. É um exemplo de injeção de dependência válido? Se não, o que é? Tem um nome nos padrões de design?

  2. Se não é nada relacionado com a injeção de dependência, quais mudanças precisam ser feitas para ser DI?

  3. A passagem de $this parâmetro em classs recém-criadas é uma prática ruim? Se sim, por quê?

Ps. Eu sei que fazer 3 perguntas em um tópico não é algo que o stackoverflow gosta, mas eu não quero copiar cole todo o texto para perguntar.

Você está quase lá.

Questão 1

Não, não vejo isso como um exemplo válido de injeção de dependência. Parece um pouco um localizador de serviço (porque está injetando o recipiente inteiro em seus serviços e usá-lo para “localizar” serviços dependentes).

Questão 2

Você está fazendo uma pequena confusão entre a injeção de dependência e um recipiente de injeção de dependência.

Primeiro, a injeção de dependência significa empurrar dependencies para um object em tempo de execução em vez de criá-las / puxá-las.

Para exemplificar isso:

 //hardcoded dependecies class BadService { public function __construct() { $this->dep1 = new ConcreteObject1(); $this->dep2 = new ConcreteObject2(); } } 

Portanto, no exemplo acima, o BadService torna impossível conectar outras dependencies em tempo de execução, porque elas já estão BadService no próprio construtor.

 //service locator pattern class AlmostGoodService { public function __construct(Container $container) { $this->dep1 = $container->getADep1(); $this->dep2 = $container->getADep2(); } } 

No exemplo AlmostGoodService , removemos as dependencies rígidas do exemplo anterior, mas ainda estamos dependendo de uma implementação específica do nosso contêiner (o que significa que nosso serviço não é reutilizável sem fornecer a implementação desse contêiner). Este é o exemplo que combina com o que você está fazendo.

 //dependecy injection class GoodService { public function __construct($dep1, OptionalInterface $dep2) { $this->dep1 = $dep1; $this->dep2 = $dep2; } } 

O serviço GoodService não se preocupa com a criação de dependencies concretas e pode ser facilmente “com fio” em tempo de execução com quaisquer dependencies que implementem o “protocolo” do $dep1 ou a $dep1 opcional para o $dep2 (portanto, o nome de Inversion of Control – o conceito subjacente por trás da Injeção de Dependência).

O componente que faz essa fiação é chamado de um recipiente de injeção de dependência .

Agora, um recipiente de injeção de dependência , na forma mais simples, não é mais do que um object capaz de conectar seus objects em tempo de execução com base em alguma forma de configuração.

Eu disse que você está quase lá, mas há alguns problemas com sua implementação:

  • a fiação deve ser preguiçosa (você não quer fazer tudo isso em seu construtor, pois sua aplicação desaceleraria consideravelmente à medida que cresce)
  • você não deve passar o contêiner inteiro ( $this ) como uma dependência porque, em seguida, volta para uma inversão de controle mais fraca, ou seja, um localizador de serviço . Você deve, em vez disso, passar as dependencies concretas para seus construtores de serviços

Questão 3

Há alguns casos em que você se encontrará querendo passar todo o $container como uma dependência para um serviço (ou seja, controladores ou fábricas de serviços preguiçosos), mas geralmente será melhor ficar longe dessa prática, pois fará seus serviços mais reutilizável e mais fácil de testar. Quando você está sentindo que seu serviço possui muitas dependencies, então é um bom sinal de que você é um serviço demais e é um bom momento para dividi-lo.

Implementação do recipiente de protótipo

Então, com base nas minhas respostas acima, aqui está uma implementação revisada (longe da perfeita):

 /* This is the revised engine model */ class FrameWork_Engine_Model { function __construct($config) { $this->config = $cofig; } public function database() { require_once('Database.class.php'); return new Database($this->config['configParams']); } public function bbcode() { require_once('BBCode.class.php'); return new BBCode($this->database()); } public function image() { require_once('Image.class.php'); $this->image = new Image($this->config['extensionName']); } .... public function register_controller($shared = true) { if ($shared && $this->register_controller) { return $this->register_controller; } return $this->register_controller = new Register_Controller($this->database(), $thus->image(), $this->bbcode()); } } 

Agora, para usar seus serviços:

 $container = new FrameWork_Engine_Model(); $container->register_controller()->doSomeAction() 

O que poderia ser melhorado? Seu recipiente deve:

  • fornecer uma maneira de compartilhar serviços – ou seja, inicializá-los apenas uma vez
  • ser bloqueável – fornecer uma maneira de bloqueá-lo após a configuração
  • ser capaz de “mesclar” com outros recipientes – dessa forma sua aplicação será realmente modular
  • permitir dependencies opcionais
  • permitir escopos
  • serviços de marcação de suporte

Pronto para usar as implementações de contêineres DI

Todos estes são acompanhados com documentação clara sobre Injeção de Dependência

  • Pimple – contentor DI leve do PHP 5.3
  • Symfony2 DI Container – PHP 5.3 possui contêiner DI completo
  • Suco DI – pequeno recipiente do PHP 5.2 DI
  1. Seu FrameWork_Engine_Model é um registro ( padrão de registro ). Injetar o registro como dependência em todos os objects é uma espécie de injeção de dependência incompreendida. Tecnicamente, é DI, mas você cria uma dependência de tudo para tudo e também tira a flexibilidade que DI deve oferecer.
  2. Se seu FrameWork_Engine_Model pretender instanciar os serviços e gerenciar suas dependencies, você poderia alterá-lo para um Container de Controle de Inversão (padrão típico relacionado a DI)
  3. Não, não em geral.

Não discutirei a escolha dos nomes das classs e das responsabilidades dos seus serviços e controladores, pois não acho que esteja dentro do escopo desta questão. Apenas uma observação: parece que seus controladores fazem muito. Se você estiver interessado em um código limpo, você pode querer dar uma olhada no Princípio de Responsabilidade Única e manter seus controladores “finos”, movendo lógica de negócios e consultas de database para uma camada de serviço e mecanismos de saída como bbcode para visualizações.

Então, de volta ao seu exemplo e como mudá-lo para um uso sensato da Injeção de Dependência. Um recipiente IoC primitivo poderia ser assim:

 public function createRegisterController() { $controller = new RegisterController(); $controller->setImage($this->getImageService()); // ... return $controller; } public function getImageService() { if ($this->imageService === null) { $this->imageService = new Image(); // inject dependencies of Image here } return $this->imageService; } 

O ponto importante aqui é: apenas injete as dependencies que são necessárias. E não crie um monte de variables ​​globais disfarçadas como DI.