¿Cómo inyectar el contenedor de servicios en una extension Twig?

Estimados,

Analizando mi aplicación con SensioLabs Insight, hay una regla que me indica que Twig no es booteable, y esta es la explicación que me dan:

You application's Twig service must be bootable. The Twig service may not be
bootable for any number of reasons, but one of the more common ones is because
of a circular dependency.
 
Twig loads its extension classes eagerly, every request, regardless whether they
are used. There is no way around this inside of Twig without breaking backward
compatibility.
 
Therefore, it is recommended that any Twig extension you create in your
application inject the service container, rather than injecting individual
dependencies. While this is usually an anti-pattern, it is appropriate when
designing a Twig extension because they are not loaded lazily.

Por lo que entiendo y traduje, se debe a una dependencia circular, y es porque las extensiones de Twig se cargan "ansiosamente" independiente si se utilizan. Por lo que se recomienda que en cualquier extensión de Twig que se cree se inyecte el contenedor de servicios, en vez de inyectar las dependencias individuales. Si bien es un anti-patrón, pero es apropiado cuando se diseñan las extensiones de Twig.

Ahora viene mi pregunta:

¿Como inyecto el contenedor completo de servicios en la extension Twig?

Porque tengo inyectado un par de servicios individualmente, pero dicen que debo inyectar el contenedor completo, y me imagino que una vez dentro, obtengo el servicio que necesito.

Agradeceré me puedan orientar al respecto. Muchas gracias.

Saludos desde Chile

Respuestas

#1

Has entendido el mensaje de error perfectamente. Para inyectar el contenedor entero, simplemente tienes que inyectar un servicio llamado service_container. En YAML sería algo así:

# app/config/services.yml
app.mi_servicio:
    class: ...
    arguments: ['@service_container']

Después, para recoger este servicio solo tienes que hacer lo siguiente:

use Symfony\Component\DependencyInjection\ContainerInterface;
 
class MiServicio
{
    private $container;
 
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
}

Y en los métodos de la extensión Twig, coges los servicios a través de la variable $container:

$logger = $this->container->get('logger');
#2

Excelente, muchas gracias. Como siempre un gran aporte. He aprendido demasiado en esta web. Mis respetos y saludos desde Chile

#3

Hola @javiereguiluz, tu sabes si esta recomendación de SensioLabs Insight va a formar parte de las buenas practicas de Symfony?

#4

Muy buenas. Y siguiendo esta recomendación, ¿cual sería la manera más correcta de inyectar parámetros del contenedor de servicios en plantillas twig? Lo comento porque hasta ahora siempre lo hacía así:

# app/config/config.yml
twig:
    globals:
        version: 1.4.2

Sin embargo, disponiendo del contenedor de servicios en la extensión twig, se me ocurre una manera de disponer de todos los parámetros sin tener que inyectarlos uno a uno en config.yml, y es creando un filtro de la siguiente manera (una aproximación):

public function parameterFilter($name)
{
    return  $this->container->getParameter($name);
}

Así en teoría podríamos disponer de cualquier parámetro del contenedor en la plantilla de esta manera:

<p>Versión: {{ 'version'|parameter }}</p>

¿Es recomendable hacer algo así o es una chapuza como un templo?

#5

No es que sea una chapuza, pero en mi experiencia las plantillas no suelen tener que acceder a más de uno o dos parámetros globales.

Si tus plantillas necesitan 10 parámetros para hacer su trabajo, es posible que puedas sustituir parte de las plantillas por filtros/funciones Twig o incluso por llamadas a controladores (render()).

Así que aunque no es una chapuza, podría ser una pista de que tus plantillas están haciendo más trabajo del que deberían.

#6

Por supuesto, estoy de acuerdo con lo que comentas y ciertamente abusar de parámetros customizados no es una política que suela seguir.

Mi comentario era más bien para aclarar si el filtro que he indicado podría considerarse una mala práctica, es decir: si en una extensión twig lo "correcto" es inyectar el service container, en teoría ese filtro evita la necesidad de inyectar parámetros directamente en twig (para qué hacerlo si están todos en el service container), ofreciendo así cierta flexibilidad a la hora de poder usar un parámetro. Lo que ya no se es si pudiera afectar al rendimiento global en comparación con la inyección de parámetros...

¡Mil gracias por los consejos! Así da gusto...