function Recipe::parse

Same name in other branches
  1. 11.x core/lib/Drupal/Core/Recipe/Recipe.php \Drupal\Core\Recipe\Recipe::parse()

Parses and validates a recipe.yml file.

Parameters

string $file: The path of a recipe.yml file.

Return value

mixed[] The parsed and validated data from the file.

Throws

\Drupal\Core\Recipe\RecipeFileException Thrown if the recipe.yml file is unreadable, invalid, or cannot be validated.

1 call to Recipe::parse()
Recipe::createFromDirectory in core/lib/Drupal/Core/Recipe/Recipe.php
Creates a recipe object from the provided path.

File

core/lib/Drupal/Core/Recipe/Recipe.php, line 80

Class

Recipe
@internal This API is experimental.

Namespace

Drupal\Core\Recipe

Code

private static function parse(string $file) : array {
    if (!file_exists($file)) {
        throw new RecipeFileException($file, "There is no {$file} file");
    }
    $recipe_contents = file_get_contents($file);
    if (!$recipe_contents) {
        throw new RecipeFileException($file, "{$file} does not exist or could not be read.");
    }
    // Certain parts of our validation need to be able to scan for other
    // recipes.
    // @see ::validateRecipeExists()
    // @see ::validateConfigActions()
    $include_path = dirname($file, 2);
    $constraints = new Collection([
        'name' => new Required([
            new Type('string'),
            new NotBlank(),
            // Matching `type: label` in core.data_types.schema.yml.
new RegexConstraint(pattern: '/([^\\PC])/u', message: 'Recipe names cannot span multiple lines or contain control characters.', match: FALSE),
        ]),
        'description' => new Optional([
            new NotBlank(),
            // Matching `type: text` in core.data_types.schema.yml.
new RegexConstraint(pattern: '/([^\\PC\\x09\\x0a\\x0d])/u', message: 'The recipe description cannot contain control characters, only visible characters.', match: FALSE),
        ]),
        'type' => new Optional([
            new Type('string'),
            new NotBlank(),
            // Matching `type: label` in core.data_types.schema.yml.
new RegexConstraint(pattern: '/([^\\PC])/u', message: 'Recipe type cannot span multiple lines or contain control characters.', match: FALSE),
        ]),
        'recipes' => new Optional([
            new All([
                new Type('string'),
                new NotBlank(),
                // If recipe depends on itself, ::validateRecipeExists() will set off
                // an infinite loop. We can avoid that by skipping that validation if
                // the recipe depends on itself, which is what Sequentially does.
new Sequentially([
                    new NotIdenticalTo(value: basename(dirname($file)), message: 'The {{ compared_value }} recipe cannot depend on itself.'),
                    new Callback(callback: self::validateRecipeExists(...), payload: $include_path),
                ]),
            ]),
        ]),
        // @todo https://www.drupal.org/i/3424603 Validate the corresponding
        //   import.
'install' => new Optional([
            new All([
                new Type('string'),
                new Sequentially([
                    new NotBlank(),
                    new Callback(self::validateExtensionIsAvailable(...)),
                ]),
            ]),
        ]),
        'config' => new Optional([
            new Collection([
                // Each entry in the `import` list can either be `*` (import all of
                // the extension's config), or a list of config names to import from
                // the extension.
                // @todo https://www.drupal.org/i/3439716 Validate config file name,
                //   if given.
'import' => new Optional([
                    new All([
                        new AtLeastOneOf([
                            new IdenticalTo('*'),
                            new All([
                                new Type('string'),
                                new NotBlank(),
                                new Regex('/^.+\\./'),
                            ]),
                        ]),
                    ]),
                ]),
                'actions' => new Optional([
                    new All([
                        new Type('array'),
                        new NotBlank(),
                        new Callback(callback: self::validateConfigActions(...), payload: $include_path),
                    ]),
                ]),
            ]),
        ]),
        'content' => new Optional([
            new Type('array'),
        ]),
    ]);
    $recipe_data = Yaml::decode($recipe_contents);
    
    /** @var \Symfony\Component\Validator\ConstraintViolationList $violations */
    $violations = Validation::createValidator()->validate($recipe_data, $constraints);
    if (count($violations) > 0) {
        throw RecipeFileException::fromViolationList($file, $violations);
    }
    $recipe_data += [
        'description' => '',
        'type' => '',
        'recipes' => [],
        'install' => [],
        'config' => [],
        'content' => [],
    ];
    return $recipe_data;
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.