Marking critical because it's missing hook documentation.

If you don't read form.inc on a regular basis, you might be unaware that in addition to hook_form_alter(), which fires for any form in the system, there is also a more granular hook_form_FORM_ID_alter() (for example, custom_form_user_register_alter() for the registration form.

This is a nice little feature and we need docs for it in contributions/docs/developers/hooks/core.php. Anyone with contrib CVS access (i.e. anyone who can maintain a module, theme, translation, etc.) can update these docs. The changes need to be made to HEAD and DRUPAL-6.

Comments

amitaibu’s picture

Assigned: Unassigned » amitaibu
Status: Active » Needs review

What do you think of this (with the use of http://drupal.org/node/144132#form-id-alter):


/**
 * Provide a form-specific alteration rather than a single hook_form_alter().
 *
 * Modules can implement hook_form_$form-id_alter() rather than a single
 * hook_form_alter() with many conditional switch statements.
 * This is optional, and is most useful for tidying the code of modules that
 * alter many forms to customize a site's operations.
 *
 * Note that this hook is executed before hook_form_alter(), in this case if
 * a) Module alpha has weight 30 and implements hook_form_$form-id_alter().
 * b) Module zeta has weight 0 and implements hook_form_alter().
 * Then zeta's form alteration will be executed first.
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 * @return
 *   None.
 *
 * @see drupal_prepare_form().
 */

function hook_form_my_first_form_alter(&$form, &$form_state) {
  // Form modification for 'my first form' code goes here.
}

function hook_form_my_second_form_alter(&$form, &$form_state) {
  // Form modification for 'my second form' code goes here.
}

function hook_form_my_third_form_alter(&$form, &$form_state) {
  // Form modification for 'my third form' code goes here.
}

cburschka’s picture

Wow, I didn't know this. Well, I had heard of it at some point, but I've never made use of it even though it would have been very useful.

Is there a specific reason that the forms in your example are named with Greek letters (as opposed to numbers or Latin letters), or that the second form is named "zeta" instead of, say, "beta"?

Also, I'm not sure the hook needs to be implemented three times - after the first "hook_form_my_form_alter" developers might get the point...

Edit: I see that the three implementations are a legacy of the 5.x->6.x upgrading page, but I really think they're only necessary to explain that change, not to document the function itself.

amitaibu’s picture


/**
 * Provide a form-specific alteration rather than a single hook_form_alter().
 *
 * Modules can implement hook_form_$form-id_alter() rather than a single
 * hook_form_alter() with many conditional switch statements.
 * This is optional, and is most useful for tidying the code of modules that
 * alter many forms to customize a site's operations.
 *
 * Note that this hook is executed before hook_form_alter(), in this case if
 * a) Module alpha has weight 30 and implements hook_form_$form-id_alter().
 * b) Module beta has weight 0 and implements hook_form_alter().
 * Then beta's form alteration will be executed first.
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 * @return
 *   None.
 *
 * @see drupal_prepare_form().
 */

function hook_form_my_first_form_alter(&$form, &$form_state) {
  // Form modification for 'my first form' code goes here.
}

dmitrig01’s picture

Looks good

webchick’s picture

Status: Needs review » Needs work

Great job! Some comments:

a) Let's make just one function and call it literally "hook_form_FORM_ID_alter". This is so that when people make implementations of this hook, and go "Implementation of hook_form_FORM_ID_alter()." it will properly map over to the API documentation. The FORM_ID matches with our template preprocess hooks (see http://api.drupal.org/api/function/theme).

b) That means the reference in the comment needs to be fixed too.

c) Please remove the newline between the end of the PHPDoc and the function definition; this will case api.drupal.org to not parse the docs correctly (annoying, but true! :)).

d) I'd like to suggest something a bit more "real" as the example code, to give people a better idea of why they might use this. I know terms_of_use.module uses one of these to add terms to the registration form, so a simpler example of that might be good.

How about something like:

  // Modification for the form with the given form ID goes here. For example, if
  // FORM_ID is "user_register" this code would fire only on the user
  // registration form.

  // Add a checkbox to registration form about agreeing to terms of use.
  $form['terms_of_use'] = array(
    '#type' => 'checkbox',
    '#title' => t("I agree with the website's terms and conditions."),
    '#required' => TRUE,
  );
cburschka’s picture

Status: Needs work » Needs review

Changes are below.

Further comments:

- The stuff about call order is contradictory. First we state that this is called /before/ the general hook_form_alter, then we state that the hook_form_alter of module beta will be triggered before the hook_form_FORM_ID_alter of alpha. I've tried to resolve that below, please check if it is still accurate.

- Consider consistently calling it hook_form_FORM_ID_alter instead of hook_form_$form-id_alter...

/**
 * Provide a form-specific alteration rather than a single hook_form_alter().
 *
 * Modules can implement hook_form_$form-id_alter() rather than a single
 * hook_form_alter() with many conditional switch statements.
 * This is optional, and is most useful for tidying the code of modules that
 * alter many forms to customize a site's operations.
 *
 * Note that this hook is executed before hook_form_alter(), therefore if
 * a) Module alpha has weight 30 and implements hook_form_$form-id_alter().
 * b) Module beta has weight 0 and implements hook_form_alter().
 * Then alpha's form alteration will be executed first.
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 * @return
 *   None.
 *
 * @see drupal_prepare_form().
 */
function hook_form_FORM_ID_alter(&$form, &$form_state) {
  // Modification for the form with the given form ID goes here. For example, if
  // FORM_ID is "user_register" this code would run only on the user
  // registration form.

  // Add a checkbox to registration form about agreeing to terms of use.
  $form['terms_of_use'] = array(
    '#type' => 'checkbox',
    '#title' => t("I agree with the website's terms and conditions."),
    '#required' => TRUE,
  );
}
webchick’s picture

Status: Needs review » Needs work
- Consider consistently calling it hook_form_FORM_ID_alter instead of hook_form_$form-id_alter...

Right, that's what I meant by b) :) So anywhere it says hook_form_$form-id_alter it should be hook_form_FORM_ID_alter.

 * Provide a form-specific alteration rather than a single hook_form_alter().

How about "rather than a global hook_form_alter()" or something that implies that hook_form_alter() applies to *all* forms, and this one to only a specific one.

Other than that looks great! Feel free to commit this once these small changes are in! :)

cburschka’s picture

Status: Needs work » Needs review

Feel free to commit this once these small changes are in! :)

Lol, I thought that was your job. I keep forgetting that the docs repository is open for everybody. ;)

Well, here's my final version. I also took out some of the text that seemed specific to updating instructions - nobody needs to know that a hook is "optional".

/**
 * Provide a form-specific alteration instead of the global hook_form_alter().
 *
 * Modules can implement hook_form_FORM_ID_alter() to modify a specific form,
 * rather than implementing hook_form_alter() and checking the form ID, or
 * using long switch statements to alter multiple forms.
 *
 * Note that this hook fires before hook_form_alter(). Therefore all 
 * implementations of hook_form_FORM_ID_alter() will run before all implementations
 * of hook_form_alter(), regardless of the module order.
 *
 * @param $form
 *   Nested array of form elements that comprise the form.
 * @param $form_state
 *   A keyed array containing the current state of the form.
 * @return
 *   None.
 *
 * @see drupal_prepare_form().
 */
function hook_form_FORM_ID_alter(&$form, &$form_state) {
  // Modification for the form with the given form ID goes here. For example, if
  // FORM_ID is "user_register" this code would run only on the user
  // registration form.

  // Add a checkbox to registration form about agreeing to terms of use.
  $form['terms_of_use'] = array(
    '#type' => 'checkbox',
    '#title' => t("I agree with the website's terms and conditions."),
    '#required' => TRUE,
  );
}

Looks as if this can go in!

cburschka’s picture

Here's a patch for core.php. I put the function right after hook_form_alter.

webchick’s picture

Status: Needs review » Reviewed & tested by the community

Looks great!

Please commit to both HEAD and DRUPAL-6--1. :)

cburschka’s picture

Status: Reviewed & tested by the community » Fixed

This I have done. :)

webchick’s picture

Yay!! You guys rock!! Thanks, Arancaytar and Amitaibu! :)

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.

miro_dietiker’s picture

Assigned: amitaibu » Unassigned
Category: bug » task
Priority: Critical » Normal
Status: Closed (fixed) » Active

Hey that's pretty cool! But...
I'd suggest adding this to the hook_form_alter page too:
http://api.drupal.org/api/function/hook_form_alter
For people to understand there's an alternative (better) way.

joachim’s picture

+1 to #14 -- add a mention and a link.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon
jhodgdon’s picture

StatusFileSize
new768 bytes
jhodgdon’s picture

StatusFileSize
new757 bytes
jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Status: Active » Needs review

Above are patches for 7.x and 6.x for #14

webchick’s picture

Version: 7.x-dev » 6.x-dev
Status: Needs review » Reviewed & tested by the community

Looks great! Committed to HEAD. Feel free to commit to the 6.x docs, anyone with contributions access. :)

jhodgdon’s picture

I don't have contrib access (yet, and/or haven't applied yet). Someone else?

gábor hojtsy’s picture

Status: Reviewed & tested by the community » Fixed

Committed to the contrib hook docs in 6.x: http://drupal.org/cvs?commit=190462

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

joachim’s picture

Status: Closed (fixed) » Active

Reopening:

+ * Note that you can also use hook_FORM_ID_alter() to alter a specific form,

It's hook_form_FORM_ID_alter()

webchick’s picture

Version: 6.x-dev » 7.x-dev
Issue tags: +Novice

D'oh. :P

jhodgdon’s picture

Whoops. I put hook_FORM_ID_alter() rather than hook_form_FORM_ID_alter() in my earlier patches, and no one else noticed the error either, so those lines got committed with the wrong hook name in them. So someone needs to make a new patch (against the current D6 and D7 documentation) to fix this error.

I don't have time to do it today, so if someone else wants to take it on to get their feet wet with supplying patches (webchick marked it as "Novice" for that reason), that is fine. Otherwise, I'll try and get to it sometime soon.

dave reid’s picture

I fixed the 6.x documentation, now we just need to patch system.api.php in D7.

jhodgdon’s picture

StatusFileSize
new769 bytes

6.x looks good to me. Here's a patch for 7.x

dave reid’s picture

Status: Active » Needs work

Let's make sure that line gets wrapped at 80 characters (move the last word to the next line).

jhodgdon’s picture

StatusFileSize
new888 bytes

Good idea. How about this patch?

dave reid’s picture

Another minor nitpick (sorry!), can we remove this extra comma?
"Note that you can also use hook_form_FORM_ID_alter() to alter a specific form, instead of this hook,"

So that it is :
"Note that you can also use hook_form_FORM_ID_alter() to alter a specific form instead of this hook,"

jhodgdon’s picture

Status: Needs work » Needs review

Can we get a second opinion on that comma? I think the comma is necessary, because the "instead of this hook" clause refers to the beginning of the sentence (using a different hook) rather than what is just before it (alter a form).

keith.smith’s picture

+ * Note that you can also use hook_form_FORM_ID_alter() to alter a specific
+ * form, instead of this hook, which gets called for all forms.

Ooh! Commas.

If a sentence can be made more clear with commas, then by all means use them. However, in this case, why don't we just rearrange this sentence so that the entire thing is more clear.

This is what I understand it to be saying:

+ * Instead of hook_form_alter(), which is called for all forms, you can also use
+ * hook_form_FORM_ID_alter() to alter a specific form.
joachim’s picture

+1 to #33.

webchick’s picture

Status: Needs review » Needs work

Sounds good to me, too. :)

Thanks a lot for your attention to detail, folks!

jhodgdon’s picture

Assigned: Unassigned » jhodgdon
jhodgdon’s picture

StatusFileSize
new1.21 KB

OK, here's one more go at it. :)

I also removed the @return: None. section.

If this is OK, I can also patch D6 (I have contrib CVS access now).

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Status: Needs work » Needs review
webchick’s picture

Version: 7.x-dev » 6.x-dev
Status: Needs review » Patch (to be ported)

Awesome. Committed to HEAD! Thanks a lot, jhodgdon!

The one tiny thing I did before I committed was remove the single trailing space at the end of the "Note that instead of hook_form_alter(), which is called for all forms, you" line.

Marking down to 6 so that the same fix is committed there. Thanks!

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

Gracious. Forgot one. :)

I'll assign this back to myself and patch D6 in the next day or two. I do have contrib repos access now.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Status: Patch (to be ported) » Fixed

I went ahead and committed the same wording change as the patch in #37 (with the end of line fix) to D6 docs: http://drupal.org/cvs?commit=204464

Hope that's OK. I figured it has now probably been reviewed to death.

webchick’s picture

yep, that's totally cool. :) Thanks so much, jhodgdon!!

Status: Fixed » Closed (fixed)
Issue tags: -Novice

Automatically closed -- issue fixed for 2 weeks with no activity.