Loading core/lib/Drupal/Core/Recipe/Recipe.php +47 −1 Changes for core/lib/Drupal/Core/Recipe/Recipe.php: 47 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ use Drupal\Core\DefaultContent\Finder; use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ThemeExtensionList; use Drupal\Component\Serialization\Yaml; Loading Loading @@ -60,6 +61,8 @@ final class Recipe { * The default content finder. * @param string $path * The recipe's path. * @param array $extra * Any extra information to expose to specific modules. */ public function __construct( public readonly string $name, Loading @@ -71,6 +74,7 @@ public function __construct( public readonly InputConfigurator $input, public readonly Finder $content, public readonly string $path, private readonly array $extra, ) {} /** Loading @@ -90,7 +94,7 @@ public static function createFromDirectory(string $path): static { $config = new ConfigConfigurator($recipe_data['config'], $path, \Drupal::service('config.storage')); $input = new InputConfigurator($recipe_data['input'] ?? [], $recipes, basename($path), \Drupal::typedDataManager()); $content = new Finder($path . '/content'); return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $recipes, $install, $config, $input, $content, $path); return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $recipes, $install, $config, $input, $content, $path, $recipe_data['extra'] ?? []); } /** Loading Loading @@ -296,6 +300,12 @@ private static function parse(string $file): array { 'content' => new Optional([ new Type('array'), ]), 'extra' => new Optional([ new Sequentially([ new Type('associative_array'), new Callback(self::validateKeysAreValidExtensionNames(...)), ]), ]), ]); $recipe_data = Yaml::decode($recipe_contents); Loading Loading @@ -423,4 +433,40 @@ private static function validateConfigActions(mixed $value, ExecutionContextInte } } /** * Validates that the keys of an array are valid extension names. * * Note that the keys do not have to be the names of extensions that are * installed, or even extensions that exist. They just have to follow the * form of a valid extension name. * * @param array $value * The array being validated. * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context * The validator execution context. */ private static function validateKeysAreValidExtensionNames(array $value, ExecutionContextInterface $context): void { $keys = array_keys($value); foreach ($keys as $key) { if (!preg_match(ExtensionDiscovery::PHP_FUNCTION_PATTERN, $key)) { $context->addViolation('%name is not a valid extension name.', [ '%name' => $key, ]); } } } /** * Returns extra information to expose to a particular extension. * * @param string $extension_name * The name of a Drupal extension. * * @return mixed * The extra data exposed to the given extension, or NULL if there is none. */ public function getExtra(string $extension_name): mixed { return $this->extra[$extension_name] ?? NULL; } } core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php +15 −0 Changes for core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -98,4 +98,19 @@ public function testImplicitlyRequiredModule(): void { $this->assertIsObject($recipe); } /** * Tests getting extra extension-specific info from a recipe. * * @covers ::getExtra */ public function testExtra(): void { $recipe = $this->createRecipe([ 'name' => 'Getting extra info', 'extra' => [ 'special_sauce' => 'Wasabi', ], ]); $this->assertSame('Wasabi', $recipe->getExtra('special_sauce')); } } core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php +39 −0 Changes for core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php: 39 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -719,6 +719,45 @@ public static function providerRecipeValidation(): iterable { default: source: config config: ['system.site', 'mail'] YAML, NULL, ]; yield 'extra is present and not an array' => [ <<<YAML name: Bad extra extra: 'yes!' YAML, [ '[extra]' => ['This value should be of type associative_array.'], ], ]; yield 'extra is an indexed array' => [ <<<YAML name: Bad extra extra: - one - two YAML, [ '[extra]' => ['This value should be of type associative_array.'], ], ]; yield 'invalid key in extra' => [ <<<YAML name: Bad extra extra: 'not a valid extension name': true YAML, [ '[extra]' => ['not a valid extension name is not a valid extension name.'], ], ]; yield 'valid extra' => [ <<<YAML name: Bad extra extra: project_browser: yes: sir YAML, NULL, ]; Loading Loading
core/lib/Drupal/Core/Recipe/Recipe.php +47 −1 Changes for core/lib/Drupal/Core/Recipe/Recipe.php: 47 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ use Drupal\Core\DefaultContent\Finder; use Drupal\Core\Extension\Dependency; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ThemeExtensionList; use Drupal\Component\Serialization\Yaml; Loading Loading @@ -60,6 +61,8 @@ final class Recipe { * The default content finder. * @param string $path * The recipe's path. * @param array $extra * Any extra information to expose to specific modules. */ public function __construct( public readonly string $name, Loading @@ -71,6 +74,7 @@ public function __construct( public readonly InputConfigurator $input, public readonly Finder $content, public readonly string $path, private readonly array $extra, ) {} /** Loading @@ -90,7 +94,7 @@ public static function createFromDirectory(string $path): static { $config = new ConfigConfigurator($recipe_data['config'], $path, \Drupal::service('config.storage')); $input = new InputConfigurator($recipe_data['input'] ?? [], $recipes, basename($path), \Drupal::typedDataManager()); $content = new Finder($path . '/content'); return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $recipes, $install, $config, $input, $content, $path); return new static($recipe_data['name'], $recipe_data['description'], $recipe_data['type'], $recipes, $install, $config, $input, $content, $path, $recipe_data['extra'] ?? []); } /** Loading Loading @@ -296,6 +300,12 @@ private static function parse(string $file): array { 'content' => new Optional([ new Type('array'), ]), 'extra' => new Optional([ new Sequentially([ new Type('associative_array'), new Callback(self::validateKeysAreValidExtensionNames(...)), ]), ]), ]); $recipe_data = Yaml::decode($recipe_contents); Loading Loading @@ -423,4 +433,40 @@ private static function validateConfigActions(mixed $value, ExecutionContextInte } } /** * Validates that the keys of an array are valid extension names. * * Note that the keys do not have to be the names of extensions that are * installed, or even extensions that exist. They just have to follow the * form of a valid extension name. * * @param array $value * The array being validated. * @param \Symfony\Component\Validator\Context\ExecutionContextInterface $context * The validator execution context. */ private static function validateKeysAreValidExtensionNames(array $value, ExecutionContextInterface $context): void { $keys = array_keys($value); foreach ($keys as $key) { if (!preg_match(ExtensionDiscovery::PHP_FUNCTION_PATTERN, $key)) { $context->addViolation('%name is not a valid extension name.', [ '%name' => $key, ]); } } } /** * Returns extra information to expose to a particular extension. * * @param string $extension_name * The name of a Drupal extension. * * @return mixed * The extra data exposed to the given extension, or NULL if there is none. */ public function getExtra(string $extension_name): mixed { return $this->extra[$extension_name] ?? NULL; } }
core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php +15 −0 Changes for core/tests/Drupal/KernelTests/Core/Recipe/RecipeTest.php: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -98,4 +98,19 @@ public function testImplicitlyRequiredModule(): void { $this->assertIsObject($recipe); } /** * Tests getting extra extension-specific info from a recipe. * * @covers ::getExtra */ public function testExtra(): void { $recipe = $this->createRecipe([ 'name' => 'Getting extra info', 'extra' => [ 'special_sauce' => 'Wasabi', ], ]); $this->assertSame('Wasabi', $recipe->getExtra('special_sauce')); } }
core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php +39 −0 Changes for core/tests/Drupal/KernelTests/Core/Recipe/RecipeValidationTest.php: 39 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -719,6 +719,45 @@ public static function providerRecipeValidation(): iterable { default: source: config config: ['system.site', 'mail'] YAML, NULL, ]; yield 'extra is present and not an array' => [ <<<YAML name: Bad extra extra: 'yes!' YAML, [ '[extra]' => ['This value should be of type associative_array.'], ], ]; yield 'extra is an indexed array' => [ <<<YAML name: Bad extra extra: - one - two YAML, [ '[extra]' => ['This value should be of type associative_array.'], ], ]; yield 'invalid key in extra' => [ <<<YAML name: Bad extra extra: 'not a valid extension name': true YAML, [ '[extra]' => ['not a valid extension name is not a valid extension name.'], ], ]; yield 'valid extra' => [ <<<YAML name: Bad extra extra: project_browser: yes: sir YAML, NULL, ]; Loading