RecipeInputFormTrait.php

Namespace

Drupal\Core\Recipe

File

core/lib/Drupal/Core/Recipe/RecipeInputFormTrait.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Core\Recipe;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Symfony\Component\Validator\Exception\ValidationFailedException;

/**
 * Defines helper methods for forms which collect input on behalf of recipes.
 */
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.
             *
             * @var array
             */
            // phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
            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,
                    ];
                    // Recipe inputs are always required.
                    $element['#required'] = TRUE;
                    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(FormStateInterface $formState) {
            }
            
            /**
             * {@inheritdoc}
             */
            public function collectValue(string $name, DataDefinitionInterface $definition, mixed $default_value) : mixed {
                return $this->formState
                    ->getValue(explode('.', $name, 2), $default_value);
            }

});
    }

}

Traits

Title Deprecated Summary
RecipeInputFormTrait Defines helper methods for forms which collect input on behalf of recipes.

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