Davidson Alencar
9 years ago
1 changed files with 321 additions and 0 deletions
@ -0,0 +1,321 @@
|
||||
Container de Injeção de Dependência |
||||
============================== |
||||
|
||||
Um container de injeção de dependência (DI) é um objeto que sabe como instanciar e configurar objetos e todas as suas dependencias. [Martin's article] (http://martinfowler.com/articles/injection.html) explica bem porque o container DI é útil. Aqui vamos explicar principalmente a utilização do container DI fornecido pelo Yii. |
||||
|
||||
Injeção de Dependência <span id="dependency-injection"></span> |
||||
-------------------- |
||||
|
||||
Yii fornece o recurso container DI através da classe [[yii\di\Container]]. Ela suporta os seguintes tipos de injeção de dependência: |
||||
|
||||
* Injeção de Construtor; |
||||
* Injeção de Setter e propriedade; |
||||
* Injeção de PHP callable. |
||||
|
||||
### Injecão de Construtor <span id="constructor-injection"></span> |
||||
|
||||
O container DI suporta injeção de construtor com o auxílio dos type hints identificados nos parâmetros dos construtores. Os type hints informam ao container quais classes ou interfaces são dependentes no momento da criação de um novo objeto. |
||||
O container tentará pegar as instâncias das classes dependentes ou interfaces e depois injetá-las dentro do novo objeto através do construtor. Por exemplo, |
||||
|
||||
```php |
||||
class Foo |
||||
{ |
||||
public function __construct(Bar $bar) |
||||
{ |
||||
} |
||||
} |
||||
$foo = $container->get('Foo'); |
||||
// que equivale a: |
||||
$bar = new Bar; |
||||
$foo = new Foo($bar); |
||||
``` |
||||
|
||||
|
||||
### Injeção de Setter e Propriedade<span id="setter-and-property-injection"></span> |
||||
Injeção de Setter e propriedade é suportado através de [configurações](concept-configurations.md). |
||||
Ao registrar uma dependência ou ao criar um novo objeto, você pode fornecer uma configuração que será utilizada pelo container para injetar as dependências através dos setters ou propriedades correspondentes. |
||||
Por exemplo, |
||||
|
||||
```php |
||||
use yii\base\Object; |
||||
|
||||
class Foo extends Object |
||||
{ |
||||
public $bar; |
||||
|
||||
private $_qux; |
||||
|
||||
public function getQux() |
||||
{ |
||||
return $this->_qux; |
||||
} |
||||
|
||||
public function setQux(Qux $qux) |
||||
{ |
||||
$this->_qux = $qux; |
||||
} |
||||
} |
||||
|
||||
$container->get('Foo', [], [ |
||||
'bar' => $container->get('Bar'), |
||||
'qux' => $container->get('Qux'), |
||||
]); |
||||
``` |
||||
|
||||
> Informação: O método [[yii\di\Container::get()]] recebe no seu terceiro parâmetro um array de configuração que deve ser aplicado ao objecto a ser criado. Se a classe implementa a interface [[yii\base\Configurable]] (por exemplo [[yii\base\Object]]), o array de configuração será passado como o último parâmetro para o construtor da classe; caso contrário, a configuração será aplicada *depois* que o objeto for criado. |
||||
|
||||
|
||||
### Injeção de PHP Callable <span id="php-callable-injection"></span> |
||||
|
||||
Neste caso, o container usará um PHP callable registrado para criar novas instâncias da classe. |
||||
Cada vez que [[yii\di\Container::get()]] é chamado, o callable correspondente será invocado. |
||||
O callable é responsável por resolver as dependências e injetá-las de forma adequada para os objetos recém-criados. Por exemplo, |
||||
|
||||
```php |
||||
$container->set('Foo', function () { |
||||
$foo = new Foo(new Bar); |
||||
// ... Outras inicializações... |
||||
return $foo; |
||||
}); |
||||
|
||||
$foo = $container->get('Foo'); |
||||
``` |
||||
|
||||
Para ocultar a lógica complexa da construção de um novo objeto você pode usar um método estático de classe para retornar o PHP callable. Por Exemplo, |
||||
|
||||
```php |
||||
class FooBuilder |
||||
{ |
||||
public static function build() |
||||
{ |
||||
return function () { |
||||
$foo = new Foo(new Bar); |
||||
// ... Outras inicializações... |
||||
return $foo; |
||||
}; |
||||
} |
||||
} |
||||
|
||||
$container->set('Foo', FooBuilder::build()); |
||||
|
||||
$foo = $container->get('Foo'); |
||||
``` |
||||
|
||||
Como você pode ver, o PHP callable é retornado pelo método `FooBuilder::build()`.Ao fazê-lo, quem precisar configurar a classe `Foo` não precisará saber como ele é construído. |
||||
|
||||
|
||||
Registrando Dependências <span id="registering-dependencies"></span> |
||||
------------------------ |
||||
|
||||
Você pode usar [[yii\di\Container::set()]] para registrar dependências. O registro requer um nome de dependência, bem como uma definição de dependência. Um nome de dependência pode ser um nome de classe, um nome de interface, ou um alias; e a definição de dependência pode ser um nome de classe, um array de configuração ou um PHP callable. |
||||
|
||||
```php |
||||
$container = new \yii\di\Container; |
||||
|
||||
// registrar um nome de classe. Isso pode ser ignorado. |
||||
$container->set('yii\db\Connection'); |
||||
|
||||
// registrar uma interface |
||||
// Quando uma classe depende da interface, a classe correspondente |
||||
// será instanciada como o objeto dependente |
||||
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); |
||||
|
||||
// registrar um alias. Você pode utilizar $container->get('foo') |
||||
// para criar uma instância de Connection |
||||
$container->set('foo', 'yii\db\Connection'); |
||||
|
||||
// registrar uma classe com configuração. A configuração |
||||
// será aplicada quando quando a classe for instanciada pelo get() |
||||
$container->set('yii\db\Connection', [ |
||||
'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||
'username' => 'root', |
||||
'password' => '', |
||||
'charset' => 'utf8', |
||||
]); |
||||
|
||||
// registrar um alias com a configuração de classe |
||||
// neste caso, um elemento "class" é requerido para especificar a classe |
||||
$container->set('db', [ |
||||
'class' => 'yii\db\Connection', |
||||
'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||
'username' => 'root', |
||||
'password' => '', |
||||
'charset' => 'utf8', |
||||
]); |
||||
|
||||
// registrar um PHP callable |
||||
// O callable será executado sempre quando $container->get('db') for chamado |
||||
$container->set('db', function ($container, $params, $config) { |
||||
return new \yii\db\Connection($config); |
||||
}); |
||||
|
||||
// registrar uma instância de componente |
||||
// $container->get('pageCache') retornará a mesma instância toda vez que for chamada |
||||
$container->set('pageCache', new FileCache); |
||||
``` |
||||
|
||||
> dica: Se um nome de dependência é o mesmo que a definição de dependência correspondente, você não precisa registrá-lo no container DI |
||||
|
||||
Um registro de dependência através de `set()`irá gerar uma instância a cada vez que a dependência for necessária. Você pode usar [[yii\di\Container::setSingleton()]] para registrar a dependência de forma a gerar apenas uma única instância: |
||||
|
||||
```php |
||||
$container->setSingleton('yii\db\Connection', [ |
||||
'dsn' => 'mysql:host=127.0.0.1;dbname=demo', |
||||
'username' => 'root', |
||||
'password' => '', |
||||
'charset' => 'utf8', |
||||
]); |
||||
``` |
||||
|
||||
|
||||
Resolvendo Dependências <span id="resolving-dependencies"></span> |
||||
---------------------- |
||||
|
||||
Depois de registrar as dependências, você pode usar o container DI para criar novos objetos, |
||||
E o container resolverá automaticamente as dependências instanciando e as injetando dentro do novo objeto criado. A resolução de dependência é recursiva, isso significa que |
||||
se uma dependência tem outras dependências, essas dependências também serão resolvidas automaticamente. |
||||
|
||||
Você pode usar [[yii\di\Container::get()]] para criar novos objetos. O método recebe um nome de dependência, que pode ser um nome de classe, um nome de interface ou um alias. O nome de dependência Pode ou não ser registrado através de `set()` ou `setSingleton()`.Você pode, opcionalmente, fornecer uma lista de parâmetros de construtor de classe e uma [configuração](concept-configurations.md) para configurara o novo objeto criado. |
||||
Por exemplo, |
||||
|
||||
```php |
||||
// "db" é um alias registrado previamente |
||||
$db = $container->get('db'); |
||||
|
||||
// equivale a: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]); |
||||
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]); |
||||
``` |
||||
|
||||
Nos bastidores, o container DI faz muito mais do que apenas a criação de um novo objeto. |
||||
O container irá inspecionar o primeiramente o construtor da classe para descobrir classes ou interfaces dependentes e automaticamente resolver estas dependências recursivamente. |
||||
O código abaixo mostra um exemplo mais sofisticado. A classe `UserLister` depende de um objeto que implementa a interface `UserFinderInterface`; A Classe `UserFinder` implementa esta interface e depende do objeto `Connection`. Todas estas dependências são declaradas através de type hinting dos parâmetros do construtor da classe. Com o registro de dependência de propriedade, o container DI é capaz de resolver estas dependências automaticamente e cria uma nova instância de `UserLister` simplesmente com `get('userLister')`. |
||||
|
||||
```php |
||||
namespace app\models; |
||||
|
||||
use yii\base\Object; |
||||
use yii\db\Connection; |
||||
use yii\di\Container; |
||||
|
||||
interface UserFinderInterface |
||||
{ |
||||
function findUser(); |
||||
} |
||||
|
||||
class UserFinder extends Object implements UserFinderInterface |
||||
{ |
||||
public $db; |
||||
|
||||
public function __construct(Connection $db, $config = []) |
||||
{ |
||||
$this->db = $db; |
||||
parent::__construct($config); |
||||
} |
||||
|
||||
public function findUser() |
||||
{ |
||||
} |
||||
} |
||||
|
||||
class UserLister extends Object |
||||
{ |
||||
public $finder; |
||||
|
||||
public function __construct(UserFinderInterface $finder, $config = []) |
||||
{ |
||||
$this->finder = $finder; |
||||
parent::__construct($config); |
||||
} |
||||
} |
||||
|
||||
$container = new Container; |
||||
$container->set('yii\db\Connection', [ |
||||
'dsn' => '...', |
||||
]); |
||||
$container->set('app\models\UserFinderInterface', [ |
||||
'class' => 'app\models\UserFinder', |
||||
]); |
||||
$container->set('userLister', 'app\models\UserLister'); |
||||
|
||||
$lister = $container->get('userLister'); |
||||
|
||||
// que é equivalente a: |
||||
|
||||
$db = new \yii\db\Connection(['dsn' => '...']); |
||||
$finder = new UserFinder($db); |
||||
$lister = new UserLister($finder); |
||||
``` |
||||
|
||||
|
||||
Uso Prático <span id="practical-usage"></span> |
||||
--------------- |
||||
|
||||
Yii cria um container DI quando você inclui o arquivo `Yii.php` no [script de entrada](structure-entry-scripts.md) da sua aplicação. O container DI é acessível através do [[Yii::$container]]. Quando você executa o método [[Yii::createObject()]], Na verdade o que será realmente executado é o método [[yii\di\Container::get()|get()]] do container para criar um novo objeto. |
||||
Conforme já informado acima, o container DI resolverá automaticamente as dependências (se existir) e as injeta dentro do novo objeto criado. Como Yii utiliza [[Yii::createObject()]] na maior parte do seu código principal para criar novos objetos, isso significa que você pode personalizar os objetos globalmente lidando com [[Yii::$container]]. |
||||
|
||||
Por exemplo, você pode customizar globalmente o número padrão de botões de paginação do [[yii\widgets\LinkPager]]: |
||||
|
||||
```php |
||||
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); |
||||
``` |
||||
|
||||
Agora, se você usar o widget na view (visão) com o seguinte código, a propriedade `maxButtonCount` será inicializado como 5 em lugar do valor padrão 10 como definido na class. |
||||
|
||||
```php |
||||
echo \yii\widgets\LinkPager::widget(); |
||||
``` |
||||
Todavia, você ainda pode substituir o valor definido através container DI: |
||||
|
||||
```php |
||||
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); |
||||
``` |
||||
|
||||
Outro exemplo é se beneficiar da injecção automática de construtor do container DI. Assumindo que a sua classe controller (controlador) depende de alguns outros objetos, tais como um serviço de reserva de um hotel. |
||||
|
||||
Você pode declarar a dependência através de um parâmetro de construtor e deixar o container DI resolver isto para você. |
||||
|
||||
```php |
||||
namespace app\controllers; |
||||
|
||||
use yii\web\Controller; |
||||
use app\components\BookingInterface; |
||||
|
||||
class HotelController extends Controller |
||||
{ |
||||
protected $bookingService; |
||||
|
||||
public function __construct($id, $module, BookingInterface $bookingService, $config = []) |
||||
{ |
||||
$this->bookingService = $bookingService; |
||||
parent::__construct($id, $module, $config); |
||||
} |
||||
} |
||||
``` |
||||
|
||||
Se você acessar este controller(controlador) a partir de um navegador, você vai ver um erro informando que `BookingInterface` não pode ser instanciado. Isso ocorre porque você precisa dizer ao container DI como lidar com esta dependência: |
||||
|
||||
```php |
||||
\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService'); |
||||
``` |
||||
|
||||
Agora se você acessar o controller(controlador) novamente, uma instância de `app\components\BookingService` será criada e injetada como o terceiro parâmetro do construtor do controller(controlador). |
||||
|
||||
|
||||
Quando registrar Dependência <span id="when-to-register-dependencies"></span> |
||||
----------------------------- |
||||
|
||||
Em função de existirem dependências na criação de novos objetos, o seu registo deve ser feito o mais cedo possível. Seguem abaixo algumas práticas recomendadas: |
||||
|
||||
* Se você é o desenvolvedor de uma aplicação, você pode registrar dependências no [script de entrada] (structure-entry-scripts.md) da sua aplicação ou em um script incluído no script de entrada. |
||||
* Se você é um desenvolvedor de [extensão](structure-extensions.md), você pode registrar as dependências no bootstrapping(inicialização) da classe da sua extensão. |
||||
|
||||
Resumo <span id="summary"></span> |
||||
------- |
||||
|
||||
Ambos injeção de dependência e [service locator](concept-service-locator.md) são padrões de projetos conhecidos que permitem a construção de software com alta coesão e baixo acoplamento. É altamente recomendável que você leia |
||||
[Martin's article](http://martinfowler.com/articles/injection.html) para obter uma compreensão mais profunda da injeção de dependência e service locator. |
||||
|
||||
Yii implementa o [service locator](concept-service-locator.md) no topo da injecção dependência container (DI). |
||||
Quando um service locator tenta criar uma nova instância de objeto, ele irá encaminhar a chamada para o container DI. |
||||
Este último vai resolver as dependências automaticamente tal como descrito acima. |
||||
|
Loading…
Reference in new issue