Hacer dependencias de una clase explícita y requerir que se inyecte es una buena manera de hacer una clase más reutilizable, comprobable y desacoplada de las demás.
Hay varias maneras en que se pueden inyectar dependencias. Cada punto de inyección tiene sus ventajas y desventajas a considerar, así como las diferentes formas de trabajar con ellas cuando se utiliza el contenedor de servicios.
La manera más común para inyectar dependencias es a través del constructor de la clase. Para ello es necesario añadir un argumento a la firma del constructor para aceptar la dependencia:
class NewsletterManager
{
protected $mailer;
public function __construct(\Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
Puedes especificar qué tipo de servicio deseas inyectar en la configuración del contenedor de servicios:
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
arguments: ["@my_mailer"]
<services>
<service id="my_mailer" ... >
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<argument type="service" id="my_mailer"/>
</service>
</services>
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager',
array(new Reference('my_mailer'))
));
Truco
Aludir el tipo de objeto a inyectar significa que puedes estar seguro de haber inyectado la dependencia adecuada. Al aludir el tipo, de inmediato obtendrás un error claro si inyectas una dependencia inadecuada. Al aludir el tipo usando una interfaz en lugar de una clase puedes hacer más flexible la elección de la dependencia. Y suponiendo que sólo utilizas los métodos definidos en la interfaz, te puedes beneficiar de la flexibilidad y seguridad al utilizar el objeto.
Hay varias ventajas al utilizar la inyección en el constructor:
Estas ventajas no significan que la inyección en el constructor no sea adecuada para trabajar con dependencias opcionales. Ademas, es más difícil usar en combinación con jerarquías de clase: si una clase utiliza la inyección en el constructor entonces extender y reemplazar el constructor se vuelve problemático.
Otro posible punto de inyección en una clase es añadiendo un método definidor que acepte la dependencia:
class NewsletterManager
{
protected $mailer;
public function setMailer(\Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
calls:
- [setMailer, ["@my_mailer"]]
<services>
<service id="my_mailer" ... >
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
</service>
</services>
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager'
))->addMethodCall('setMailer', array(new Reference('my_mailer')));
Esta vez las ventajas son:
Las desventajas de la inyección en definidor son:
Otra posibilidad es sólo configurar campos públicos en la clase directamente:
class NewsletterManager
{
public $mailer;
// ...
}
services:
my_mailer:
# ...
newsletter_manager:
class: RNewsletterManager
properties:
mailer: "@my_mailer"
<services>
<service id="my_mailer" ... >
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<property name="mailer" type="service" id="my_mailer" />
</service>
</services>
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager'
))->setProperty('mailer', new Reference('my_mailer')));
Hay desventajas, principalmente sólo para usar la inyección de la propiedad, es similar a la inyección del definidor, pero con estos importantes problemas adicionales:
Sin embargo, es útil saber que esto se puede hacer en el contenedor de servicios, sobre todo si trabajas con código que está fuera de tu control, como en una biblioteca de terceros, que utiliza propiedades públicas para sus dependencias.