Skip to content
Snippets Groups Projects
Verified Commit 2c3c9572 authored by Dave Long's avatar Dave Long
Browse files

Issue #3447210 by alexpott, jnicola: Move RecipeDiscovery into RecipeConfigurator

(cherry picked from commit 2e3e8746)
parent 47794d40
No related branches found
No related tags found
17 merge requests!12212Issue #3445525 by alexpott, japerry, catch, mglaman, longwave: Add BC layer...,!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8949Backport .gitlabci.yml changes.,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #174023 passed with warnings
Pipeline: drupal

#174069

    Pipeline: drupal

    #174061

      Pipeline: drupal

      #174054

        +5
        ......@@ -57,8 +57,7 @@ public function __construct(
        public static function createFromDirectory(string $path): static {
        $recipe_data = self::parse($path . '/recipe.yml');
        $recipe_discovery = static::getRecipeDiscovery(dirname($path));
        $recipes = new RecipeConfigurator(is_array($recipe_data['recipes']) ? $recipe_data['recipes'] : [], $recipe_discovery);
        $recipes = new RecipeConfigurator(is_array($recipe_data['recipes']) ? $recipe_data['recipes'] : [], dirname($path));
        $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 Finder($path . '/content');
        ......@@ -90,7 +89,7 @@ private static function parse(string $file): array {
        // recipes.
        // @see ::validateRecipeExists()
        // @see ::validateConfigActions()
        $discovery = self::getRecipeDiscovery(dirname($file, 2));
        $include_path = dirname($file, 2);
        $constraints = new Collection([
        'name' => new Required([
        ......@@ -136,7 +135,7 @@ private static function parse(string $file): array {
        ),
        new Callback(
        callback: self::validateRecipeExists(...),
        payload: $discovery,
        payload: $include_path,
        ),
        ]),
        ]),
        ......@@ -177,7 +176,7 @@ private static function parse(string $file): array {
        new NotBlank(),
        new Callback(
        callback: self::validateConfigActions(...),
        payload: $discovery,
        payload: $include_path,
        ),
        ]),
        ]),
        ......@@ -232,33 +231,21 @@ private static function validateExtensionIsAvailable(string $value, ExecutionCon
        * The machine name of the recipe to look for.
        * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
        * The validator execution context.
        * @param \Drupal\Core\Recipe\RecipeDiscovery $discovery
        * A discovery object to find other recipes.
        * @param string $include_path
        * The recipe's include path.
        */
        private static function validateRecipeExists(string $name, ExecutionContextInterface $context, RecipeDiscovery $discovery): void {
        private static function validateRecipeExists(string $name, ExecutionContextInterface $context, string $include_path): void {
        if (empty($name)) {
        return;
        }
        try {
        $discovery->getRecipe($name);
        RecipeConfigurator::getIncludedRecipe($include_path, $name);
        }
        catch (UnknownRecipeException) {
        $context->addViolation('The %name recipe does not exist.', ['%name' => $name]);
        }
        }
        /**
        * Gets the recipe discovery object for a recipe.
        *
        * @param string $recipeDirectory
        * The directory the contains the recipe.
        *
        * @return \Drupal\Core\Recipe\RecipeDiscovery
        */
        private static function getRecipeDiscovery(string $recipeDirectory): RecipeDiscovery {
        return new RecipeDiscovery($recipeDirectory);
        }
        /**
        * Validates that the corresponding extension is enabled for a config action.
        *
        ......@@ -266,10 +253,10 @@ private static function getRecipeDiscovery(string $recipeDirectory): RecipeDisco
        * The config action; not used.
        * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context
        * The validator execution context.
        * @param \Drupal\Core\Recipe\RecipeDiscovery $discovery
        * A discovery object to find other recipes.
        * @param string $include_path
        * The recipe's include path.
        */
        private static function validateConfigActions(mixed $value, ExecutionContextInterface $context, RecipeDiscovery $discovery): void {
        private static function validateConfigActions(mixed $value, ExecutionContextInterface $context, string $include_path): void {
        $config_name = str_replace(['[config][actions]', '[', ']'], '', $context->getPropertyPath());
        [$config_provider] = explode('.', $config_name);
        if ($config_provider === 'core') {
        ......@@ -279,7 +266,7 @@ private static function validateConfigActions(mixed $value, ExecutionContextInte
        $recipe_being_validated = $context->getRoot();
        assert(is_array($recipe_being_validated));
        $configurator = new RecipeConfigurator($recipe_being_validated['recipes'] ?? [], $discovery);
        $configurator = new RecipeConfigurator($recipe_being_validated['recipes'] ?? [], $include_path);
        // The config provider must either be an already-installed module or theme,
        // or an extension being installed by this recipe or a recipe it depends on.
        ......
        ......@@ -10,17 +10,57 @@
        */
        final class RecipeConfigurator {
        /**
        * @var \Drupal\Core\Recipe\Recipe[]
        */
        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.
        * @param string $include_path
        * The recipe's include path.
        */
        public function __construct(array $recipes, RecipeDiscovery $recipeDiscovery) {
        $this->recipes = array_map([$recipeDiscovery, 'getRecipe'], $recipes);
        public function __construct(array $recipes, string $include_path) {
        $this->recipes = array_map(fn(string $name) => static::getIncludedRecipe($include_path, $name), $recipes);
        }
        /**
        * Gets an included recipe object.
        *
        * @param string $include_path
        * The recipe's include path.
        * @param string $name
        * The machine name of the recipe to get.
        *
        * @return \Drupal\Core\Recipe\Recipe
        * The recipe object.
        *
        * @throws \Drupal\Core\Recipe\UnknownRecipeException
        * Thrown when the recipe cannot be found.
        */
        public static function getIncludedRecipe(string $include_path, string $name): Recipe {
        // In order to allow recipes to include core provided recipes, $name can be
        // a Drupal root relative path to a recipe folder. For example, a recipe can
        // include the core provided 'article_tags' recipe by listing the recipe as
        // 'core/recipes/article_tags'. It is strongly recommended not to rely on
        // relative paths for including recipes. Required recipes should be put in
        // the same parent directory as the recipe being applied. Note, only linux
        // style directory separators are supported. PHP on Windows can resolve the
        // mix of directory separators.
        if (str_contains($name, '/')) {
        $path = \Drupal::root() . "/$name/recipe.yml";
        }
        else {
        $path = $include_path . "/$name/recipe.yml";
        }
        if (file_exists($path)) {
        return Recipe::createFromDirectory(dirname($path));
        }
        $search_path = dirname($path, 2);
        throw new UnknownRecipeException($name, $search_path, sprintf("Can not find the %s recipe, search path: %s", $name, $search_path));
        }
        /**
        ......
        <?php
        declare(strict_types=1);
        namespace Drupal\Core\Recipe;
        /**
        * @internal
        * This API is experimental.
        */
        final class RecipeDiscovery {
        /**
        * Constructs a recipe discovery object.
        *
        * @param string $path
        * The path will be searched folders containing a recipe.yml. There will be
        * no traversal further into the directory structure.
        */
        public function __construct(protected string $path) {
        }
        /**
        * Gets a recipe 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 {
        // In order to allow recipes to include core provided recipes, $name can be
        // a Drupal root relative path to a recipe folder. For example, a recipe can
        // include the core provided 'article_tags' recipe by listing the recipe as
        // 'core/recipes/article_tags'. It is strongly recommended not to rely on
        // relative paths for including recipes. Required recipes should be put in
        // the same parent directory as the recipe being applied. Note, only linux
        // style directory separators are supported. PHP on Windows can resolve the
        // mix of directory separators.
        if (str_contains($name, '/')) {
        $path = \Drupal::root() . "/$name/recipe.yml";
        }
        else {
        $path = $this->path . "/$name/recipe.yml";
        }
        if (file_exists($path)) {
        return Recipe::createFromDirectory(dirname($path));
        }
        $search_path = dirname($path, 2);
        throw new UnknownRecipeException($name, $search_path, sprintf("Can not find the %s recipe, search path: %s", $name, $search_path));
        }
        }
        ......@@ -6,20 +6,19 @@
        use Drupal\Core\Recipe\Recipe;
        use Drupal\Core\Recipe\RecipeConfigurator;
        use Drupal\Core\Recipe\RecipeDiscovery;
        use Drupal\Core\Recipe\UnknownRecipeException;
        use Drupal\KernelTests\KernelTestBase;
        /**
        * @covers \Drupal\Core\Recipe\RecipeConfigurator
        * @coversDefaultClass \Drupal\Core\Recipe\RecipeConfigurator
        * @group Recipe
        */
        class RecipeConfiguratorTest extends KernelTestBase {
        public function testRecipeConfigurator(): void {
        $discovery = new RecipeDiscovery('core/tests/fixtures/recipes');
        $recipe_configurator = new RecipeConfigurator(
        ['install_two_modules', 'install_node_with_config', 'recipe_include'],
        $discovery
        'core/tests/fixtures/recipes'
        );
        // Private method "listAllRecipes".
        $reflection = new \ReflectionMethod('\Drupal\Core\Recipe\RecipeConfigurator', 'listAllRecipes');
        ......@@ -50,4 +49,37 @@ public function testRecipeConfigurator(): void {
        $this->assertEquals(1, array_count_values($recipe_extensions)['field']);
        }
        /**
        * Tests that RecipeConfigurator can load recipes.
        *
        * @testWith ["install_two_modules", "Install two modules"]
        * ["recipe_include", "Recipe include"]
        *
        * @covers ::getIncludedRecipe
        */
        public function testIncludedRecipeLoader(string $recipe, string $name): void {
        $recipe = RecipeConfigurator::getIncludedRecipe('core/tests/fixtures/recipes', $recipe);
        $this->assertSame($name, $recipe->name);
        }
        /**
        * Tests exception thrown when RecipeConfigurator cannot find a recipe.
        *
        * @testWith ["no_recipe"]
        * ["does_not_exist"]
        *
        * @covers ::getIncludedRecipe
        */
        public function testIncludedRecipeLoaderException(string $recipe): void {
        try {
        RecipeConfigurator::getIncludedRecipe('core/tests/fixtures/recipes', $recipe);
        $this->fail('Expected exception not thrown');
        }
        catch (UnknownRecipeException $e) {
        $this->assertSame($recipe, $e->recipe);
        $this->assertSame('core/tests/fixtures/recipes', $e->searchPath);
        $this->assertSame('Can not find the ' . $recipe . ' recipe, search path: ' . $e->searchPath, $e->getMessage());
        }
        }
        }
        <?php
        declare(strict_types=1);
        namespace Drupal\KernelTests\Core\Recipe;
        use Drupal\Core\Recipe\RecipeDiscovery;
        use Drupal\Core\Recipe\UnknownRecipeException;
        use Drupal\KernelTests\KernelTestBase;
        /**
        * @coversDefaultClass \Drupal\Core\Recipe\RecipeDiscovery
        * @group Recipe
        */
        class RecipeDiscoveryTest extends KernelTestBase {
        /**
        * Tests that recipe discovery can find recipes.
        *
        * @testWith ["install_two_modules", "Install two modules"]
        * ["recipe_include", "Recipe include"]
        */
        public function testRecipeDiscovery(string $recipe, string $name): void {
        $discovery = new RecipeDiscovery('core/tests/fixtures/recipes');
        $recipe = $discovery->getRecipe($recipe);
        $this->assertSame($name, $recipe->name);
        }
        /**
        * Tests the exception thrown when recipe discovery cannot find a recipe.
        *
        * @testWith ["no_recipe"]
        * ["does_not_exist"]
        */
        public function testRecipeDiscoveryException(string $recipe): void {
        $discovery = new RecipeDiscovery('core/tests/fixtures/recipes');
        try {
        $discovery->getRecipe($recipe);
        $this->fail('Expected exception not thrown');
        }
        catch (UnknownRecipeException $e) {
        $this->assertSame($recipe, $e->recipe);
        $this->assertSame('core/tests/fixtures/recipes', $e->searchPath);
        $this->assertSame('Can not find the ' . $recipe . ' recipe, search path: ' . $e->searchPath, $e->getMessage());
        }
        }
        }
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Please register or to comment