El componente Inyección de dependencias

El componente Inyección de dependencias, te permite estandarizar y centralizar la forma en que se construyen los objetos en tu aplicación.

Para una introducción general a los contenedores de inyección de dependencias y servicios consulta el capítulo Contenedor de servicios del libro.

Instalando

Puedes instalar el componente de varias maneras diferentes:

Uso básico

Tal vez tengas una simple clase Mailer como la siguiente, la cual quieres hacer disponible como un servicio:

class Mailer
{
    private $transport;

    public function __construct()
    {
        $this->transport = 'sendmail';
    }

    // ...
}

La puedes registrar en el contenedor como un servicio:

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('mailer', 'Mailer');

Una mejora a la clase para hacerla más flexible sería permitir que el contenedor establezca el transporte utilizado. Si cambias la clase para que esta sea pasada al constructor:

class Mailer
{
    private $transport;

    public function __construct($transport)
    {
        $this->transport = $transport;
    }

    // ...
}

Entonces, puedes configurar la opción de transporte en el contenedor:

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container
    ->register('mailer', 'Mailer')
    ->addArgument('sendmail');

Esta clase ahora es mucho más flexible puesto que separamos la elección del transporte de la implementación y la colocamos en el contenedor.

Cual transporte de correo has elegido, puede ser algo sobre lo cual los demás servicios necesiten saber. Puedes evitar tener que cambiarlo en varios lugares volviéndolo un parámetro en el contenedor y luego refiriendo este parámetro como argumento en el constructor del servicio Mailer:

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

Ahora que el servicio mailer está en el contenedor lo puedes inyectar como una dependencia de otras clases. Si tienes una clase NewsletterManager como esta:

class NewsletterManager
{
    private $mailer;

    public function __construct(\Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    // ...
}

Entonces, también la puedes registrar como un servicio y pasarla al servicio mailer:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

$container = new ContainerBuilder();

$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addArgument(new Reference('mailer'));

Si el NewsletterManager no requiere el Mailer y la inyección sólo era opcional, entonces, en su lugar puedes utilizar el método definidor para inyectarlo:

class NewsletterManager
{
    private $mailer;

    public function setMailer(\Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    // ...
}

Ahora puedes optar por no inyectar un Mailer en el NewsletterManager. Aún así, si quieres entonces el contenedor puede llamar al método definidor:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

$container = new ContainerBuilder();

$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addMethodCall('setMailer', array(new Reference('mailer')));

A continuación, puedes obtener tu servicio newsletter_manager desde el contenedor de esta manera:

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();

// ...

$newsletterManager = $container->get('newsletter_manager');

Evitando que tu código sea dependiente en el contenedor

Si bien puedes recuperar los servicios directamente desde el contenedor, lo mejor es minimizar esto. Por ejemplo, en el NewsletterManager inyectamos el servicio Mailer en vez de solicitarlo desde el contenedor. Podríamos haber inyectado el contenedor y recuperar el servicio Mailer desde ahí, pero entonces, estaría vinculado a ese contenedor particular, lo cual dificulta la reutilización de la clase en cualquier otro lugar.

Tendrás que conseguir un servicio desde el contenedor en algún momento, pero esto debe ser tan pocas veces como sea posible en el punto de entrada a tu aplicación.

Configurando el contenedor con archivos de configuración

As well as setting up the services using PHP as above you can also use configuration files. This allows you to use XML or Yaml to write the definitions for the services rather than using PHP to define the services as in the above examples. In anything but the smallest applications it make sense to organize the service definitions by moving them into one or more configuration files. To do this you also need to install the Config Component.

Cargando un archivo de configuración XML:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');

Cargando un archivo de configuración YAML:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');

Nota

Si quieres cargar archivos de configuración YAML entonces también necesitarás instalar el componente YAML.

If you do want to use PHP to create the services then you can move this into a separate config file and load it in a similar way:

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');

You can now set up the newsletter_manager and mailer services using config files:

  • YAML
    parameters:
        # ...
        mailer.transport: sendmail
    
    services:
        mailer:
            class:     Mailer
            arguments: ["%mailer.transport%"]
        newsletter_manager:
            class:     NewsletterManager
            calls:
                - [setMailer, ["@mailer"]]
    
  • XML
    <parameters>
        <!-- ... -->
        <parameter key="mailer.transport">sendmail</parameter>
    </parameters>
    
        <services>
        <service id="mailer" class="Mailer">
            <argument>%mailer.transport%</argument>
        </service>
    
        <service id="newsletter_manager" class="NewsletterManager">
            <call method="setMailer">
                 <argument type="service" id="mailer" />
            </call>
        </service>
        </services>
    
  • PHP
    use Symfony\Component\DependencyInjection\Reference;
    
    // ...
    $container->setParameter('mailer.transport', 'sendmail');
    $container
        ->register('mailer', 'Mailer')
        ->addArgument('%mailer.transport%');
    
    $container
        ->register('newsletter_manager', 'NewsletterManager')
        ->addMethodCall('setMailer', array(new Reference('mailer')));
    
Bifúrcame en GitHub