Inyección automática de dependencias
En webman, la inyección automática de dependencias es una característica opcional que está deshabilitada de forma predeterminada. Si necesitas utilizar la inyección automática de dependencias, se recomienda usar php-di. A continuación se muestra cómo se utiliza php-di
en combinación con webman.
Instalación
composer require psr/container ^1.1.1 php-di/php-di ^6 doctrine/annotations ^1.14
Modifica el archivo de configuración config/container.php
de la siguiente manera:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
El archivo
config/container.php
debe devolver una instancia de contenedor que cumpla con la especificaciónPSR-11
. Si no deseas utilizarphp-di
, puedes crear y devolver otra instancia que cumpla con la especificaciónPSR-11
.
Inyección a través de constructores
Crea el archivo app/service/Mailer.php
(crea el directorio si no existe) con el siguiente contenido:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// Código para enviar correo electrónico omitido
}
}
El contenido del archivo app/controller/UserController.php
sería:
<?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', '¡Hola y bienvenido!');
return response('ok');
}
}
Normalmente, para instanciar app\controller\UserController
, se necesitaría el siguiente código:
$mailer = new Mailer;
$user = new UserController($mailer);
Sin embargo, al utilizar php-di
, el desarrollador no necesita instanciar manualmente Mailer
en el controlador, ya que webman lo hace automáticamente. Además, si durante la instancia de Mailer
existe alguna dependencia, webman la instanciará e inyectará automáticamente. El desarrollador no necesita realizar ningún trabajo adicional de inicialización.
Nota
Solo las instancias creadas por el framework ophp-di
pueden implementar la inyección automática de dependencias. Las instancias creadas manualmente connew
no pueden utilizar la inyección automática de dependencias. Si es necesario la inyección, se debe utilizar la interfazsupport\Container
en lugar de la declaraciónnew
, por ejemplo:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// Las instancias creadas con la palabra clave 'new' no pueden ser inyectadas
$user_service = new UserService;
// Las instancias creadas con la palabra clave 'new' no pueden ser inyectadas
$log_service = new LogService($path, $name);
// Las instancias creadas con el contenedor pueden ser inyectadas
$user_service = Container::get(UserService::class);
// Las instancias creadas con el contenedor pueden ser inyectadas
$log_service = Container::make(LogService::class, [$path, $name]);
Inyección a través de anotaciones
Además de la inyección de dependencias a través de constructores, también se puede utilizar la inyección a través de anotaciones. Continuando con el ejemplo anterior, el archivo app/controller/UserController
sería modificado de la siguiente manera:
<?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', '¡Hola y bienvenido!');
return response('ok');
}
}
En este ejemplo, se utiliza la inyección a través de la anotación @Inject
, junto con la anotación @var
para declarar el tipo de objeto. El efecto de este ejemplo es similar a la inyección a través de constructores, pero el código es más conciso.
Nota
Antes de la versión 1.4.6, webman no admitía la inyección de parámetros del controlador, por lo tanto, el siguiente código no sería compatible con webman <= 1.4.6:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// Antes de la versión 1.4.6, no se admitía la inyección de parámetros del controlador
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', '¡Hola y bienvenido!');
return response('ok');
}
}
Inyección personalizada a través de constructores
En ocasiones, los parámetros pasados al constructor pueden ser de tipos como cadenas, números, arreglos, etc. Por ejemplo, el constructor de la clase Mailer
requiere la dirección IP y el puerto del 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 correo electrónico omitido
}
}
En este caso, no es posible utilizar la inyección automática de constructores mencionada anteriormente, ya que php-di
no puede determinar los valores de $smtp_host
y $smtp_port
. En este caso, se puede intentar utilizar una inyección personalizada.
Agrega el siguiente contenido al archivo config/dependence.php
(crea el archivo si no existe):
return [
// ... Se omitieron otras configuraciones
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
De esta manera, cuando la inyección de dependencias necesita obtener una instancia de app\service\Mailer
, se utilizará automáticamente la instancia de app\service\Mailer
creada en esta configuración.
Observa que en config/dependence.php
se usó new
para instanciar la clase Mailer
. En este ejemplo no hay problema, pero si la clase Mailer
tiene dependencias de otras clases o utiliza la inyección a través de anotaciones, la inicialización con new
no permitirá la inyección automática de dependencias. La solución es utilizar la inyección personalizada a través de la interfaz Container::get(class_name)
o el método Container::make(class_name, [constructor_parameters])
en lugar de la declaración new
.
Inyección personalizada a través de interfaces
En proyectos reales, es preferible programar orientado a interfaces en lugar de a clases concretas. Por ejemplo, en app\controller\UserController
, se debería hacer referencia a app\service\MailerInterface
en lugar de app\service\Mailer
.
Definimos la interfaz MailerInterface
de la siguiente manera:
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
Implementamos la interfaz MailerInterface
de la siguiente manera:
<?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 correo electrónico omitido
}
}
En el archivo app\controller\UserController
, hacemos referencia a la interfaz MailerInterface
en lugar de la implementación 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', '¡Hola y bienvenido!');
return response('ok');
}
}
En el archivo config/dependence.php
, se define la implementación de la interfaz MailerInterface
de la siguiente manera:
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]);
}
];
De esta manera, cuando se necesite utilizar la interfaz MailerInterface
en el proyecto, se utilizará automáticamente la implementación de Mailer
. Una de las ventajas de programar orientado a interfaces es que, al cambiar un componente, no es necesario modificar el código del proyecto, simplemente se debe cambiar la implementación concreta en config/dependence.php
. Esto es muy útil también para realizar pruebas unitarias.
Otras inyecciones personalizadas
Además de definir las dependencias de clases, config/dependence.php
también puede definir otros valores, como cadenas de texto, números, matrices, etc.
Por ejemplo, si definimos lo siguiente en config/dependence.php
:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
En este caso, podemos inyectar smtp_host
y smtp_port
en las propiedades de la clase utilizando @Inject
.
<?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 correos electrónicos omitido
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Imprimirá 192.168.1.11:25
}
}
Nota: Dentro de
@Inject("clave")
, se utilizan comillas dobles
Más información
Consulta el manual de php-di para obtener más detalles.