InputConfigurator.php
Namespace
Drupal\Core\RecipeFile
-
core/
lib/ Drupal/ Core/ Recipe/ InputConfigurator.php
View source
<?php
declare (strict_types=1);
namespace Drupal\Core\Recipe;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Symfony\Component\Validator\Exception\ValidationFailedException;
/**
* Collects and validates input values for a recipe.
*
* @internal
* This API is experimental.
*/
final class InputConfigurator {
/**
* The input data.
*
* @var \Drupal\Core\TypedData\TypedDataInterface[]
*/
private array $data = [];
/**
* The collected input values, or NULL if none have been collected yet.
*
* @var mixed[]|null
*/
private ?array $values = NULL;
/**
* @param array<string, array<string, mixed>> $definitions
* The recipe's input definitions, keyed by name. This is an array of arrays
* where each sub-array has, at minimum:
* - `description`: A short, human-readable description of the input (e.g.,
* what the recipe uses it for).
* - `data_type`: A primitive data type known to the typed data system.
* - `constraints`: An optional array of validation constraints to apply
* to the value. This should be an associative array of arrays, keyed by
* constraint name, where each sub-array is a set of options for that
* constraint (identical to the way validation constraints are defined in
* config schema).
* - `default`: A default value for the input, if it cannot be collected
* the user. See ::getDefaultValue() for more information.
* @param \Drupal\Core\Recipe\RecipeConfigurator $dependencies
* The recipes that this recipe depends on.
* @param string $prefix
* A prefix for each input definition, to give each one a unique name
* when collecting input for multiple recipes. Usually this is the unique
* name of the recipe.
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager
* The typed data manager service.
*/
public function __construct(array $definitions, RecipeConfigurator $dependencies, string $prefix, TypedDataManagerInterface $typedDataManager) {
// Convert the input definitions to typed data definitions.
foreach ($definitions as $name => $definition) {
$data_definition = DataDefinition::create($definition['data_type'])->setDescription($definition['description'])
->setConstraints($definition['constraints'] ?? []);
unset($definition['data_type'], $definition['description'], $definition['constraints']);
$data_definition->setSettings($definition);
$this->data[$name] = $typedDataManager->create($data_definition);
}
}
/**
* Returns the typed data definitions for the inputs defined by this recipe.
*
* This does NOT return the data definitions for inputs defined by this
* recipe's dependencies.
*
* @return \Drupal\Core\TypedData\DataDefinitionInterface[]
* The typed data definitions, keyed by input name.
*/
public function getDataDefinitions() : array {
return array_map(fn(TypedDataInterface $data) => $data->getDataDefinition(), $this->data);
}
/**
* Returns the collected input values, keyed by name.
*
* @return mixed[]
* The collected input values, keyed by name.
*/
public function getValues() : array {
return $this->values ?? [];
}
/**
* Returns the description for all inputs of this recipe and its dependencies.
*
* @return string[]
* The descriptions of every input defined by the recipe and its
* dependencies, keyed by the input's fully qualified name (i.e., prefixed
* by the name of the recipe that defines it).
*/
public function describeAll() : array {
$descriptions = [];
foreach ($this->dependencies->recipes as $dependency) {
$descriptions = array_merge($descriptions, $dependency->input
->describeAll());
}
foreach ($this->getDataDefinitions() as $key => $definition) {
$name = $this->prefix . '.' . $key;
$descriptions[$name] = $definition->getDescription();
}
return $descriptions;
}
/**
* Collects input values for this recipe and its dependencies.
*
* @param \Drupal\Core\Recipe\InputCollectorInterface $collector
* The input collector to use.
* @param string[] $processed
* The names of the recipes for which input has already been collected.
* Internal use only, should not be passed in by calling code.
*
* @throws \Symfony\Component\Validator\Exception\ValidationFailedException
* Thrown if any of the collected values violate their validation
* constraints.
*/
public function collectAll(InputCollectorInterface $collector, array &$processed = []) : void {
if (is_array($this->values)) {
throw new \LogicException('Input values cannot be changed once they have been set.');
}
// Don't bother collecting values for a recipe we've already seen.
if (in_array($this->prefix, $processed, TRUE)) {
return;
}
// First, collect values for the recipe's dependencies.
/** @var \Drupal\Core\Recipe\Recipe $dependency */
foreach ($this->dependencies->recipes as $dependency) {
$dependency->input
->collectAll($collector, $processed);
}
$this->values = [];
foreach ($this->data as $key => $data) {
$definition = $data->getDataDefinition();
$value = $collector->collectValue($this->prefix . '.' . $key, $definition, $this->getDefaultValue($definition));
$data->setValue($value, FALSE);
$violations = $data->validate();
if (count($violations) > 0) {
throw new ValidationFailedException($value, $violations);
}
$this->values[$key] = $data->getCastedValue();
}
$processed[] = $this->prefix;
}
/**
* Returns the default value for an input definition.
*
* @param array $definition
* An input definition. Must contain a `source` element, which can be either
* 'config' or 'value'. If `source` is 'config', then there must also be a
* `config` element, which is a two-element indexed array containing
* (in order) the name of an extant config object, and a property path
* within that object. If `source` is 'value', then there must be a `value`
* element, which will be returned as-is.
*
* @return mixed
* The default value.
*/
private function getDefaultValue(DataDefinition $definition) : mixed {
$settings = $definition->getSetting('default');
if ($settings['source'] === 'config') {
[
$name,
$key,
] = $settings['config'];
$config = \Drupal::config($name);
if ($config->isNew()) {
throw new \RuntimeException("The '{$name}' config object does not exist.");
}
return $config->get($key);
}
return $settings['value'];
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
InputConfigurator | Collects and validates input values for a recipe. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.