Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Drupal

Drupal 8: Inyectar correctamente dependencias con DI

by
Difficulty:AdvancedLength:MediumLanguages:

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Como estoy seguro de que ya sabe, la inyección de dependencia (DI) y el contenedor de servicios Symfony son importantes nuevas características de desarrollo de Drupal 8. Sin embargo, a pesar de que están empezando a ser mejor comprendidos en la comunidad de desarrollo de Drupal, todavía hay cierta falta de claridad sobre cómo exactamente inyectar servicios en Drupal 8 clases.

Muchos ejemplos hablan de servicios, pero la mayoría cubren sólo la forma estática de cargarlos:

Esto es comprensible, ya que el enfoque de la inyección adecuada es más detallado, y si ya lo sabes, más bien califica. Sin embargo, el enfoque estático en la vida real sólo debe utilizarse en dos casos:

  • En el archivo .module (fuera de un contexto de clase)
  • Esas raras ocasiones dentro de un contexto de clase en el que la clase se está cargando sin conocimiento del contenedor de servicio

Aparte de eso, inyectar servicios es la mejor práctica, ya que garantiza el código desacoplado y facilita las pruebas.

En Drupal 8 hay algunas especificidades acerca de la inyección de dependencia que no podrás entender únicamente a partir de un enfoque puro de Symfony. Así que en este artículo vamos a ver algunos ejemplos de inyección de constructor adecuada en Drupal 8. Para este fin, pero también para cubrir todos los conceptos básicos, vamos a ver tres tipos de ejemplos, en orden de complejidad:

  • Inyectar servicios en otro de sus propios servicios
  • Inyectar servicios en clases sin servicio
  • Inyección de servicios en clases de complemento

Avanzando, la suposición es que usted ya sabe lo que es DI, con qué propósito sirve y cómo el contenedor de servicio lo soporta. Si no, recomiendo el chequear hacia fuera este artículo primero.

Servicios

Inyectar servicios en su propio servicio es muy fácil. Puesto que usted es el que define el servicio, todo lo que tiene que hacer es pasarlo como un argumento al servicio que desea inyectar. Imagine las siguientes definiciones de servicio:

Aquí definimos dos servicios donde el segundo toma el primero como argumento constructor. Así que todo lo que tenemos que hacer ahora en la clase AnotherDemoService es almacenarlo como una variable local:

Y eso es más o menos. También es importante mencionar que este enfoque es exactamente el mismo que en Symfony, por lo que no hay cambio aquí.

Clases sin servicio

Ahora echemos un vistazo a las clases que a menudo interactuamos con pero que no son nuestros propios servicios. Para comprender cómo se lleva a cabo esta inyección, debe comprender cómo se resuelven las clases y cómo se instancian. Pero lo veremos pronto en la práctica.

Controladores

Las clases de controlador se utilizan principalmente para asignar las rutas de enrutamiento a la lógica empresarial. Se supone que deben permanecer delgados y delegar una lógica de negocios más pesada a los servicios. Muchos extienden la clase ControllerBase y obtienen algunos métodos auxiliares para recuperar servicios comunes del contenedor. Sin embargo, estos se devuelven estáticamente.

Cuando se está creando un objeto controlador (ControllerResolver::createController), el ClassResolver se utiliza para obtener una instancia de la definición de clase del controlador. El resolvedor es consciente del contenedor y devuelve una instancia del controlador si el contenedor ya lo tiene. Por el contrario, instancia una nueva y devuelve eso.

Y aquí es donde tiene lugar nuestra inyección: si la clase que se resuelve implementa ContainerAwareInterface, la instancia se lleva a cabo utilizando el método estatico create() en esa clase que recibe todo el contenedor. Y nuestra clase ControllerBase también implementa ContainerAwareInterface.

Así que echemos un vistazo a un controlador de ejemplo que correctamente inyecta servicios utilizando este enfoque (en lugar de solicitarlos de forma estática):

La clase EntityListController no hace nada para nuestros propósitos aquí, así que imagínese que BlockListController extiende directamente la clase ControllerBase, que a su vez implementa el ContainerInjectionInterface.

Como dijimos, cuando este controlador es instanciado, se llama al método estático create(). Su propósito es instanciar esta clase y pasar los parámetros que quiera al constructor de clase. Y dado que el contenedor se pasa a create(), puede elegir qué servicios solicitar y pasar al constructor.

Entonces, el constructor simplemente tiene que recibir los servicios y almacenarlos localmente. Tenga en cuenta que es una mala práctica inyectar el contenedor entero en su clase, y siempre debe limitar los servicios que usted inyecta a los que necesita. Y si usted necesita demasiados, usted es probable que este haciendo algo mal.

Utilizamos este ejemplo de controlador para ir un poco más profundo en el enfoque de inyección de dependencia de Drupal y entender cómo funciona la inyección de constructor. También hay posibilidades de inyección de setter haciendo que las clases sean conscientes de los contenedores, pero no cubriremos esto aquí. Veamos otros ejemplos de clases con las que puedes interactuar y en las que deberías inyectar servicios.

Formularios

Las formas son otro gran ejemplo de las clases donde usted necesita inyectar servicios. Normalmente, puede extender las clases de FormBase o ConfigFormBase que ya implementan ContainerInjectionInterface. En este caso, si anula los métodos create() y constructor, puede inyectar lo que quiera. Si no desea extender estas clases, todo lo que tiene que hacer es implementar esta interfaz usted mismo y seguir los mismos pasos que hemos visto anteriormente con el controlador.

Como ejemplo, echemos un vistazo al SiteInformationForm que extiende el ConfigFormBase y verá cómo inyecta servicios encima del config.factory que su superior necesita:

Como antes, el método create() se utiliza para la instancia, que pasa al constructor el servicio requerido por la clase padre así como algunos adicionales que necesita en la parte superior.

Y esto es más o menos cómo funciona la inyección de constructor básica en Drupal 8. Está disponible en casi todos los contextos de clase, excepto para algunos en los que la parte de instanciación todavía no se resolvió de esta manera (por ejemplo, los complementos FieldType). Además, hay un subsistema importante que tiene algunas diferencias, pero es crucialmente importante para entender: plugins.

Plugins

El sistema de complementos es un componente muy importante de Drupal 8 que proporciona mucha funcionalidad. Así que vamos a ver cómo funciona la inyección de dependencia con las clases de complemento.

La diferencia más importante en cómo la inyección se maneja con los enchufes es las clases del complemento de la interfaz necesitan implementar: ContainerFactoryPluginInterface. La razón es que los complementos no se resuelven pero son administrados por un administrador de complementos. Así que cuando este gestor necesita instanciar uno de sus plugins, lo hará utilizando una fábrica. Y por lo general, esta fábrica es el ContainerFactory (o una variación similar de la misma).

Así que si nos fijamos en ContainerFactory::createInstance(), vemos que aparte del contenedor que se pasa al método usual create(), también se pasan las variables $configuration, $plugin_id y $plugin_definition (que son las tres variables básicas Parámetros de cada plugin viene con).

Así que veamos dos ejemplos de plugins que inyectan servicios. En primer lugar, el complemento principal de UserLoginBlock (@Block):

Como puede ver, implementa la ContainerFactoryPluginInterface y el método create() recibe esos tres parámetros adicionales. Éstos se pasan entonces en el orden correcto al constructor de clase, y desde el contenedor se solicita y se pasa un servicio también. Este es el ejemplo más básico, pero de uso general, de inyectar servicios en clases de complemento.

Otro ejemplo interesante es el complemento FileWidget (@FieldWidget):

Como puede ver, el método create() recibe los mismos parámetros, pero el constructor de la clase espera otros adicionales que son específicos para este tipo de complemento. Esto no es un problema. Por lo general, se pueden encontrar dentro de la array $configuration de ese complemento en particular y se pasa desde allí.

Así que estas son las principales diferencias cuando se trata de inyectar servicios en clases de complemento. Hay una interfaz diferente para implementar y algunos parámetros adicionales en el método create().

Conclusión

Como hemos visto en este artículo, hay una serie de maneras en que podemos poner nuestros servicios en Drupal 8. A veces tenemos que solicitarlos estáticamente. Sin embargo, la mayoría de las veces no debemos hacerlo. Y hemos visto algunos ejemplos típicos de cuándo y cómo debemos inyectarlos en nuestras clases en su lugar. También hemos visto las dos interfaces principales que las clases necesitan implementar para ser instanciadas con el contenedor y estar listas para inyección, así como la diferencia entre ellas.

Si está trabajando en un contexto de clase y no está seguro de cómo inyectar servicios, comience a buscar en otras clases de ese tipo. Si son plugins, compruebe si alguno de los padres implementa ContainerFactoryPluginInterface. Si no, hágalo usted mismo para su clase y asegúrese de que el constructor reciba lo que espera. También echa un vistazo a la clase de administrador de plugins responsable y vea qué fábrica utiliza.

En otros casos, como con clases TypedData como el FieldType, eche un vistazo a otros ejemplos en el núcleo. Si ve a otros usuarios utilizando servicios cargados estáticamente, lo más probable es que aún no esté listo para la inyección, por lo que tendrá que hacer lo mismo. Pero mantenga un ojo hacia fuera, porque esto podría cambiar en el futuro.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.