¿Cómo agrego un controlador de validación personalizado a un formulario / campo existente?

21

¿Cómo agrego un controlador de validación personalizado a un formulario existente (o campo de formulario) en Drupal 8?

Tengo un formulario que no he creado. Quiero agregar mis propias reglas de validación en algunos campos cuando se envíe el formulario.

Para Drupal 7, ¿ Validación personalizada para un formulario? explica implementar hook_form_alter()y luego agregar su controlador de validación] [1] a la $form['#validate']matriz, pero en Drupal 8 los formularios son clases. La validación se realiza a través del validateForm()método y no sé cómo conectar mi código a eso.

AngularChef
fuente
44
¿Posible duplicado de validación personalizada para un formulario?
bummi
1
No es exactamente un duplicado. Mi pregunta es para D8, su enlace es para D7.
AngularChef
Me encontré con esto hoy y solo quería tener en cuenta a los demás si no está utilizando POST (quería un envío de URL a una página de vista existente) ni se ejecuta validateForm ni submitForm. En retrospectiva, esto es obvio ... pero pasé 30 minutos tratando de resolverlo antes de
darme

Respuestas:

19

La #validatepropiedad todavía se usa en Drupal 8. (Con la solución de Adi, anulará el validador existente)

Si desea agregar su validador personalizado además del predeterminado, deberá agregar algo como esto en hook_form_FORM_ID_alter (o similar):

$form['#validate'][] = 'my_test_validate';
Shabir A.
fuente
Gracias Shabir. Por lo tanto, agregar un validador personalizado funciona igual en D7 y D8. ;)
AngularChef
Exactamente, consulte el código del módulo de nodo. hay muchos ejemplos allí
Shabir A.
2
Lo probé y funcionó perfectamente, gracias. Tenga en cuenta que, al contrario de lo que dice la Referencia de API de formulario D8 para #validate(su enlace), no debe usarlo $form_statecomo una matriz (la forma D7), sino como un objeto de implementación FormStateInterface(la forma D8). En otras palabras, el código en su validador personalizado debe ser análogo al código que encontraría en un validateForm()método original .
AngularChef
25

Berdir dio la respuesta correcta, que una restricción es la forma correcta de agregar validación a un campo en Drupal 8. Aquí hay un ejemplo.

En el siguiente ejemplo, trabajaré con un nodo de tipo podcast, que tiene el campo de valor único field_podcast_duration. El valor de este campo debe formatearse como HH: MM: SS (horas, minutos y segundos).

Para crear una restricción, se deben agregar dos clases. El primero es la definición de restricción, y el segundo es el validador de restricción. Ambos son complementos, en el espacio de nombres de Drupal\[MODULENAME]\Plugin\Validation\Constraint.

Primero, la definición de restricción. Tenga en cuenta que la ID del complemento se proporciona como 'PodcastDuration', en la anotación (comentario) de la clase. Esto se usará más abajo.

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';
}

A continuación, debemos proporcionar el validador de restricciones. Este nombre de esta clase será el nombre de la clase de arriba, junto Validatorcon él:

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]);
    }
  }
}

Finalmente, necesitamos decirle a Drupal que use nuestra restricción field_podcast_durationen el podcasttipo de nodo. Hacemos esto 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
fuente
Si termina necesitando otros valores de campo para validar su campo, puede agregar una restricción al tipo de contenido. Vea esta publicación de blog: lakshminp.com/entity-validation-drupal-8-part-2
ummdorian
1
La API de validación de formularios D8 se ha explicado en detalle en Drupal.org aquí. Proporcionar una restricción de validación personalizada
Sukhjinder Singh
1
Dado que esta pregunta se refiere específicamente a la API de formulario, no a la API de campo, ¿cómo se adjunta esta restricción a un elemento de formulario (no a un campo de entidad)?
aaronbauman
Los elementos de formulario no pueden tener restricciones. Puede agregar validación a un elemento de formulario específico usando #element_validate. Vea la respuesta principal en este hilo: funciona igual en D8 que D7 drupal.stackexchange.com/questions/86990/…
Jaypan
Asegúrese de verificar $item = $items->first();y devolver NULL si no hay nada o de lo contrario obtendrá un error fatal al editar el campo: TypeError: Argument 2 pasado a Drupal \ Component \ Utility \ NestedArray :: getValue () debe ser del tipo array, nulo dado, llamado en /data/app/core/lib/Drupal/Core/Field/WidgetBase.php en la línea 407 en Drupal \ Component \ Utility \ NestedArray :: getValue () (línea 69 de core / lib / Drupal / Component /Utility/NestedArray.php).
Ivan Zugec
16

La forma correcta de hacer esto para una entidad de contenido como nodo es registrarlo como una restricción.

Ver forum_entity_bundle_field_info_alter()y el correspondiente? ForumLeafrestricción de validación (tenga en cuenta que se necesitan dos clases).

Eso es un poco más complicado al principio, pero la ventaja es que está integrado en la API de validación, por lo que su validación no se limita al sistema de formularios sino que, por ejemplo, también puede funcionar con nodos enviados a través de la API REST.

Berdir
fuente
Buen punto: Esto es algo nuevo para quienes solían escribir código para Drupal 7. Estoy seguro de que hay muchos usuarios que intentarán agregar controladores de validación cuando una restricción sea más apropiada.
kiamlaluno
Berdir: exploré esta opción intentando implementar hook_entity_bundle_field_info_alter()(como se describe aquí ) pero nunca funcionó ... Parece que hay un problema documentado con este enlace : drupal.org/node/2346347 .
AngularChef
Hay algunos problemas, pero no creo que estén relacionados con su problema. forum.module muestra que funciona. Comparta su código, de lo contrario no es posible señalar posibles problemas en su implementación.
Berdir
1
Me gustaría seguir con este método, pero hasta que haya un buen ejemplo de cómo usarlo con tipos de datos (es decir, no específicos del campo) para verificar si se cumple alguna condición externa, estoy atascado con los cambios de formulario. El artículo no profundizó en esto. ¿Alguien podría señalarme algún lugar útil o publicarlo aquí? Gracias.
colan
¿Qué sucede si necesitamos agregar validación existente como \ Drupal \ user \ Form \ UserLoginForm :: validateName (). En d7 era simple como $ form ['# validate'] = array ('user_login_name_validate', 'myother_validaion',); Pero parece que el módulo sin contribución implementó estos cambios drupal.org/node/2185941
kiranking el
8

Quiero agregar algo más de luz sobre este asunto. La adición de la validación es exactamente la misma que antes: en hook_form_alter:

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

Sin embargo, el uso del objeto de valores dentro de $ form_state en la función de validación es un poco diferente. p.ej:

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'));
     }

  }
}

Entonces, no con un acceso directo al objeto de variables privadas, sino con una función getter.

Para obtener más información, puede ver un ejemplo completo en mi github: https://github.com/flesheater/drupal8_modules_experiments/blob/master/webham_formvalidation/webham_formvalidation.module

¡aclamaciones!

Nikolay Borisov
fuente
De hecho, es. Justo como escribí en mi comentario anterior. ;)
AngularChef
7

Es muy parecido a D7. Un ejemplo completo:

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
fuente
Esto está bastante cerca. Solo el hook_form_FORM_ID_alternecesita la identificación del formulario. Puede hacer que la función de validación personalizada sea la que desee. Además, siga la guía API aquí para conocer los parámetros adecuados.
mikeDOTexe
Antes de eso, cómo obtener la identificación del formulario también, dónde verificar este código.
logeshvaran
3

En complemento de estas buenas respuestas, agregaría:

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

Es cómo llamar a un método de clase distante para una validación de formulario. Creo que es mejor llamar a una función anterior en el archivo del módulo como en el ejemplo dado.

Pauleau
fuente
Ya no es necesario saltar al código de procedimiento de OO.
Colan 01 de
1

puede usar el módulo de Validación del lado del cliente . Algunos detalles más al respecto (de su página de proyecto):

... agrega validación del lado del cliente (también conocido como "validación de formulario Ajax") para todos los formularios y formularios web utilizando jquery.validate . El archivo jquery.validate.js incluido está parcheado porque necesitábamos poder ocultar mensajes vacíos.

Mohammed ATIFI
fuente