0

I have a custom autocomplete field widget, defined like below:

/**
 * Implements hook_field_widget_form
 */
function mymodule_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $element += array(
    '#id' => 'mymodule-field',
    '#type' => 'textfield',
    '#default_value' => '',
    '#autocomplete_path' => 'some/path',
    '#element_validate' => array('mymodule_autocomplete_validate'),
  );
  return $element;
}

I have a content type with 2 Term reference autocomplete fields: field foo with standard Autocomplete term widget (tagging) widget, and field bar with my custom autocomplete widget.

Field bar has a higher weight than field foo on a node form.

mymodule has weight 20 in system table. taxonomy module has weight 0.

However, when I submit a node form which includes both fields, mymodule_autocomplete_validate() is invoked earlier than taxonomy_autocomplete_validate(), so $form_state['values']['field-myfield-a'] is not processed yet.

The problem is I need to use the processed value of foo in mymodule_autocomplete_validate().

I have another 2 fields with same widgets but another names, and they are processed in the right order.

So the question is: how the order of execution of these functions is defined? Is there a way to change it?

4 Answers 4

1
+50

Order of execution of widget validation function is handled by the first foreach loop of _form_validate() function.

function _form_validate(&$elements, &$form_state, $form_id = NULL) {
  // Also used in the installer, pre-database setup.
  $t = get_t();

  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key], $form_state);
    }
  }

It looks like field weight could be used if element_children($elements, TRUE) was used to retrieve the fields for its recursive _form_validate() call (e.g. via a core patch).

The only other alternative would be to used hook_form_alter() to alter the $form element key order (different from field weight) so that element_children($elements) would produce the order that you want.

1
1

hook_module_implements_alter() would seem to be the one you are looking for to change execution order.

However, I'm not sure It is exactly what you are looking for because it could have to do with the node edit form. One field could be higher on that form, and that could be causing the validation order issue. The weight of the fields on the form itself could be dictating the validation order. If that is the case, you can hook into the node form and run the validation in the order needed.

2
  • I'm aware about fields weight, and as I mentioned, field bar has a higher weight than field foo.
    – Misha
    Commented Sep 29, 2016 at 19:22
  • Description of hook_module_implements_alter() says: A module may implement this hook in order to reorder the implementing modules, which are otherwise ordered by the module's system weight But I don't implement this hook, so hooks should be ordered by module's system weight, and my module's weight is higher than taxonomy module's weight.
    – Misha
    Commented Sep 29, 2016 at 19:24
1

You're not making an apples-to-apples comparison here. mymodule_autocomplete_validate() is widget validation function, while taxonomy_field_validate() is a field validation function.

There is a good description of the difference on the docs for field_attach_form_validate():

There are two levels of validation for fields in forms: widget validation, and field validation.

  • Widget validation steps are specific to a given widget's own form structure and UI metaphors. They are executed through FAPI's #element_validate property during normal form validation.
  • Field validation steps are common to a given field type, independently of the specific widget being used in a given form. They are defined in the field type's implementation of hook_field_validate().

As such, your module & field weights are irrelevant here; _form_validate() processes widget validation functions before any field validation functions.

If you want a guaranteed way to validate after any field validation, just append a form validation handler in your node form:

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&$form, &$form_state, $form_id) {
  // Append a custom handler to mynodetype forms
  if ($form_id == 'mynodetype_node_form') {
    $form['#validate'][] = 'mymodule_validation_handler';
  }
}
function mymodule_validation_handler($form, &$form_state) {
  // @TODO: Handle node form validation.
}

This will ensure that mymodule_validation_handler() is executed after all widget & field validation functions.

2
  • oops...My bad, I have typed taxonomy_field_validate instead of taxonomy_autocomplete_validate in the original question. But anyway thanks for response, I will have to use form validation handler unless I find a better solution.
    – Misha
    Commented Sep 30, 2016 at 18:49
  • @Mikhail moved to an answer.
    – Shawn Conn
    Commented Sep 30, 2016 at 19:36
0

For those interested in Drupal 9's order of form validation and want a validation function to first before all the others - to for example, change a value but still want that value validated. (and as @Shawn Conn mentioned, the #element_validate are run first, then the top level validations are run last).

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Append a custom handler to media_image_add_form form.
  if ($form_id == 'media_image_add_form') {
    // Get the lowest #weight -1.
    $lowest_weight = call_user_func(function($form) {
      $lowest_weight = 0;
      foreach (Element::children($form) as $key) {
        if (isset($form[$key]['#weight']) && is_numeric($form[$key]['#weight'])) {
          if ($form[$key]['#weight'] < $lowest_weight) {
            $lowest_weight = $form[$key]['#weight'];
          }
        }
      }
      return $lowest_weight - 1;
    }, $form);
    $form['custom_first_validation_element'] = [
      '#type' => 'hidden',
      '#element_validate' => ['__custom_first_validation_element__validate'],
      '#weight' => $lowest_weight,
    ];
  }
}

/**
 * Validation handler for custom_first_validation_element.
 *
 * @param array $element
 *   The element the #element_validate is on.
 * @param Drupal\Core\Form\FormStateInterface $form_state
 *   The current form state.
 * @param array $complete_form
 *   The complete form - all the elements in the form.
 */
function __custom_first_validation_element__validate(array &$element, FormStateInterface $form_state, array &$complete_form) {
  // Force a image file to be used on the media form.
  $file_id = 6;
  // These values are used if form submission is successful.
  $media_image_values = $form_state->getValue('field_media_image');
  $media_image_values[0] = [
    'fids' => [
      $file_id,
    ],
    'focal_point' => '50,50',
  ] + $media_image_values[0];
  $form_state->setValue('field_media_image', $media_image_values);
  // These are used in valdation (and shown on the form if an error).
  $complete_form['field_media_image']['widget'][0]['fids']['#value'][0] = $file_id;
  $complete_form['field_media_image']['widget'][0]['focal_point']['#value'] = '50,50';
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.