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
16 merge requests!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
+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.
Finish editing this message first!
Please register or to comment