HOOK_form_profile2_form_alter and HOOK_form_profile2_TYPE_form_alter functions are called repeatedly, whereas they should be called exactly once. In my situation, the alter function was adding a validation handler and the validation handler not working properly because it was being installed (and then called) many times.

In profile2.module:

function profile2_form_user_register_form_alter(&$form, &$form_state) {
  foreach (profile2_get_types() as $type_name => $profile_type) {
    if (!empty($profile_type->data['registration'])) {
      if (empty($form_state['profiles'][$type_name])) {
        $form_state['profiles'][$type_name] = profile2_create(array('type' => $type_name));
      }
      profile2_attach_form($form, $form_state);
      ...
    }
  }
}

function profile2_attach_form(&$form, &$form_state) {
  foreach ($form_state['profiles'] as $type => $profile) {
    $form['profile_' . $profile->type]['#tree'] = TRUE;
    $form['profile_' . $profile->type]['#parents'] = array('profile_' . $profile->type);
    field_attach_form('profile2', $profile, $form['profile_' . $profile->type], $form_state);
    ...
    // Provide a central place for modules to alter the profile forms, but
    // skip that in case the caller cares about invoking the hooks.
    // @see profile2_form().
    if (!isset($form_state['profile2_skip_hook'])) {
      $hooks[] = 'form_profile2_edit_' . $type . '_form';
      $hooks[] = 'form_profile2_form';
      drupal_alter($hooks, $form, $form_state);
    }
  }
  ...
}

So if there are 3 profiles, HOOK_form_profile_form_alter will be called:
1 times for the first call to profile_form_attach
1+2 times for the second call to profile_form_attach
1+2+3 times for the third call to profile_form_attach
----
10 times.

There are two causes of this bug. The first is simply that the $hooks array is appended to inside the loop. The second is that profile_form_attach calls the alter functions for all the profiles, not just the one added. Also note that the form is built repeatedly too.

The first bug could easily be fixed by accumulating an array of $hooks inside the loop, and then, if needed, adding the overall alter function and calling drupal_alter. In addition, inside the loop, the form could be built if it doesn't already exist. This could call the profile-specific alter function exactly once. However, it would still call the overall alter function once for every profile type -- a drastic improvement (3 versus 10 in the above example), but still not ideal.

To solve that, move the call the profile2_attach_form outside of the loop, attaching all the form elements at once. This would create all the profiles in $form_state, then attach them all at once, calling all the alters. Then (awkwardly) go back and make each profile form a field_set in a separate loop. This would also call field_attach_form only once for each profile, eliminating the redundant form building.

If these seems right, I will make a patch.

CommentFileSizeAuthor
#3 profile2-1865996-2.patch1.22 KBkristiaanvandeneynde

Comments

kristiaanvandeneynde’s picture

I just stumbled upon this as well: It just seems as if the profile2_attach_form() call should be called after the loop.

Right now, it causes hooks, validation callbacks and submit handlers to be invoked multiple times.

joachim’s picture

Issue tags: +Needs tests

> HOOK_form_profile2_TYPE_form_alter

Is this the hook_form_FORM_ID_alter() for the profile type? Seems to me that should be called for each profile type.

This needs tests, so we can specify exactly what the behaviour should be.

kristiaanvandeneynde’s picture

Status: Active » Needs review
StatusFileSize
new1.22 KB

Git-aware patch attached (for 'git am').

kristiaanvandeneynde’s picture

No it's in hook_form_FORM_ID_alter() for 'user_register_form' that profile2_attach_form() is being called WAY too often :)

donquixote’s picture

The issue summary correctly explains that there are two separate problems.
The one part that is dealt with in the patch #3 seems to be already fixed in 7.x-1.3+20-dev.
The other part, the $hooks[] not being reset between loop iterations, is *not* fixed yet. And neither does the patch touch it.
Or actually, the "other part" could be seen as two separate parts as well..

A first fix attempt would be to set $hooks = array() before any $hooks[] = ...
I think this should be done and committed.
This still leaves us with hook_form_profile2_form_alter() being called separately for each profile. But at least it is only once, and not multiple times.

As a next step, we could consider to call hook_form_profile2_form_alter() only once *after* the loop, but to call hook_form_profile2_edit_TYPE_form_alter *during* the loop.

donquixote’s picture

What about sending the profile type name as an additional parameter to the hook_form_profile2_form_alter() ?