Commit ad42c3f6 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3297426 by alexpott: Enable a recipe to apply other recipes prior to by applied itself

parent 35a07b52
Loading
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ final class Recipe {
    public readonly string $name,
    public readonly string $description,
    public readonly string $type,
    public readonly RecipeConfigurator $recipes,
    public readonly InstallConfigurator $install,
    public readonly ConfigConfigurator $config,
    public readonly ContentConfigurator $content
@@ -39,6 +40,7 @@ final class Recipe {
    $recipe_data = Yaml::decode(file_get_contents($path . '/recipe.yml')) + [
      'description' => '',
      'type' => '',
      'recipes' => [],
      'install' => [],
      'config' => [],
      'content' => [],
@@ -48,10 +50,12 @@ final class Recipe {
      throw new RecipeFileException("The $path/recipe.yml has no name value.");
    }

    $recipe_discovery = new RecipeDiscovery([dirname($path)]);
    $recipes = new RecipeConfigurator($recipe_data['recipes'], $recipe_discovery);
    $install = new InstallConfigurator($recipe_data['install'], \Drupal::service('extension.list.module'), \Drupal::service('extension.list.theme'));
    $config = new ConfigConfigurator($recipe_data['config'], $path, \Drupal::service('config.storage'));
    $content = new ContentConfigurator($recipe_data['content']);
    return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $install, $config, $content);
    return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $recipes, $install, $config, $content);
  }

}
+24 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Core\Recipe;

/**
 * @internal
 *   This API is experimental.
 */
final class RecipeConfigurator {

  public readonly array $recipes;

  /**
   * @param string[] $recipes
   *   A list of recipes for a recipe to apply. The recipes will be applied in
   *   the order listed.
   * @param \Drupal\Core\Recipe\RecipeDiscovery $recipeDiscovery
   *   Recipe discovery.
   */
  public function __construct(array $recipes, RecipeDiscovery $recipeDiscovery) {
    $this->recipes = array_map([$recipeDiscovery, 'getRecipe'], $recipes);
  }

}
+46 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Core\Recipe;

use Drupal\Component\Assertion\Inspector;

/**
 * @internal
 *   This API is experimental.
 */
final class RecipeDiscovery {

  /**
   * Constructs a recipe discovery object.
   *
   * @param array $paths
   *   An array of paths where to search for recipes. The path will be searched
   *   folders containing a recipe.yml. There will be no traversal further into
   *   the directory structure.
   */
  public function __construct(protected readonly array $paths) {
    assert(Inspector::assertAllStrings($paths), 'Paths must be strings.');
  }

  /**
   * Constructs a RecipeDiscovery object.
   *
   * @param string $name
   *   The machine name of the recipe to find.
   *
   * @return \Drupal\Core\Recipe\Recipe
   *   The recipe object.
   *
   * @throws \Drupal\Core\Recipe\UnknownRecipeException
   *   Thrown when the recipe cannot be found.
   */
  public function getRecipe(string $name): Recipe {
    foreach ($this->paths as $path) {
      if (file_exists($path . DIRECTORY_SEPARATOR . $name . DIRECTORY_SEPARATOR . 'recipe.yml')) {
        return Recipe::createFromDirectory($path . DIRECTORY_SEPARATOR . $name);
      }
    }
    throw new UnknownRecipeException($name, $this->paths, sprintf("Can not find the %s recipe, search paths: %s", $name, implode(', ', $this->paths)));
  }

}
+13 −0
Original line number Diff line number Diff line
@@ -23,11 +23,24 @@ final class RecipeRunner {
   *   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);
  }

  /**
   * 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.
   *
+29 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Core\Recipe;

/**
 * Exception thrown when recipe is can not be found.
 *
 * @internal
 *   This API is experimental.
 */
final class UnknownRecipeException extends \RuntimeException {

  /**
   * @param string $recipe
   *   The recipe's name.
   * @param array $searchPaths
   *   The paths searched for the recipe.
   * @param string $message
   *   (optional) The exception message.
   * @param int $code
   *   (optional) The exception code.
   * @param \Throwable|null $previous
   *   (optional) The previous exception.
   */
  public function __construct(public readonly string $recipe, public readonly array $searchPaths, string $message = "", int $code = 0, ?\Throwable $previous = NULL) {
    parent::__construct($message, $code, $previous);
  }

}
Loading