Unverified Commit b436c8c9 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3503190 by phenaproxima, thejimbirch: Allow recipes to contain an...

Issue #3503190 by phenaproxima, thejimbirch: Allow recipes to contain an "extra" property with arbitrary information for specific modules to use

(cherry picked from commit c9212079)
parent 6ada4987
Loading
Loading
Loading
Loading
Loading
+47 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -71,6 +74,7 @@ public function __construct(
    public readonly InputConfigurator $input,
    public readonly Finder $content,
    public readonly string $path,
    private readonly array $extra,
  ) {}

  /**
@@ -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'] ?? []);
  }

  /**
@@ -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);
@@ -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;
  }

}
+15 −0
Original line number Diff line number Diff line
@@ -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'));
  }

}
+39 −0
Original line number Diff line number Diff line
@@ -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,
    ];