Injeção de Dependência Automática
No Webman, a injeção de dependência automática é uma funcionalidade opcional, que está desativada por padrão. Se você precisar da injeção de dependência automática, é recomendado utilizar o php-di. A seguir, apresentamos como integrar o Webman com o php-di
.
Instalação
composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14
Modifique a configuração config/container.php
, o conteúdo final deve ser como abaixo:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
O arquivo
config/container.php
retorna uma instância de contêiner que está em conformidade com a especificaçãoPSR-11
. Se você não deseja usar ophp-di
, pode criar e retornar outra instância de contêiner que esteja em conformidade comPSR-11
.
Injeção pelo Construtor
Crie o arquivo app/service/Mailer.php
(se o diretório não existir, crie por conta própria) com o conteúdo a seguir:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// Código para enviar email omitido
}
}
O arquivo app/controller/UserController.php
deve conter o seguinte:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Normalmente, o seguinte código é necessário para completar a instanciação de app\controller\UserController
:
$mailer = new Mailer;
$user = new UserController($mailer);
Com o uso do php-di
, o desenvolvedor não precisa instanciar manualmente o Mailer
, o Webman cuida disso automaticamente. Se a instanciação do Mailer
requerer a injeção de outras classes, o Webman também fará a injeção automaticamente. O desenvolvedor não precisa realizar nenhuma operação de inicialização.
Atenção
Apenas instâncias criadas pelo framework ouphp-di
podem completar a injeção de dependência automática. Instâncias criadas manualmente comnew
não conseguirão realizar a injeção de dependência; para injeção, deve-se usar a interfacesupport\Container
para substituir as instruçõesnew
, por exemplo:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// Instâncias criadas com a palavra-chave new não conseguem realizar injeção de dependência
$user_service = new UserService;
// Instâncias criadas com a palavra-chave new não conseguem realizar injeção de dependência
$log_service = new LogService($path, $name);
// Instâncias criadas com Container podem realizar injeção de dependência
$user_service = Container::get(UserService::class);
// Instâncias criadas com Container podem realizar injeção de dependência
$log_service = Container::make(LogService::class, [$path, $name]);
Injeção por Anotações
Além da injeção automática via construtor, também podemos usar a injeção por anotações. Continuando o exemplo anterior, modifique app\controller\UserController
para o seguinte:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Este exemplo utiliza a anotação @Inject
para a injeção, e a anotação @var
para declarar o tipo do objeto. Este exemplo produz o mesmo efeito que a injeção por construtor, mas com um código mais conciso.
Atenção
O Webman não suporta a injeção de parâmetros em controladores antes da versão 1.4.6; por exemplo, o seguinte código não será suportado quando o Webman for <=1.4.6:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// A injeção de parâmetros em controladores não é suportada antes da versão 1.4.6
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Injeção Personalizada por Construtor
Às vezes, os parâmetros passados para o construtor podem não ser instâncias de classe, mas sim strings, números, arrays, etc. Por exemplo, o construtor do Mailer pode precisar receber o IP e a porta do servidor SMTP:
<?php
namespace app\service;
class Mailer
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// Código para enviar email omitido
}
}
Nessa situação, não é possível usar diretamente a injeção automática do construtor como mencionado anteriormente, pois o php-di
não consegue determinar quais são os valores de $smtp_host
e $smtp_port
. Podemos tentar realizar uma injeção personalizada.
No arquivo config/dependence.php
(caso o arquivo não exista, crie-o) adicione o seguinte código:
return [
// ... outras configurações omitidas
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
Assim, quando a injeção de dependência precisar de uma instância de app\service\Mailer
, a instância criada por esta configuração será automaticamente utilizada.
Notamos que, em config/dependence.php
, o uso de new
para instanciar a classe Mailer
não apresenta problemas neste exemplo. Porém, imagine que a classe Mailer
depende de outras classes ou que utiliza injeção por anotações: usar new
para inicializar não permitirá a injeção automática. A solução é utilizar a injeção através de interfaces personalizadas, utilizando Container::get(classe)
ou Container::make(classe, [parâmetros do construtor])
para inicializar a classe.
Injeção por Interface Personalizada
Em projetos reais, preferimos programar voltados para interfaces, em vez de classes concretas. Por exemplo, o app\controller\UserController
deve importar app\service\MailerInterface
em vez de app\service\Mailer
.
Defina a interface MailerInterface
.
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
Defina a implementação da interface MailerInterface
.
<?php
namespace app\service;
class Mailer implements MailerInterface
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// Código para enviar email omitido
}
}
Importe a interface MailerInterface
em vez da implementação concreta.
<?php
namespace app\controller;
use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var MailerInterface
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
No arquivo config/dependence.php
, defina a interface MailerInterface
com a seguinte implementação.
use Psr\Container\ContainerInterface;
return [
app\service\MailerInterface::class => function(ContainerInterface $container) {
return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
}
];
Assim, quando for necessário usar a interface MailerInterface
, a implementação Mailer
será utilizada automaticamente.
A vantagem de programar orientado a interfaces é que, quando precisamos substituir um componente, não precisamos alterar o código de negócios, apenas a implementação concreta no
config/dependence.php
. Isso também é muito útil para testes unitários.
Outras Injeções Personalizadas
O config/dependence.php
não só pode definir dependências de classes, mas também outros valores, como strings, números, arrays, etc.
Por exemplo, config/dependence.php
pode ser definido como abaixo:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
Nesse caso, podemos usar @Inject
para injetar smtp_host
e smtp_port
nas propriedades da classe.
<?php
namespace app\service;
use DI\Annotation\Inject;
class Mailer
{
/**
* @Inject("smtp_host")
*/
private $smtpHost;
/**
* @Inject("smtp_port")
*/
private $smtpPort;
public function mail($email, $content)
{
// Código para enviar email omitido
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Exibirá 192.168.1.11:25
}
}
Atenção:
@Inject("key")
deve ser escrito em aspas duplas.
Mais Conteúdo
Consulte o manual do php-di para mais informações.