class RecipeRunner

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Core/Recipe/RecipeRunner.php \Drupal\Core\Recipe\RecipeRunner

Applies a recipe.

This class is currently static and use \Drupal::service() in order to put off having to solve issues caused by container rebuilds during module install and configuration import.

@internal This API is experimental.

@todo https://www.drupal.org/i/3439717 Determine if there is a better to inject and re-inject services.

Hierarchy

Expanded class hierarchy of RecipeRunner

12 files declare their use of RecipeRunner
AddItemToToolbarConfigActionTest.php in core/modules/ckeditor5/tests/src/Kernel/ConfigAction/AddItemToToolbarConfigActionTest.php
AddModerationConfigActionTest.php in core/modules/content_moderation/tests/src/Kernel/ConfigAction/AddModerationConfigActionTest.php
AddToAllBundlesConfigActionTest.php in core/modules/field/tests/src/Kernel/AddToAllBundlesConfigActionTest.php
ConfigActionValidationTest.php in core/tests/Drupal/KernelTests/Core/Recipe/ConfigActionValidationTest.php
ConfigValidationTest.php in core/tests/Drupal/KernelTests/Core/Recipe/ConfigValidationTest.php

... See full list

File

core/lib/Drupal/Core/Recipe/RecipeRunner.php, line 28

Namespace

Drupal\Core\Recipe
View source
final class RecipeRunner {
  
  /**
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to apply.
   */
  public static function processRecipe(Recipe $recipe) : void {
    static::processRecipes($recipe->recipes);
    static::processInstall($recipe->install, $recipe->config
      ->getConfigStorage());
    static::processConfiguration($recipe->config);
    static::processContent($recipe->content);
    static::triggerEvent($recipe);
  }
  
  /**
   * Triggers the RecipeAppliedEvent.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to apply.
   * @param array<mixed>|null $context
   *   The batch context if called by a batch.
   */
  public static function triggerEvent(Recipe $recipe, ?array &$context = NULL) : void {
    $event = new RecipeAppliedEvent($recipe);
    \Drupal::service(EventDispatcherInterface::class)->dispatch($event);
    $context['message'] = t('Applied %recipe recipe.', [
      '%recipe' => $recipe->name,
    ]);
    $context['results']['recipe'][] = $recipe->name;
  }
  
  /**
   * Applies any recipes listed by the recipe.
   *
   * @param \Drupal\Core\Recipe\RecipeConfigurator $recipes
   *   The list of recipes to apply.
   */
  protected static function processRecipes(RecipeConfigurator $recipes) : void {
    foreach ($recipes->recipes as $recipe) {
      static::processRecipe($recipe);
    }
  }
  
  /**
   * Installs the extensions.
   *
   * @param \Drupal\Core\Recipe\InstallConfigurator $install
   *   The list of extensions to install.
   * @param \Drupal\Core\Config\StorageInterface $recipeConfigStorage
   *   The recipe's configuration storage. Used to override extension provided
   *   configuration.
   */
  protected static function processInstall(InstallConfigurator $install, StorageInterface $recipeConfigStorage) : void {
    foreach ($install->modules as $name) {
      static::installModule($name, $recipeConfigStorage);
    }
    // Themes can depend on modules so have to be installed after modules.
    foreach ($install->themes as $name) {
      static::installTheme($name, $recipeConfigStorage);
    }
  }
  
  /**
   * Creates configuration and applies configuration actions.
   *
   * @param \Drupal\Core\Recipe\ConfigConfigurator $config
   *   The config configurator from the recipe.
   */
  protected static function processConfiguration(ConfigConfigurator $config) : void {
    $config_installer = new RecipeConfigInstaller(\Drupal::service('config.factory'), \Drupal::service('config.storage'), \Drupal::service('config.typed'), \Drupal::service('config.manager'), \Drupal::service('event_dispatcher'), NULL, \Drupal::service('extension.path.resolver'));
    // Create configuration that is either supplied by the recipe or listed in
    // the config.import section that does not exist.
    $config_installer->installRecipeConfig($config);
    if (!empty($config->config['actions'])) {
      // Process the actions.
      /** @var \Drupal\Core\Config\Action\ConfigActionManager $config_action_manager */
      $config_action_manager = \Drupal::service('plugin.manager.config_action');
      foreach ($config->config['actions'] as $config_name => $actions) {
        foreach ($actions as $action_id => $data) {
          $config_action_manager->applyAction($action_id, $config_name, $data);
        }
      }
    }
  }
  
  /**
   * Creates content contained in a recipe.
   *
   * @param \Drupal\Core\DefaultContent\Finder $content
   *   The content finder object for the recipe.
   */
  protected static function processContent(Finder $content) : void {
    /** @var \Drupal\Core\DefaultContent\Importer $importer */
    $importer = \Drupal::service(Importer::class);
    $importer->setLogger(\Drupal::logger('recipe'));
    $importer->importContent($content, Existing::Skip);
  }
  
  /**
   * Converts a recipe into a series of batch operations.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to convert to batch operations.
   *
   * @return array<int, array{0: callable, 1: array{mixed}}>
   *   The array of batch operations. Each value is an array with two values.
   *   The first value is a callable and the second value are the arguments to
   *   pass to the callable.
   *
   * @see \Drupal\Core\Batch\BatchBuilder::addOperation()
   */
  public static function toBatchOperations(Recipe $recipe) : array {
    $modules = [];
    $themes = [];
    $recipes = [];
    return static::toBatchOperationsRecipe($recipe, $recipes, $modules, $themes);
  }
  
  /**
   * Helper method to convert a recipe to batch operations.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to convert to batch operations.
   * @param string[] $recipes
   *   The paths of the recipes that have already been converted to batch operations.
   * @param string[] $modules
   *   The modules that will already be installed due to previous recipes in the
   *   batch.
   * @param string[] $themes
   *   The themes that will already be installed due to previous recipes in the
   *   batch.
   *
   * @return array<int, array{0: callable, 1: array{mixed}}>
   *   The array of batch operations. Each value is an array with two values.
   *   The first value is a callable and the second value are the arguments to
   *   pass to the callable.
   */
  protected static function toBatchOperationsRecipe(Recipe $recipe, array $recipes, array &$modules, array &$themes) : array {
    if (in_array($recipe->path, $recipes, TRUE)) {
      return [];
    }
    $steps = [];
    $recipes[] = $recipe->path;
    foreach ($recipe->recipes->recipes as $sub_recipe) {
      $steps = array_merge($steps, static::toBatchOperationsRecipe($sub_recipe, $recipes, $modules, $themes));
    }
    $steps = array_merge($steps, static::toBatchOperationsInstall($recipe, $modules, $themes));
    if ($recipe->config
      ->hasTasks()) {
      $steps[] = [
        [
          RecipeRunner::class,
          'installConfig',
        ],
        [
          $recipe,
        ],
      ];
    }
    if (!empty($recipe->content->data)) {
      $steps[] = [
        [
          RecipeRunner::class,
          'installContent',
        ],
        [
          $recipe,
        ],
      ];
    }
    $steps[] = [
      [
        RecipeRunner::class,
        'triggerEvent',
      ],
      [
        $recipe,
      ],
    ];
    return $steps;
  }
  
  /**
   * Converts a recipe's install tasks to batch operations.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to convert install tasks to batch operations.
   * @param string[] $modules
   *   The modules that will already be installed due to previous recipes in the
   *   batch.
   * @param string[] $themes
   *   The themes that will already be installed due to previous recipes in the
   *   batch.
   *
   * @return array<int, array{0: callable, 1: array{mixed}}>
   *   The array of batch operations. Each value is an array with two values.
   *   The first value is a callable and the second value are the arguments to
   *   pass to the callable.
   */
  protected static function toBatchOperationsInstall(Recipe $recipe, array &$modules, array &$themes) : array {
    foreach ($recipe->install->modules as $name) {
      if (in_array($name, $modules, TRUE)) {
        continue;
      }
      $modules[] = $name;
      $steps[] = [
        [
          RecipeRunner::class,
          'installModule',
        ],
        [
          $name,
          $recipe,
        ],
      ];
    }
    foreach ($recipe->install->themes as $name) {
      if (in_array($name, $themes, TRUE)) {
        continue;
      }
      $themes[] = $name;
      $steps[] = [
        [
          RecipeRunner::class,
          'installTheme',
        ],
        [
          $name,
          $recipe,
        ],
      ];
    }
    return $steps ?? [];
  }
  
  /**
   * Installs a module for a recipe.
   *
   * @param string $module
   *   The name of the module to install.
   * @param \Drupal\Core\Config\StorageInterface|\Drupal\Core\Recipe\Recipe $recipeConfigStorage
   *   The recipe or recipe's config storage.
   * @param array<mixed>|null $context
   *   The batch context if called by a batch.
   */
  public static function installModule(string $module, StorageInterface|Recipe $recipeConfigStorage, ?array &$context = NULL) : void {
    if ($recipeConfigStorage instanceof Recipe) {
      $recipeConfigStorage = $recipeConfigStorage->config
        ->getConfigStorage();
    }
    // Disable configuration entity install but use the config directory from
    // the module.
    \Drupal::service('config.installer')->setSyncing(TRUE);
    $default_install_path = \Drupal::service('extension.list.module')->get($module)
      ->getPath() . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
    // Allow the recipe to override simple configuration from the module.
    $storage = new RecipeOverrideConfigStorage($recipeConfigStorage, new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION));
    \Drupal::service('config.installer')->setSourceStorage($storage);
    \Drupal::service('module_installer')->install([
      $module,
    ]);
    \Drupal::service('config.installer')->setSyncing(FALSE);
    $context['message'] = t('Installed %module module.', [
      '%module' => \Drupal::service('extension.list.module')->getName($module),
    ]);
    $context['results']['module'][] = $module;
  }
  
  /**
   * Installs a theme for a recipe.
   *
   * @param string $theme
   *   The name of the theme to install.
   * @param \Drupal\Core\Config\StorageInterface|\Drupal\Core\Recipe\Recipe $recipeConfigStorage
   *   The recipe or recipe's config storage.
   * @param array<mixed>|null $context
   *   The batch context if called by a batch.
   */
  public static function installTheme(string $theme, StorageInterface|Recipe $recipeConfigStorage, ?array &$context = NULL) : void {
    if ($recipeConfigStorage instanceof Recipe) {
      $recipeConfigStorage = $recipeConfigStorage->config
        ->getConfigStorage();
    }
    // Disable configuration entity install.
    \Drupal::service('config.installer')->setSyncing(TRUE);
    $default_install_path = \Drupal::service('extension.list.theme')->get($theme)
      ->getPath() . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
    // Allow the recipe to override simple configuration from the theme.
    $storage = new RecipeOverrideConfigStorage($recipeConfigStorage, new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION));
    \Drupal::service('config.installer')->setSourceStorage($storage);
    \Drupal::service('theme_installer')->install([
      $theme,
    ]);
    \Drupal::service('config.installer')->setSyncing(FALSE);
    $context['message'] = t('Installed %theme theme.', [
      '%theme' => \Drupal::service('extension.list.theme')->getName($theme),
    ]);
    $context['results']['theme'][] = $theme;
  }
  
  /**
   * Installs a config for a recipe.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to install config for.
   * @param array<mixed>|null $context
   *   The batch context if called by a batch.
   */
  public static function installConfig(Recipe $recipe, ?array &$context = NULL) : void {
    static::processConfiguration($recipe->config);
    $context['message'] = t('Installed configuration for %recipe recipe.', [
      '%recipe' => $recipe->name,
    ]);
    $context['results']['config'][] = $recipe->name;
  }
  
  /**
   * Installs a content for a recipe.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   The recipe to install content for.
   * @param array<mixed>|null $context
   *   The batch context if called by a batch.
   */
  public static function installContent(Recipe $recipe, ?array &$context = NULL) : void {
    static::processContent($recipe->content);
    $context['message'] = t('Created content for %recipe recipe.', [
      '%recipe' => $recipe->name,
    ]);
    $context['results']['content'][] = $recipe->name;
  }

}

Members

Title Sort descending Modifiers Object type Summary
RecipeRunner::installConfig public static function Installs a config for a recipe.
RecipeRunner::installContent public static function Installs a content for a recipe.
RecipeRunner::installModule public static function Installs a module for a recipe.
RecipeRunner::installTheme public static function Installs a theme for a recipe.
RecipeRunner::processConfiguration protected static function Creates configuration and applies configuration actions.
RecipeRunner::processContent protected static function Creates content contained in a recipe.
RecipeRunner::processInstall protected static function Installs the extensions.
RecipeRunner::processRecipe public static function
RecipeRunner::processRecipes protected static function Applies any recipes listed by the recipe.
RecipeRunner::toBatchOperations public static function Converts a recipe into a series of batch operations.
RecipeRunner::toBatchOperationsInstall protected static function Converts a recipe&#039;s install tasks to batch operations.
RecipeRunner::toBatchOperationsRecipe protected static function Helper method to convert a recipe to batch operations.
RecipeRunner::triggerEvent public static function Triggers the RecipeAppliedEvent.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.