Surcharger une configuration de façon dynamique avec Drupal 8

Usine

Dans certains cas de figure, il peut être extrêmement intéressant de pouvoir surcharger une configuration de façon dynamique. Un des premiers cas d'usage immédiatement perceptible est dans le cas d'une usine à sites disposant d'un ensemble de fonctionnalités partagées et déployées, et donc de configurations identiques partagées. Et ceci sur une usine à site qu'elle soit basée sur l'architecture multi-sites native de Drupal 8, ou encore basée sur les modules Domain ou Micro site qui permettent de propulser de mutliples sites depuis une seule et unique instance Drupal 8.

L'intérêt immédiat est de pouvoir utiliser et configurer dynamiquement les modules contribués de Drupal dans le contexte d'une instance déployée sur une usine à sites, et donc de bénéficier de tout le travail déjà effectué, tout en modifiant uniquement certains éléments de configuration du module pour l'adapter au contexte où il est employé.

Passons à la pratique, sur un exemple concret. Imaginons dans le contexte d'une usine à sites, toute une galaxie de sites disposant d'un ensemble de vocabulaires de taxonomy partagés. Du point de vue de la maintenance l'intérêt est de pouvoir maintenir et modifier toute configuration relative à ces vocabulaires sur l'ensemble des sites qui l'utilisent. Du point de vue de chaque site, l'enjeu majeur est que le nom de vocabulaire (et donc de ses usages possibles dans les labels des champs, des alias générés automatiquement, etc.) peut et doit différer.

Nous avons plusieurs méthodes pour arriver à surcharger le nom de vocabulaire pour chaque site. La méthode la plus simple, dans le cas d'une architecture multi-site est de procéder à une surcharge depuis le fichier settings.php de chaque instance.

$config['taxonomy.vocabulary.VOCABULARY_ID']['name'] = 'NEW_VOCABULATY_LABEL';

Et c'est terminé. Mais cette méthode nécessite un accès au serveur, et n'est donc pas administrable depuis une l'instance de site elle-même par un administrateur ou un webmestre. En outre, cette méthode n'est utilisable que dans le contexte d'une usine à site basée sur l'architecture multi-sites de Drupal, et non pas dans le contexte d'une usine à sites basée sur Domain ou Micro site, puisque ces deux architectures utilisent en fait une même et seule instance Drupal 8.

Une deuxième méthode est de fournir un formulaire de configuration qui sera chargé de permettre une modification des labels de vocabulaires pour chaque instance de site.

Par exemple

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    foreach ($this->vocabularies as $id => $label) {
      $original_label = $this->configFactory->get('taxonomy.vocabulary.' . $id)->getOriginal('name', FALSE);
      $form[$id] = [
        '#type' => 'fieldset',
        '#title' => $label . ' (original : ' . $original_label . ')',
      ];
     
      $form[$id][$id . '_label'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Label'),
        '#maxlength' => 64,
        '#size' => 64,
        '#default_value' => ($this->settings->get('vocabularies.' . $id . '.label')) ?: $label,
      ];

      $options_widget = [
        '' => $this->t('Default'),
        'entity_reference_autocomplete' => $this->t('Autocompletion (with automatic creation)'),
        'options_buttons' => $this->t('Checkboxes (without automatic creation)'),
        'options_select' => $this->t('Select list (without automatic creation)'),
      ];
      $form[$id][$id . '_widget'] = [
        '#type' => 'select',
        '#title' => $this->t('Widget'),
        '#options' => $options_widget,
        '#default_value' => ($this->settings->get('vocabularies.' . $id . '.widget')) ?: '',
      ];
      
    }

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);

    foreach ($this->vocabularies as $id => $label) {
      $this->settings->set('vocabularies.' . $id . '.label', $form_state->getValue($id . '_label'));
      $this->settings->set('vocabularies.' . $id . '.widget', $form_state->getValue($id . '_widget'));
    }

    // We need to store here the node types and vocabularies because we can not
    // call the entity type manager in the TaxonomyConfigOverrides service
    // because of a circular reference.
    $this->settings->set('node', $this->getNodeTypeIds(FALSE));
    $this->settings->set('taxonomy_vocabulary', $this->getVocabularyIds(FALSE));

    $this->settings->save();
    drupal_flush_all_caches();
  }

}

Comme vous pouvez le voir, nous pouvons surcharger beaucoup plus que le simple label d'un vocabulaire. Par exemple le widget utilisé dans les formulaires d'édition des contenus. Et bien d'autres choses.

Puis une fois ce formulaire en place et ses éléments de configuration sauvegardés, il nous suffit de créer un service qui va implémenter le service config.factory.override.

services:

  MY_MODULE.taxonomy_overrider:
    class: \Drupal\MY_MODULE\TaxonomyConfigOverrides
    tags:
      - {name: config.factory.override, priority: 300}
    arguments: ['@config.factory']

 

Et nous créons la classe TaxonomyConfigOverrides

<?php

namespace Drupal\MY_MODULE;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;

/**
 * Override Taxonomy configuration per micro site.
 *
 * @package Drupal\MY_MODULE
 */
class TaxonomyConfigOverrides implements ConfigFactoryOverrideInterface {

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;
  
  /**
   * An array of all the vocabulary ids.
   *
   * @var array
   */
  protected $vocabularies;

  /**
   * An array of all the node type ids.
   *
   * @var array
   */
  protected $nodeTypes;

  /**
   * The settings of the factory taxonomy configuration.
   *
   * @var \Drupal\Core\Config\Config
   */
  protected $taxonomySettings;

  /**
   * Constructs the object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   */
  public function __construct(ConfigFactoryInterface $config_factory) {
    $this->configFactory = $config_factory;
    $this->taxonomySettings = $this->configFactory->get('MY_MODULE.taxonomy_settings');
    $this->vocabularies = $this->getVocabularyIds();
  }

  /**
   * {@inheritdoc}
   */
  public function loadOverrides($names) {
    $overrides = [];

    foreach ($this->vocabularies as $vocabularyId) {
      $label = $this->taxonomySettings->get('vocabularies.' . $vocabularyId . '.label');
      if (empty($label)) {
        continue;
      }

      $vocabulary_config_id = $this->getVocabularyConfigName($vocabularyId);
      if (in_array($vocabulary_config_id, $names)) {
        $overrides[$vocabulary_config_id]['name'] = $label;
      }

    }

    return $overrides;
  }

  /**
   * @param $vocabularyId
   * @return string
   */
  protected function getVocabularyConfigName($vocabularyId) {
    return "taxonomy.vocabulary.{$vocabularyId}";
  }

  /**
   * Get an array of vocabulary id.
   *
   * @return array
   *   An array of vocabulary id.
   */
  protected function getVocabularyIds() {
    return $this->taxonomySettings->get('taxonomy_vocabulary') ?: [];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheSuffix() {
    return 'config_taxonomy_label';
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheableMetadata($name) {
    $meta = new CacheableMetadata();
    $meta->addCacheableDependency($this->taxonomySettings);
    return $meta;
  }

  /**
   * {@inheritdoc}
   */
  public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
    return NULL;
  }

}

Et ainsi vous pouvez désormais partager une base de configuration commune sur une multitude de sites, tout en permettant à chacun de personnaliser à volonté différents éléments de configuration selon leur besoins.

Override vocabulary settings

Bien sûr cette méthode doit être utilisée en fonction des besoins et de l'architecture de l'usine à sites. Utiliser une telle méthode dans le contexte d'une architecture multi-sites pour tout et n'importe quoi n'est pas forcément le meilleur moyen.  Ni le plus optimum. Quand il peut suffire tout simplement de fournir des éléments de configuration initiaux, pour tel ou tel module, par le code puis de le laisser vivre sa vie au sein de l'instance de l'usine à sites.

N'hésitez pas à demander conseil auprès d'un développeur Drupal ou un expert Drupal qui pourra définir avec vous la meilleure stratégie pour que votre usine à site dispose de tous les atouts pour évoluer en toute sérénité, et gagner en maturité sur le long terme.

 

Ajouter un commentaire