0

I'm trying to add a button to all node forms via callback in hook_forms. I get a similar error. Can someone help.

Warning: call_user_func_array() expects parameter 1 to be a valid callback, class 'my_dossier_form' not found in drupal_retrieve_form() (line 844 of /var/www/includes/form.inc).
Notice: Array to string conversion in drupal_prepare_form() (line 1094 of /var/www/includes/form.inc).
Notice: Array to string conversion in drupal_prepare_form() (line 1108 of /var/www/includes/form.inc).
Notice: Array to string conversion in drupal_prepare_form() (line 1128 of /var/www/includes/form.inc).

Code

function my_dossier_forms($form_id, $args) {
  $forms = array();
  if ($types = node_type_get_types()) {
    foreach (array_keys($types) as $type) {
      $forms[$type . '_node_form']['callback'] = 'my_dossier_form';
    }
  }

  return $forms;
}

function my_dossier_form($form, &$form_state) {
  $form['delete'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
    '#weight' => 10,
  );
  return $form;
}
6
  • Are sure you don't want hook_form_alter?
    – beltouche
    Commented Aug 12, 2021 at 16:04
  • According to the task, I need to use hook_forms specifically. Commented Aug 12, 2021 at 16:11
  • Your code looks ok, but it's not finding my_dossier_form(). What file is this function in? You may need to explicitly include it.
    – Jaypan
    Commented Aug 12, 2021 at 16:16
  • In the my_dossier.module file. Commented Aug 12, 2021 at 16:25
  • 1
    @beltouche I agree. The form builder set with hook_forms() (a.k.a. the callback value) should build the full form, not just a submission button. It seems the OP's task is altering the node edit forms, for which hook_form_BASE_FORM_ID() is the hook to implement.
    – avpaderno
    Commented Aug 13, 2021 at 8:17

1 Answer 1

1

What node_type_get_types() returns is an associative array of node type objects, keyed by the type. The description of the value returned is more understandable in the _node_types_build() documentation.

The hook implementation done from a Drupal core module, for example comment_forms(), helps to understand the correct code.

function comment_forms() {
  $forms = array();
  foreach (node_type_get_types() as $type) {
    $forms["comment_node_{$type->type}_form"]['callback'] = 'comment_form';
  }
  return $forms;
}

In your case, the code should be similar to the following one.

function my_dossier_forms($form_id, $args) {
  $forms = array();
  foreach (node_type_get_types() as $type) {
    $forms["my_dossier_node_{$type->type}_form"]['callback'] = 'my_dossier_form';
  }

  return $forms;
}

This code isn't much different from the code shown in the question, and it would not cause, alone, the class 'my_dossier_form' not found error.

The line 844 is the following one.

$form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);

$callback is initialized from the following code.

if (!isset($forms) || !isset($forms[$form_id])) {
  $forms = module_invoke_all('forms', $form_id, $args);
}
$form_definition = $forms[$form_id];
if (isset($form_definition['callback arguments'])) {
  $args = array_merge($form_definition['callback arguments'], $args);
}
if (isset($form_definition['callback'])) {
  $callback = $form_definition['callback'];
  $form_state['build_info']['base_form_id'] = isset($form_definition['base_form_id']) ? $form_definition['base_form_id'] : $callback;
}

Line 844 could not think my_dossier_form is a class, except in the case $callback contains an array like array('my_dossier_form', 'mymodule_form');, or it contains a string like 'my_dossier_form::methodName'.

The warnings about an array to string conversion means Drupal is getting an array when it expects a string, for example from the following line (line 1094).

    elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) {

I would just avoid returning a form ID that starts with $type as that is probably creating conflicts with other modules implementing hook_forms(), which are expected to return a unique form ID each. See module_invoke_all() to understand what happens when two hook_forms() implementations return information for the same form ID.

  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    if (function_exists($function)) {
      $result = call_user_func_array($function, $args);

      // In the case of hook_forms(), $result contains an array.
      if (isset($result) && is_array($result)) {
        $return = array_merge_recursive($return, $result);
      }

      elseif (isset($result)) {
        $return[] = $result;
      }
    }
  }

To be more exact, two modules returning values for the same form ID could at least cause the call_user_func_array() expects parameter 1 to be a valid callback, class '[callback]' not found when they both set the value for the same callback. See the output of the following code, to understand what exactly happens.

$return["type_node_form"]["callback"] = "my_dossier_form";
$result["type_node_form"]["callback"] = "mymodule_form";
print_r(array_merge_recursive($return, $result));

The callback value becomes an array of two strings, which call_user_func_array() interprets as an array containing a class name and a method name.

Array
(
    [type_node_form] => Array
        (
            [callback] => Array
                (
                    [0] => my_dossier_form
                    [1] => mymodule_form
                )

        )

)

In this case, the conflict is with the Node module, which uses the following code for its hook_forms() implementation.

function node_forms() {
  $forms = array();
  if ($types = node_type_get_types()) {
    foreach (array_keys($types) as $type) {
      $forms[$type . '_node_form']['callback'] = 'node_form';
    }
  }
  return $forms;

As side note, the hook_forms() purpose is providing the same form builder for a group of forms whose ID follow a schema, such as in the case of comment edit forms, whose IDs are comment_node_[node type]_form. When provided, the form builder callback (passed as callback value) should build the full form, not part of it.
The fact my_dossier_form() builds just a submission button, and my_dossier_forms() is using the same code used by node_forms() makes me think the code purpose is altering the node edit forms, which should be accomplished by implementing hook_form_BASE_FORM_ID_alter(). That's what the Book module does with book_form_node_form_alter(), which is invoked for the node edit form of every content type.

function book_form_node_form_alter(&$form, &$form_state, $form_id) {
  $node = $form['#node'];
  $access = user_access('administer book outlines');
  if (!$access) {
    if (user_access('add content to books') && (!empty($node->book['mlid']) && !empty($node->nid) || book_type_is_allowed($node->type))) {

      // Already in the book hierarchy, or this node type is allowed.
      $access = TRUE;
    }
  }
  if ($access) {
    _book_add_form_elements($form, $form_state, $node);

    // Since the "Book" dropdown can't trigger a form submission when
    // JavaScript is disabled, add a submit button to do that. book.css hides
    // this button when JavaScript is enabled.
    $form['book']['pick-book'] = array(
      '#type' => 'submit',
      '#value' => t('Change book (update list of parents)'),
      '#submit' => array(
        'book_pick_book_nojs_submit',
      ),
      '#weight' => 20,
    );
  }
}

If then the hook needs to use the content type name, that is available in $form['#node']->type.

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.