TL;DR: Drupal puede cargar servicios de manera opcional usando un ServiceProvider y etiquetas en los servicios.
En Drupal, es común que un módulo dependa de otros módulos para ofrecer ciertas funcionalidades. Sin embargo, en ocasiones se quiere que un servicio solo esté disponible si otro módulo está instalado, sin crear una dependencia obligatoria en el archivo "*.info.yml". Esto puede ser útil por ejemplo para módulos que solo se instalan en cierto entorno (ej: solo en desarrollo) o en un multisitio.
Para lograr esto, se puede usar un proveedor de servicios (Service Provider) junto a etiquetas en los servicios ("required_module") para eliminar dinámicamente los servicios cuando el módulo del que dependen no está instalado.
Paso 1: Service Provider
Drupal sigue una convención para los proveedores de servicios de los módulos. El nombre de la clase debe ser "{NombreDelModulo}ServiceProvider", usando CamelCase.
En este caso, el módulo es "my_module" (a sustituir por el nombre dle módulo donde se integre), por lo que la clase se llama "MyModuleServiceProvider".
El archivo debe ponerse en "[RUTA MÓDULO]/src/MyModuleServiceProvider.php".
<?php
declare(strict_types=1);
namespace Drupal\my_module;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\DependencyInjection\Definition;
/**
* Service provider for the my_module module.
*/
final class MyModuleServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
#[\Override]
public function alter(ContainerBuilder $container): void {
$module_handler = $container->get('module_handler');
if (!$module_handler instanceof ModuleHandlerInterface) {
return;
}
foreach ($container->getDefinitions() as $id => $definition) {
if ($this->shouldRemoveService($definition, $module_handler)) {
$container->removeDefinition($id);
}
}
}
/**
* Checks if a service should be removed based on required_module tags.
*/
private function shouldRemoveService(Definition $definition, ModuleHandlerInterface $module_handler): bool {
$tags = $definition->getTags();
foreach ($tags as $tag_name => $tag_attributes) {
if ($tag_name !== 'required_module') {
continue;
}
foreach ($tag_attributes as $attributes) {
if (!isset($attributes['value'])) {
continue;
}
$required_module = $attributes['value'];
if (!$module_handler->moduleExists($required_module)) {
return TRUE;
}
}
}
return FALSE;
}
}Este proveedor recorre todos los servicios definidos en el contenedor y elimina aquellos que tengan la etiqueta "required_module" si el valor de esa etiqueta es un módulo que no está instalado.
Paso 2: etiquetar servicios opcionales
Para marcar un servicio como opcional, solo hay que añadirle la etiqueta `required_module` en su definición, indicando el módulo necesario.
Ejemplo en `my_module.services.yml`:
services:
my_module.optional_service:
class: Drupal\my_module\OptionalService
arguments: ['@dependency.from.optional.module']
tags:
- { name: required_module, value: 'optional_module' }Si el módulo "optional_module" está instalado, el servicio se cargará de manera normal. Si no lo está, "MyModuleServiceProvider" lo eliminará del contenedor.
La etiqueta puede repetirse, por ejemplo si el servicio depende de varios módulos que pueden o no estar instalados:
services:
my_module.optional_service:
class: Drupal\my_module\OptionalService
arguments: ['@dependency.from.optional.module']
tags:
- { name: required_module, value: 'optional_module' }
- { name: required_module, value: 'another_optional_module' }En este caso, si cualquiera de los módulos no está instalado, el servicio no se cargará.