function Recipe::parse
Same name in other branches
- 10 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 113
Class
- Recipe
- @internal This API is experimental.
Namespace
Drupal\Core\RecipeCode
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(...)),
]),
]),
]),
'input' => new Optional([
new Type('associative_array'),
new All([
new Collection(fields: [
// Every input definition must have a description.
'description' => [
new Type('string'),
new NotBlank(),
],
// There can be an optional set of constraints, which is an
// associative array of arrays, as in config schema.
'constraints' => new Optional([
new Type('associative_array'),
]),
'data_type' => [
// The data type must be known to the typed data system.
\Drupal::service('validation.constraint')->createInstance('PluginExists', [
'manager' => 'typed_data_manager',
// Only primitives are supported because it's not always clear
// how to collect, validate, and cast complex structures.
'interface' => PrimitiveInterface::class,
]),
],
// The `prompt` and `form` elements, though optional, have their
// own sets of constraints,
'prompt' => new Optional([
new Collection([
'method' => [
new Choice([
'ask',
'askHidden',
'confirm',
'choice',
]),
],
'arguments' => new Optional([
new Type('associative_array'),
]),
]),
]),
'form' => new Optional([
new Sequentially([
new Type('associative_array'),
// Every element in the `form` array has to be a form API
// property, prefixed with `#`. Because recipe inputs can only
// be primitive data types, child elements aren't allowed.
new Callback(function (array $element, ExecutionContextInterface $context) : void {
if (Element::children($element)) {
$context->addViolation('Form elements for recipe inputs cannot have child elements.');
}
}),
]),
]),
// Every input must define a default value.
'default' => new Required([
new Collection([
'source' => new Required([
new Choice([
'value',
'config',
]),
]),
'value' => new Optional(),
'config' => new Optional([
new Sequentially([
new Type('list'),
new Count(2),
new All([
new Type('string'),
new NotBlank(),
]),
]),
]),
]),
new Callback(self::validateDefaultValueDefinition(...)),
]),
]),
]),
]),
'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('/^.+\\./'),
]),
]),
]),
]),
'strict' => new Optional([
new AtLeastOneOf([
new Type('boolean'),
new All([
new Type('string'),
new NotBlank(),
new Regex('/^.+\\./'),
]),
], message: 'This value must be a boolean, or a list of config names.', includeInternalMessages: FALSE),
]),
'actions' => new Optional([
new All([
new Type('array'),
new NotBlank(),
new Callback(callback: self::validateConfigActions(...), payload: $include_path),
]),
]),
]),
]),
'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);
/** @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.