Comment ajouter un gestionnaire de validation personnalisé à un formulaire / champ existant?

21

Comment ajouter un gestionnaire de validation personnalisé à un formulaire (ou champ de formulaire) existant dans Drupal 8?

J'ai un formulaire que je n'ai pas créé. Je souhaite ajouter mes propres règles de validation sur certains champs lors de l'envoi du formulaire.

Pour Drupal 7, Validation personnalisée d'un formulaire? explique comment implémenter hook_form_alter()puis ajouter votre gestionnaire de validation] [1] au $form['#validate']tableau, mais dans Drupal 8, les formes sont des classes. La validation se fait par la validateForm()méthode et je ne sais pas comment y brancher mon code.

AngularChef
la source
1
Ce n'est pas exactement un doublon. Ma question est pour D8, votre lien est pour D7.
AngularChef
Je suis tombé sur cela aujourd'hui et je voulais juste noter pour les autres si vous n'utilisez pas POST (je voulais une soumission d'URL vers une page de vue existante) ni le validateForm ni le run submitForm. Avec le recul, c'est évident .... mais j'ai passé 30 minutes à essayer de comprendre avant de réaliser ....: /
ben.hamelin

Réponses:

19

La #validatepropriété est toujours utilisée dans Drupal 8. (Avec la solution d'Adi, vous remplacerez le validateur existant)

Si vous souhaitez ajouter votre validateur personnalisé en plus de celui par défaut, vous devrez ajouter quelque chose comme ceci dans hook_form_FORM_ID_alter (ou similaire):

$form['#validate'][] = 'my_test_validate';
Shabir A.
la source
Merci, Shabir. Ainsi, l'ajout d'un validateur personnalisé fonctionne de la même manière en D7 et D8. ;)
AngularChef
Exactement, consultez le code du module de nœud. il y a beaucoup d'exemples là
Shabir A.
2
Je viens de l'essayer et cela a parfaitement fonctionné, merci. Veuillez noter que contrairement à ce que dit la référence de l'API de formulaire D8 pour #validate(votre lien), vous ne devez pas l'utiliser $form_statecomme un tableau (à la manière D7), mais comme un objet implémentant FormStateInterface(à la manière D8). En d'autres termes, le code de votre validateur personnalisé doit être analogue au code que vous trouveriez dans une validateForm()méthode originale .
AngularChef
25

Berdir a donné la bonne réponse, qu'une contrainte est la bonne façon de procéder pour ajouter la validation à un champ dans Drupal 8. Voici un exemple.

Dans l'exemple ci-dessous, je vais travailler avec un nœud de type podcast, qui a le champ de valeur unique field_podcast_duration. La valeur de ce champ doit être formatée en HH: MM: SS (heures, minutes et secondes).

Pour créer une contrainte, deux classes doivent être ajoutées. Le premier est la définition de contrainte, et le second est le validateur de contrainte. Ces deux sont des plugins, dans l'espace de noms de Drupal\[MODULENAME]\Plugin\Validation\Constraint.

Tout d'abord, la définition de la contrainte. Notez que l'ID du plugin est donné comme 'PodcastDuration', dans l'annotation (commentaire) de la classe. Cela sera utilisé plus bas.

namespace Drupal\[MODULENAME]\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Checks that the submitted duration is of the format HH:MM:SS
 *
 * @Constraint(
 *   id = "PodcastDuration",
 *   label = @Translation("Podcast Duration", context = "Validation"),
 * )
 */
class PodcastDurationConstraint extends Constraint {

  // The message that will be shown if the format is incorrect.
  public $incorrectDurationFormat = 'The duration must be in the format HH:MM:SS or HHH:MM:SS. You provided %duration';
}

Ensuite, nous devons fournir le validateur de contraintes. Ce nom de cette classe sera le nom de la classe ci-dessus, avec en Validatorannexe:

namespace Drupal\[MODULENAME]\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Validates the PodcastDuration constraint.
 */
class PodcastDurationConstraintValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate($items, Constraint $constraint) {
    // This is a single-item field so we only need to
    // validate the first item
    $item = $items->first();

    // If there is no value we don't need to validate anything
    if (!isset($item)) {
      return NULL;
    }

    // Check that the value is in the format HH:MM:SS
    if (!preg_match('/^[0-9]{1,2}:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}$/', $item->value)) {
      // The value is an incorrect format, so we set a 'violation'
      // aka error. The key we use for the constraint is the key
      // we set in the constraint, in this case $incorrectDurationFormat.
      $this->context->addViolation($constraint->incorrectDurationFormat, ['%duration' => $item->value]);
    }
  }
}

Enfin, nous devons dire à Drupal d'utiliser notre contrainte sur field_podcast_durationle podcasttype de nœud. Nous le faisons en hook_entity_bundle_field_info_alter():

use Drupal\Core\Entity\EntityTypeInterface;

function HOOK_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  if (!empty($fields['field_podcast_duration'])) {
    $fields['field_podcast_duration']->addConstraint('PodcastDuration');
  }
}
Jaypan
la source
Si vous finissez par avoir besoin d'autres valeurs de champ pour valider votre champ, vous pouvez ajouter une contrainte au type de contenu. Voir cet article de blog: lakshminp.com/entity-validation-drupal-8-part-2
ummdorian
1
L'API de validation de formulaire D8 a été expliquée en détail sur Drupal.org ici. Fournir une contrainte de validation personnalisée
Sukhjinder Singh
1
Étant donné que cette question concerne spécifiquement l'API de formulaire, et non l'API de champ, comment associer cette contrainte à un élément de formulaire (pas un champ d'entité)?
aaronbauman
Les éléments de formulaire ne peuvent pas avoir de contraintes. Vous pouvez ajouter une validation à un élément de formulaire spécifique à l'aide de #element_validate. Voir la meilleure réponse sur ce fil - cela fonctionne de la même manière en D8 que D7 drupal.stackexchange.com/questions/86990/…
Jaypan
Assurez-vous de vérifier $item = $items->first();et de renvoyer NULL s'il n'y a rien, sinon vous obtiendrez une erreur fatale lors de la modification du champ: TypeError: l'argument 2 transmis à Drupal \ Component \ Utility \ NestedArray :: getValue () doit être du type tableau, null donné, appelé dans /data/app/core/lib/Drupal/Core/Field/WidgetBase.php sur la ligne 407 dans Drupal \ Component \ Utility \ NestedArray :: getValue () (ligne 69 de core / lib / Drupal / Component /Utility/NestedArray.php).
Ivan Zugec
16

La façon correcte de le faire pour une entité de contenu comme un nœud est de l'enregistrer en tant que contrainte.

Voir forum_entity_bundle_field_info_alter()et le correspondant? ForumLeafcontrainte de validation (notez que deux classes sont nécessaires).

C'est un peu plus compliqué au début, mais l'avantage est qu'il est intégré à l'API de validation, donc votre validation n'est pas limitée au système de formulaire mais peut, par exemple, également fonctionner avec des nœuds soumis via l'API REST.

Berdir
la source
Bon point: c'est quelque chose de nouveau pour qui écrivait du code pour Drupal 7. Je suis sûr que de nombreux utilisateurs essaieront d'ajouter des gestionnaires de validation lorsqu'une contrainte est plus appropriée.
kiamlaluno
Berdir: J'ai exploré cette option en essayant de l'implémenter hook_entity_bundle_field_info_alter()(comme décrit ici ) mais cela n'a jamais fonctionné ... Il semble y avoir un problème documenté avec ce hook: drupal.org/node/2346347 .
AngularChef
Il y a des problèmes mais je ne pense pas qu'ils soient liés à votre problème. forum.module montre que cela fonctionne. Partagez votre code, sinon il n'est pas possible de signaler d'éventuels problèmes dans votre implémentation.
Berdir
1
J'aimerais utiliser cette méthode, mais jusqu'à ce qu'il y ait un bon exemple de la façon de l'utiliser avec des types de données (c'est-à-dire non spécifiques au champ) pour vérifier si une condition externe est remplie, je suis bloqué avec les modifications du formulaire. L'article n'a pas approfondi cela. Quelqu'un pourrait-il me montrer un endroit utile ou l'afficher ici? Merci.
colan
que faire si nous devons ajouter une validation existante comme \ Drupal \ user \ Form \ UserLoginForm :: validateName (). En d7, c'était simple comme $ form ['# validate'] = array ('user_login_name_validate', 'myother_validaion',); Mais il semble que le module sans contribution ait
kiranking
8

Je veux ajouter un peu plus de lumière sur cette question. L'ajout de la validation est exactement le même que précédemment: dans hook_form_alter:

$form['#validate'][] = '_form_validation_number_title_validate';

L'utilisation de l'objet values ​​à l'intérieur de $ form_state dans la fonction de validation est cependant un peu différente. par exemple:

function _form_validation_number_title_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {

  if ($form_state->hasValue('title')) {
     $title = $form_state->getValue('title');

     if (!is_numeric($title[0]['value'])) {
        $form_state->setErrorByName('title', t('Your title should be number'));
     }

  }
}

Donc pas avec un accès direct à l'objet de variables privées, mais plutôt avec une fonction getter.

pour plus d'informations, vous pouvez voir un exemple complet dans mon github: https://github.com/flesheater/drupal8_modules_experiments/blob/master/webham_formvalidation/webham_formvalidation.module

à votre santé!

Nikolay Borisov
la source
En effet, ça l'est. Tout comme je l'ai écrit dans mon commentaire ci-dessus. ;)
AngularChef
7

C'est à peu près la même chose qu'en D7. Un exemple complet:

mymodule.module :

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_FORM_ID_alter() for the FORM_ID() form.
 */
function mymodule_form_FORM_ID_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form['#validate'][] = '_mymodule_form_FORM_ID_validate';
}

/**
 * Validates submission values in the FORM_ID() form.
 */
function _mymodule_form_FORM_ID_validate(array &$form, FormStateInterface $form_state) {
  // Validation code here
}
nicholas.alipaz
la source
C'est assez proche. Seul le hook_form_FORM_ID_alterbesoin de l'ID du formulaire. Vous pouvez avoir la fonction de validation personnalisée comme vous voulez. Suivez également le guide API ici pour les paramètres appropriés.
mikeDOTexe
Avant cela, comment obtenir également l'ID du formulaire, où vérifier ce code.
logeshvaran
3

En complément de ces bonnes réponses, j'ajouterais:

$form['#validate'][] = 'Drupal\your_custom_module_name\CustomClass::customValidate';

C'est comment appeler une méthode de classe distante pour une validation de formulaire. Je pense qu'il vaut mieux que d'appeler une fonction ci-dessus dans le fichier de module comme dans l'exemple donné.

Pauleau
la source
Il n'est plus nécessaire de sauter dans le code procédural de OO.
colan
1

vous pouvez utiliser le module de validation côté client . Quelques détails supplémentaires (à partir de sa page de projet):

... ajoute la validation côté client (également appelée "validation de formulaire Ajax") pour tous les formulaires et formulaires Web à l'aide de jquery.validate . Le fichier jquery.validate.js inclus est corrigé car nous devions pouvoir masquer les messages vides.

Mohammed ATIFI
la source