trait RecipeInputFormTrait

Defines helper methods for forms which collect input on behalf of recipes.

Hierarchy

1 file declares its use of RecipeInputFormTrait
FormTestRecipeInputForm.php in core/modules/system/tests/modules/form_test/src/Form/FormTestRecipeInputForm.php

File

core/lib/Drupal/Core/Recipe/RecipeInputFormTrait.php, line 16

Namespace

Drupal\Core\Recipe
View source
trait RecipeInputFormTrait {
  
  /**
   * Generates a tree of form elements for a recipe's inputs.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   A recipe.
   *
   * @return array[]
   *   A nested array of form elements for collecting input values for the given
   *   recipe and its dependencies. The elements will be grouped by the recipe
   *   that defined the input -- for example, $return['recipe_name']['input1'],
   *   $return['recipe_name']['input2'], $return['dependency']['input_name'],
   *   and so forth. The returned array will have the `#tree` property set to
   *   TRUE.
   */
  protected function buildRecipeInputForm(Recipe $recipe) : array {
    $collector = new class  implements InputCollectorInterface {
      
      /**
       * A form array containing the input elements for the given recipe.
       *
       * This will be a tree of input elements, grouped by the name of the
       * recipe that defines them. For example:
       *
       * @code
       * $form = [
       *   'recipe_1' => [
       *     'input_1' => [
       *       '#type' => 'textfield',
       *       '#title' => 'Some input value',
       *     ],
       *     'input_2' => [
       *       '#type' => 'checkbox',
       *       '#title' => 'Enable some feature or other?',
       *     ],
       *   ],
       *   'dependency_recipe' => [
       *     'input_1' => [
       *       '#type' => 'textarea',
       *       '#title' => 'An input defined by a dependency of recipe_1',
       *     ],
       *   ],
       *   '#tree' => TRUE,
       * ];
       * @endcode
       *
       * The `#tree` property will always be set to TRUE.
       */
      public array $form = [];
      
      /**
       * {@inheritdoc}
       */
      public function collectValue(string $name, DataDefinitionInterface $definition, mixed $default_value) : mixed {
        $element = $definition->getSetting('form');
        if ($element) {
          $element += [
            '#description' => $definition->getDescription(),
            '#default_value' => $default_value,
            '#type' => 'value',
          ];
          // Recipe inputs are required by default, unless they are single
          // checkboxes, in which case the `#required` behavior doesn't make
          // a lot of sense because it forces the user to check the box.
          $element['#required'] ??= $element['#type'] !== 'checkbox';
          NestedArray::setValue($this->form, explode('.', $name, 2), $element);
          // Always return the input elements as a tree.
          $this->form['#tree'] = TRUE;
        }
        return $default_value;
      }

};
    $recipe->input
      ->collectAll($collector);
    return $collector->form;
  }
  
  /**
   * Validates user-inputted values to a recipe and its dependencies.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   A recipe.
   * @param array $form
   *   The form being validated, which should include the tree of elements
   *   returned by ::buildRecipeInputForm().
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state. The values should be organized in the tree
   *   structure that was returned by ::buildRecipeInputForm().
   */
  protected function validateRecipeInput(Recipe $recipe, array &$form, FormStateInterface $form_state) : void {
    try {
      $this->setRecipeInput($recipe, $form_state);
    } catch (ValidationFailedException $e) {
      $data = $e->getValue();
      if ($data instanceof TypedDataInterface) {
        $element = NestedArray::getValue($form, explode('.', $data->getName(), 2));
        $form_state->setError($element, $e->getMessage());
      }
      else {
        // If the data isn't a typed data object, we have no idea how to handle
        // the situation, so just re-throw the exception.
        throw $e;
      }
    }
  }
  
  /**
   * Supplies user-inputted values to a recipe and its dependencies.
   *
   * @param \Drupal\Core\Recipe\Recipe $recipe
   *   A recipe.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form state. The values should be organized in the tree
   *   structure that was returned by ::buildRecipeInputForm().
   */
  protected function setRecipeInput(Recipe $recipe, FormStateInterface $form_state) : void {
    $recipe->input
      ->collectAll(new class ($form_state) implements InputCollectorInterface {
      public function __construct(private readonly FormStateInterface $formState) {
      }
      
      /**
       * {@inheritdoc}
       */
      public function collectValue(string $name, DataDefinitionInterface $definition, mixed $default_value) : mixed {
        return $this->formState
          ->getValue(explode('.', $name, 2), $default_value);
      }

});
  }

}

Members

Title Sort descending Modifiers Object type Summary
RecipeInputFormTrait::buildRecipeInputForm protected function Generates a tree of form elements for a recipe's inputs.
RecipeInputFormTrait::setRecipeInput protected function Supplies user-inputted values to a recipe and its dependencies.
RecipeInputFormTrait::validateRecipeInput protected function Validates user-inputted values to a recipe and its dependencies.

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