Injeção de Dependência (DI) e Inversão de Controle (IoC) no Laravel
E aí galera, tudo bem? Vamos lá?
Como de costume, vou presumir que você tenha já algum conhecimento básico em PHP, MVC e POO… Esse assuntos são pre requisitos para entender muito do funcionamento do Laravel.
Contêiner de Serviço (Service Container)
Basicamente, Service Container (ou IoC Container) é uma ferramenta, centralizada, para gerenciar as dependências de classe e executar a injeção de dependência (Dependency Injection).
Uma compreensão mais aprofundada do Laravel Service Container se faz necessária para que você seja capaz de construir aplicações escaláveis, com baixo acoplamento e também, caso queira, contribuir com o próprio Laravel.
Vamos a um exemplo:
<?php
namespace App\Http\Controllers;
use App\Category;
use App\Repositories\CategoryRepository;
use App\Http\Controllers\Controller;
class CategoryController extends Controller
{
/**
* The category repository implementation.
*
* @var CategoryRepository
*/
protected $categories;
/**
* Create a new controller instance.
*
* @param CategoryRepository $categories
* @return void
*/
public function __construct(CategoryRepository $categories)
{
$this->categories = $categories;
}
/**
* Show the details for the given category.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$category = $this->categories->find($id);
return view('category.detail', ['category' => $category]);
}
}
No exemplo o controller de categoria precisa buscar em uma fonte de dados, os detalhes de uma categoria. Sendo assim, injetamos um serviço (service) apto a recuperar as informações necessárias, no caso o CategoryRepository
.
Como ele foi injetado, caso precisemos trocar por outra implementação, não necessitaremos alterar o código do controller. Outra vantagem é que podemos simular (Mocking) ou até mesmo criar uma implementação fictícia de CategoryRepository
para fins de testes.
Para quem não é tão familiarizado como termo injetado, deve entender que quem usa o objeto injetado não é o responsável pela criação do mesmo. Inclusive no caso do Laravel quem faz isso é justamente o Service Container.
Por dentro do Service Container
No Laravel apenas as classes que dependem de uma interface devem ser vinculadas ao Service Container, isso para instruí-lo como as classes devem ser construídas. No caso de classes independentes de interface o Laravel as resolve através de Reflection.
Agora vou mostrar 3 das várias maneiras que a injeção de dependência pode ser realizada no Service Container do Laravel.
Vínculo Simples
Dentro de um Service Provider, você sempre pode acessar o Service Container através da propriedade $this->app
. Podemos usar o método bind, passando a classe ou o nome da interface que desejamos registrar junto com um Closure que retorna uma instância da classe, desta forma:
$this->app->bind('Repositories\CategoryRepositoryInterface', function($app) {
return new Repositories\CategoryRepository();
});
Vinculando as Interfaces com suas implementações
Outra forma de realizar a instanciação é através da capacidade do Service Container vincular uma interface a um determinada implementação. Um exemplo seria algo como: dado que temos uma interface HumanRepositoryInterface
e uma implementação ManRepository
podemos registrar isso da seguinte maneira:
$this->app->bind(
'App\Contracts\HumanRepositoryInterface',
'App\Repositories\ManRepository'
);
Essa instrução informa ao Service Container que deve injetar o ManRepository
sempre que uma classe precisar de uma implementação do HumanRepositoryInterface
. Agora podemos digitar a interface HumanRepositoryInterace
em um construtor ou em qualquer outro local onde as dependências são injetadas pelo Service Container:
use App\Contracts\HumanRepositoryInterface;
/**
* Create a new class instance.
*
* @param HumanRepositoryInterface $human
* @return void
*/
public function __construct(HumanRepositoryInterface $human)
{
$this->human= $human;
}
Vínculo Contextual
A terceira e última maneira que irei mostrar é o vínculo contextual, no exemplo anterior usamos a interface HumanRepositoryInterface
a qual pode possuir mais de uma implementação, por exemplo ManRepository
e WomanRepository
.
O nosso contexto seria que temos dois controllers (o ManController
e o WomanController
) cada um dependendo de uma das implementações específicas, então no Service Container podemos fazer o seguinte:
use App\Http\Controllers\ManController;
use App\Http\Controllers\WomanController;
use Illuminate\Contracts\HumanRepositoryInterface;
use Illuminate\Contracts\ManRepository;
use Illuminate\Contracts\WomanRepository;$this->app->when(ManController::class)
->needs(HumanRepositoryInterface::class)
->give(ManRepository::class);// Ou$this->app->when(WomanController::class)
->needs(HumanRepositoryInterface::class)
->give(function() {
return New WomanRepository();
});
E o uso desse modo de vincular seria:
<?php
namespace App\Http\Controllers;
use App\Category;
use App\Repositories\HumanRepositoryInterface;
use App\Http\Controllers\Controller;
class WomanController extends Controller
{
/**
* The woman repository implementation.
*
* @var HumanRepositoryInterface
*/
protected $human;
/**
* Create a new controller instance.
*
* @param HumanRepositoryInterface $human
* @return void
*/
public function __construct(HumanRepositoryInterface $human)
{
$this->human= $human;
}
/**
* Show the details for the given woman.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$woman= $this->human->find($id);
return view('woman.detail', ['woman' => $woman]);
}
}
Bem legal, certo? Fazer um código altamente desacoplado, manutenível e fácil de testar… por hoje eu vou parando por aqui… até a próxima. 😎
Por favor, não deixem de compartilhar, comentar e deixar seu feedback.
Referências