Symfony 2.1, el libro oficial

16.5. Importando otros recursos para configurar el contenedor

Truco En esta sección, nos referiremos a los archivos de configuración de servicios como recursos. Esto se hace para resaltar el hecho de que, si bien la mayoría de la configuración se realiza mediante archivos (en formato YAML, XML o PHP), Symfony2 es tan flexible que la configuración se puede cargar desde otros lugares (por ejemplo, una base de datos e incluso a través de un servicio web).

El contenedor de servicios se construye usando un único recurso de configuración (el archivo app/config/config.yml por defecto). Toda la configuración de los servicios (incluido el núcleo de Symfony2 y la configuración de los bundles) se debe importar desde este archivo de una manera u otra. Esto proporciona una flexibilidad absoluta sobre los servicios en tu aplicación.

La configuración externa de servicios se puede importar de dos maneras diferentes. La primera, y más común, es mediante la directiva imports. Más adelante se explica la segunda forma, que es más flexible y es la opción preferida para importar la configuración de los bundles de terceros.

16.5.1. Importando configuración con imports

En los ejemplos anteriores, la definición del servicio my_mailer se escribía directamente en el archivo de configuración de la aplicación (por ejemplo, app/config/config.yml). Como la clase Mailer se encuentra dentro del bundle AcmeHelloBundle, tiene más sentido poner la definición del servicio my_mailer dentro de su propio bundle.

En primer lugar, mueve la configuración del servicio my_mailer a un nuevo archivo llamado Resources/config/services.yml dentro de AcmeHelloBundle. Si los directorios Resources y Resources/config no existen, créalos.

# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    my_mailer.class:      Acme\HelloBundle\Mailer
    my_mailer.transport:  sendmail

services:
    my_mailer:
        class:        "%my_mailer.class%"
        arguments:    ["%my_mailer.transport%"]
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<parameters>
    <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
    <parameter key="my_mailer.transport">sendmail</parameter>
</parameters>

<services>
    <service id="my_mailer" class="%my_mailer.class%">
        <argument>%my_mailer.transport%</argument>
    </service>
</services>
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;

$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
$container->setParameter('my_mailer.transport', 'sendmail');

$container->setDefinition('my_mailer', new Definition(
    '%my_mailer.class%',
    array('%my_mailer.transport%')
));

La configuración no ha cambiado, sólo su ubicación. El problema es que ahora el contenedor de servicios no es consciente de la existencia de este nuevo recurso. Afortunadamente, es fácil importar este nuevo archivo utilizando la clave imports en la configuración de la aplicación.

# app/config/config.yml
imports:
    - { resource: "@AcmeHelloBundle/Resources/config/services.yml" }
<!-- app/config/config.xml -->
<imports>
    <import resource="@AcmeHelloBundle/Resources/config/services.xml"/>
</imports>
// app/config/config.php
$this->import('@AcmeHelloBundle/Resources/config/services.php');

La directiva imports permite a tu aplicación incluir recursos de configuración del contenedor de servicios desde cualquier otro lugar (normalmente desde bundles). El valor de la opción resource para los archivos es directamente la ruta absoluta del archivo. La sintaxis especial @AcmeHello se traduce como la ruta al directorio del bundle AcmeHelloBundle. Esto te permite indicar la ruta al recurso sin preocuparte si después mueves de sitio el bundle AcmeHelloBundle.

16.5.2. Importando la configuración mediante las extensiones del contenedor

Cuando desarrollas una aplicación con Symfony2, casi siempre utilizas la directiva imports para importar la configuración del contenedor desde los bundles que has creado específicamente para tu aplicación. La configuración de los bundles desarrollados por terceros, incluyendo los bundles del núcleo de Symfony2, normalmente se cargan con otro método más flexible, aunque un poco más complicado.

Su funcionamiento es el siguiente: internamente, cada bundle define sus servicios de manera muy parecida a lo que has visto hasta ahora. Es decir, un bundle utiliza uno o más archivos de configuración (por lo general en formato XML) para especificar los parámetros y servicios de ese bundle. Sin embargo, en lugar de importar cada uno de estos recursos directamente desde la configuración de tu aplicación utilizando la directiva imports, sólo tienes que invocar una extensión del contenedor de servicios dentro del bundle, la cual hace ese trabajo por ti. Una extensión del contenedor de servicios es una clase PHP creada por el autor del bundle para conseguir dos cosas:

  • Importar todos los recursos necesarios para configurar los servicios del bundle.
  • Permitir que el bundle se pueda configurar de forma "semántica", sin tener que modificar directamente los parámetros de sus archivos de configuración.

En otras palabras, una extensión del contenedor de servicios se encarga de configurar automáticamente los servicios de un bundle. Y como se explicará a continuación, la extensión también permite configurar el bundle de manera sencilla.

En el siguiente ejemplo se utiliza el bundle FrameworkBundle, que es el bundle interno más importante de Symfony2. Al añadir la siguiente configuración en tu aplicación, se carga automáticamente la extensión que se encuentra dentro del bundle FrameworkBundle:

# app/config/config.yml
    framework:
        secret:          xxxxxxxxxx
        form:            true
        csrf_protection: true
        router:        { resource: "%kernel.root_dir%/config/routing.yml" }
        # ...
<!-- app/config/config.xml -->
<framework:config secret="xxxxxxxxxx">
    <framework:form />
    <framework:csrf-protection />
    <framework:router resource="%kernel.root_dir%/config/routing.xml" />
    <!-- ... -->
</framework>
// app/config/config.php
$container->loadFromExtension('framework', array(
    'secret'          => 'xxxxxxxxxx',
    'form'            => array(),
    'csrf-protection' => array(),
    'router'          => array('resource' => '%kernel.root_dir%/config/routing.php'),

    // ...
));

Cuando se procesa la configuración de la aplicación, el contenedor busca una extensión que sea capaz de procesar la directiva de configuración framework. Después, se carga y ejecuta esa extensión, que se encuentra en el bundle FrameworkBundle. Si eliminas la opción framework del archivo de configuración de tu aplicación, no se carga la extensión y por tanto, no se cargarán los servicios básicos de Symfony2. Lo mejor es que tu siempre tienes el control: Symfony2 no contiene ningún tipo de magia ni realiza ninguna acción sobre la que tú no tengas el control.

Además de activar la configuración, las extensiones te permiten realizar muchas otras tareas. Cada extensión te permite personalizar fácilmente el bundle, sin tener que preocuparte sobre cómo se definen los servicios internos.

En este caso, la extensión te permite personalizar la configuración de las opciones error_handler, csrf_protection, router y muchas más. Internamente, el bundle FrameworkBundle utiliza las opciones especificadas aquí para definir y configurar los sus servicios. El bundle se encarga de crear todas las opciones parameters y services necesarios para el contenedor de servicios, mientras que permite configurar sus valores fácilmente. Además, la mayoría de las extensiones del contenedor de servicios también son lo suficientemente inteligentes como para validar la configuración, notificándote opciones que faltan o datos de tipo incorrecto.

Al instalar o configurar un bundle, consulta su documentación para saber cómo se deben instalar y configurar sus servicios. Las opciones disponibles para los bundles internos de Symfony2 se pueden consultar en la Reference Guide.

Nota El contenedor de servicios sólo reconoce de forma nativa las directivas parameters, services e imports. Cualquier otra directiva debe ser procesada por una extensión del contenedor de servicios.