array_insert() to help inject items into a particular position in form array

jjeff - May 30, 2006 - 01:22
Project:Drupal
Version:7.x-dev
Component:forms system
Category:feature request
Priority:normal
Assigned:Unassigned
Status:needs work
Description

One of the most difficult things about manipulating Drupal forms is the process of manipulating the arrays themselves. And one of the things that I commonly want to do is "stick this stuff in after that". But I have yet to find a PHP function to manipulate arrays in this way.

So I wrote this function called array_insert(). Perhaps there's a cleaner way to handle this, but it seems like a function that should be available in Drupal core. It would certainly help with a lot of common hook_form_alter() tasks.

For instance, this function (finally) offers a way to stick something into the node-type settings page (admin/settings/content-types/[node-type]) immediately before the "submit" buttons, you'd just create an array with your form items and then call

$form = array_insert($form, 'buttons', $my_stuff, TRUE);

<?php
/**
* Inserts values from $arr2 after (or before) $key in $arr1
* if $key is not found, $arr2 is appended to $arr1 using array_merge()
*
* @param $arr1
*   array to insert into
* @param $key
*   key of $arr1 to insert after
* @param $arr2
*   array whose values should be inserted
* @param $before
*   insert before the given key. defaults to inserting after
* @return
*   merged array
*/
function array_insert($arr1, $key, $arr2, $before = FALSE){
 
$done = FALSE;
  foreach(
$arr1 as $arr1_key => $arr1_val){
    if(!
$before){
     
$new_array[$arr1_key] = $arr1_val;
    }
    if(
$arr1_key == $key && !$done){
      foreach(
$arr2 as $arr2_key => $arr2_val){
       
$new_array[$arr2_key] = $arr2_val;
      }
     
$done = TRUE;
    }
    if(
$before){
     
$new_array[$arr1_key] = $arr1_val;
    }
  }
  if(!
$done){
   
$new_array = array_merge($arr1, $arr2);
  }
  return
$new_array;
}
?>

#1

m3avrck - May 30, 2006 - 01:24

Seems like this would be useful for core. I'm no array expert but it looks good.

#2

chx - May 30, 2006 - 04:34

The functin may be useful.

But, I think you are better off with a combination of array_keys, array_search and array_splice. timer it out.

#3

jjeff - May 31, 2006 - 14:33

Per CHX's advice, here's a new version. It turns out that array_splice() actually destroys the keys of the inserted array, so I've had to go another route.

<?php
/**
* inserts values from $arr2 after (or before) $key in $arr1
* if $key is not found, values from $arr2 are appended to the end of $arr1
*
* This function uses array_merge() so be aware that values from conflicting keys
* will overwrite each other
*
* @param $arr1
*   array to insert into
* @param $key
*   key of $arr1 to insert after (or before)
* @param $arr2
*   array whose values should be inserted
* @param $before
*   boolean. insert before the given key. defaults to inserting after
* @return
*   merged array
*/
function drupal_array_insert($arr1, $key, $arr2, $before = FALSE) {
 
$index = array_search($key, array_keys($arr1));
  if(
$index === FALSE){
   
$index = count($arr1); // insert @ end of array if $key not found
 
}
  else {
    if(!
$before){
     
$index++;
    }
  }
 
$end = array_splice($arr1, $index);
  return
array_merge($arr1, $arr2, $end);
}

// EXAMPLE //////////////////

$arr1 = array('one' => 1, 'two' => 2, 'three' => 3, 'four' => 4);
$arr2 = array('first' => 1, 'second' => 2, 'third' => 3, 'fourth' => 4);

$new_array = drupal_array_insert($arr1, 'three', $arr2);

print_r($new_array);

/**
Array
(
    [one] => 1
    [two] => 2
    [three] => 3
    [first] => 1
    [second] => 2
    [third] => 3
    [fourth] => 4
    [four] => 4
)
**/
?>

#4

chx - May 31, 2006 - 18:30

array_splice() removes the elements designated by offset and length from the input array, and replaces them with the elements of the replacement array, if supplied. It returns an array containing the extracted elements. Note that numeric keys in input are not preserved.

sorry for not reading the manual page before :( but losing numberic keys is not good :( array_slice also resets numeric keys :(

#5

moshe weitzman - June 21, 2006 - 16:03
Status:active» needs review

i wrote a different version for my big nodeapi submit handler patch. since that patch is dormant for now, i think this function should be considered for core under this issue. my version is very small.

AttachmentSizeStatusTest resultOperations
array_insert.patch939 bytesIgnoredNoneNone

#6

jjeff - June 22, 2006 - 00:16

Moshe's version is shorter because it doesn't do the same thing as mine.

- Mine allows you to stick array key/value pairs into an array either before or after a given key.
- Moshe's just sticks stuff in at a given numeric position.

Both have their merits, but I think mine is a bit more useful for manipulating Drupal forms.

e.g.: Stick this stuff before the 'buttons' in the form.

#7

drumm - June 23, 2006 - 09:04

Aren't weights supposed to let us put things where we want?

#8

moshe weitzman - June 26, 2006 - 02:03

there are no weights in submit/validate handlers, for example.

#9

webchick - June 27, 2006 - 04:11

Nor in form_alter for most forms. For example, I was trying tonight to alter taxonomy_image so that the form would appear in the actual taxomomy edit page rather than on a separate tab. At the time the form is built, none of the fields are assigned weights, therefore the additions always appear /below/ the Submit/Delete buttons which is less than ideal.

However, this function didn't allow me to do that either. :P

<?php
function taxonomy_image_form_alter($form_id, &$original_form) {

  if (
$form_id == 'taxonomy_form_term') {

   
// $form definition...

   
$original_form = drupal_array_insert($original_form, 'submit', $form, TRUE);
    return
$original_form;

  }
}

// still appears under the Submit/Delete buttons. ;(
?>

...either that or I'm just tired and being stupid, which is also very likely. ;) I just figured I'd mention it here since this function sounds like it should allow me to do that.

#10

moshe weitzman - August 2, 2006 - 05:43

@webchick - in your example you are accepting $original_form as a reference AND returning it. not good, though i dunno if thats bad enough to explain the failure. anyone available to review this some more?

#11

dmitrig01 - June 23, 2007 - 17:35
Version:x.y.z» 6.x-dev
Status:needs review» closed

there is now a #weight element...

#12

moshe weitzman - June 23, 2007 - 21:12
Title:array_insert()» array_insert() to help inject items into a particular position in form array
Status:closed» needs work

huh? we have had #weight since fapi was born. how does that help with the stated problem?

#13

birdmanx35 - January 26, 2008 - 15:55
Version:6.x-dev» 7.x-dev

Feature requests go to 7.x-dev.

#14

cyberswat - October 9, 2008 - 02:18

I had a need for this functionality so did some testing on the two patches ... I was able to get what I wanted by using jjeff's version in comment 3 ... the following "code" is probably lengthier than it needs to be, but it illustrates that the function performs as promised. I was not able to get this functionality from moshe's patch as they indeed have different purposes.

// text keys
$before_submit = drupal_array_insert($form, '#submit', array('mykey' => 'test'), TRUE );

[mykey] => test
[#submit] => Array
(
  [0] => user_register_submit
)


$after_submit = drupal_array_insert($form, '#submit', array('mykey' => 'test'), FALSE );

[#submit] => Array
(
  [0] => user_register_submit
)
[mykey] => test


//numeric keys
$before_submit = drupal_array_insert($form, '#submit', array('test'), TRUE );

[0] => test
[#submit] => Array
(
  [0] => user_register_submit
)


$after_submit = drupal_array_insert($form, '#submit', array('test'), FALSE );

[#submit] => Array
(
  [0] => user_register_submit
)
[0] => test

// text keys
$before_submit = drupal_array_insert($form['#submit'], 0, array('mykey' => 'test'), TRUE );

Array
(
[mykey] => test
[0] => user_register_submit
)


$after_submit = drupal_array_insert($form['#submit'], 0, array('mykey' => 'test'), FALSE );

Array
(
[0] => user_register_submit
[mykey] => test
)

//numeric keys
$before_submit = drupal_array_insert($form['#submit'], 0, array('test'), TRUE );

Array
(
[0] => test
[1] => user_register_submit
)



$after_submit = drupal_array_insert($form['#submit'], 0, array('test'), FALSE );

Array
(
[0] => user_register_submit
[1] => test
)

Just for kicks I tried to break the above tests by setting the key of the array I was passing in to the same as the key I was trying to position before and after. This worked as promised with numbers. When I tried conflicting string keys like in the following it simply returned the original value of $form

$before_submit = drupal_array_insert($form, '#submit', array('#submit' => 'test'), TRUE );

Hope that helps

#15

deviantintegral - January 30, 2009 - 18:13

This is great! I did a google search for this functionality and the Drupal page was the first hit :).

#3 works as expected for me, and helps in the use case where no weights are assigned to elements. I'll plan on writing a patch + test for this soon.

 
 

Drupal is a registered trademark of Dries Buytaert.