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);

/**
 * 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;
}
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

m3avrck’s picture

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

chx’s picture

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.

jjeff’s picture

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.


/**
 * 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
)
**/
chx’s picture

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 :(

moshe weitzman’s picture

Status: Active » Needs review
FileSize
939 bytes

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.

jjeff’s picture

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.

drumm’s picture

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

moshe weitzman’s picture

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

webchick’s picture

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


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.

moshe weitzman’s picture

@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?

dmitrig01’s picture

Version: x.y.z » 6.x-dev
Status: Needs review » Closed (fixed)

there is now a #weight element...

moshe weitzman’s picture

Title: array_insert() » array_insert() to help inject items into a particular position in form array
Status: Closed (fixed) » Needs work

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

birdmanx35’s picture

Version: 6.x-dev » 7.x-dev

Feature requests go to 7.x-dev.

cyberswat’s picture

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

deviantintegral’s picture

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.

effulgentsia’s picture

Version: 7.x-dev » 8.x-dev

D8 material. What I'd really like to see in D8 is full DOM-like management of form/render arrays, perhaps modeled after jQuery, if we're able to do so in a performant way.

moshe weitzman’s picture

One of Steven's masterpieces - http://drupal.org/project/fquery. He didn't ever maintain it so it has not seen any traction. But someone should really dust it off and promote.

aaron’s picture

bfroehle’s picture

I'd like to see moshe's patch in #5 make it in. It's short and extremely useful when altering forms. For example, to insert a new column in a tableselect or table, you need to insert a new entry into $element['#header']

LordQuackstar’s picture

Subscribing...

bfroehle’s picture

Status: Needs work » Needs review
FileSize
2.16 KB

Resurrecting this issue. '#weight' can solve many, but not all, reasons for this helper function. In addition to one use in batch_set() there are certainly some additional uses in contrib. For example, CAS uses this technique to add a column to a table render array. [Clearly this is something '#weight' would never be used for].

joachim’s picture

Status: Needs review » Needs work
+++ b/core/includes/common.inc
@@ -2602,6 +2602,26 @@ function drupal_exit($destination = NULL) {
+ * @param $position
+ *   The position in $array before which $insert_array is to be inserted.

If you have to give a numeric position for the insertion, then I don't really see the point of it. You're only saving a couple of lines in cases where you know the position already.

The OP function was useful because you often *don't* know the position of the array where you want to insert something, you know it's 'after key 'foo'' or 'before key 'bar'.

joachim’s picture

Here's the OP's function tweaked for current coding standards and modified to change the given array directly, which keeps it in line with array_splice().

/**
 * Inserts values into an array after a given key.
 *
 * Values from $insert_array are inserted after (or before) $key in $array. If
 * $key is not found, $insert_array is appended to $array using array_merge().
 *
 * @param $array
 *   The array to insert into. Passed by reference and altered in place.
 * @param $key
 *   The key of $array to insert after
 * @param $insert_array
 *   An array whose values should be inserted.
 * @param $before
 *   If TRUE, insert before the given key, rather than after it.
 *   Defaults to inserting after.
 */
function array_insert(&$array, $key, $insert_array, $before = FALSE) {
  $done = FALSE;
  foreach ($array as $array_key => $array_val) {
    if (!$before) {
      $new_array[$array_key] = $array_val;
    }
    if ($array_key == $key && !$done) {
      foreach ($insert_array as $insert_array_key => $insert_array_val) {
        $new_array[$insert_array_key] = $insert_array_val;
      }
      $done = TRUE;
    }
    if ($before) {
      $new_array[$array_key] = $array_val;
    }
  }
  if (!$done) {
    $new_array = array_merge($array, $insert_array);
  }
  // Put the new array in the place of the original.
  $array = $new_array;
}

Could do with some more comments to explain how it works, though I haven't fully worked that out yet.... it does work though :D

amontero’s picture

#15: +1! I googled and grabbed #23. Saved me a lot of work turning dealing with steps in a D7 install profile into one liners!

If it is backport elegible (not sure) or considering backports module, wanted to add my use case. Subscribing.

amontero’s picture

mrfelton’s picture

Nice. Tis lets me really inject additional views fileds and filters into a specific place with a view:

/**
 * Implements hook_views_default_view_alter().
 */
function mymodule_locale_views_default_views_alter(&$views) {
  if (isset($views['admin_views_node'])) {
    $handler =& $views['admin_views_node']->display['default']->handler;

    /* Field: Content: Language */
    $language_field = array();
    $language_field['language']['id'] = 'language';
    $language_field['language']['table'] = 'node';
    $language_field['language']['field'] = 'language';
    $language_field['language']['empty'] = ' - ';
    array_insert(&$handler->display->display_options['fields'], 'name', $language_field);

    /* Filter criterion: Content: Language */
    $language_filter = array();
    $language_filter['language']['id'] = 'language';
    $language_filter['language']['table'] = 'node';
    $language_filter['language']['field'] = 'language';
    $language_filter['language']['value'] = array(
      'und' => 'und',
      'ie' => 'ie',
      'en' => 'en',
    );
    $language_filter['language']['group'] = 1;
    $language_filter['language']['exposed'] = TRUE;
    $language_filter['language']['expose']['operator_id'] = 'language_op';
    $language_filter['language']['expose']['label'] = 'Language';
    $language_filter['language']['expose']['operator'] = 'language_op';
    $language_filter['language']['expose']['identifier'] = 'language';
    $language_filter['language']['expose']['reduce'] = TRUE;
    array_insert(&$handler->display->display_options['filters'], 'uid', $language_filter);
  }
}
tim.plunkett’s picture

Status: Needs work » Postponed
Issue tags: -Backwards compatible API change +Needs backport to D7
13rac1’s picture

Ah ha! Setting my issue/patch as duplicate of this: #1343024: Add drupal_array_insert_before() & drupal_array_insert_after()

DuaelFr’s picture

For those who need it today, here is a patch for 7.15 based on #23 (Thanks a lot !)

For consistency reasons, I changed function signature to

drupal_array_insert(&$array, $key, $insert_array, $before = FALSE)
jcisio’s picture

Is core going to use that, or is it just a utility function to help contrib module for _alter()?

bfroehle’s picture

A long time ago in #21 there were some proposed uses in core. Not sure if those are still relevant.

amontero’s picture

Issue tags: +Novice

Tagging

amontero’s picture

Title: array_insert() to help inject items into a particular position in form array » Add insert() to Drupal\Component\Utility\NestedArray to help inject items into a particular position in arrays
Status: Postponed » Needs review
FileSize
2.06 KB

As per #27, rerolling joachim's code (#21) and moving from postponed to active again.

amontero’s picture

RobLoach’s picture

Is there any place where we can take advantage of this? With the introduction of form controllers, I see the need for this slowly fade.

amontero’s picture

Not only forms were intended to benefit from this. IMO, it's a useful function to have for dealing with arrays.
My use case was inserting a step in an install profile. I'm not fully aware of how much D8 has evolved code-wise, but would take me by surprise not having other parts of its API dealing with arrays in general.
Maybe forms can not take advantage of it, but I think there are other cases in that it might be helpful.

tstoeckler’s picture

If http://drupal.org/node/1876718 does not make it, a lot of contribs will hit this use-case. I would obviously prefer a cooler solution over there, but...

amontero’s picture

Component: forms system » base system

IMO, this solution is more generic so you get a function you can use everywhere, not only in the linked issue. More like a toolbox function. And use this in #1876718: Allow modules to inject columns into tables more easily, of course.
This would have the nice side benefit of being safely backportable as an API addition to 7.x as drupal_array_insert() for instance ;)
Even more code could benefit.

(Tentatively changing component to better reflect this)

deviantintegral’s picture

Would this still be useful for fields with deltas? If not, I'd say this can be closed as a won't fix.

DuaelFr’s picture

What gives strength to Drupal is its great API which allow developpers to (almost) always find the tool for their needs. Implementing this function has no cost for performances nor maintenance because its code is quite simple so I think we should just do it. We do not need endless topics about such kind of tiny problems.

The main use case in which I needed it was to inject a new filter into a view. Views' code is full of these string indexed arrays and I am sure we could find some other examples where it could help.

Maybe the good practice would be to have weight on everything but as long as we have to deal with contributed modules we cannot ensure that this will happen.

deviantintegral’s picture

Assigned: Unassigned » deviantintegral
deviantintegral’s picture

Assigned: deviantintegral » Unassigned
FileSize
3.86 KB

Here's an update that adds tests for the three cases of array insertion (before, middle, end) as well as does some minor documentation style fixes.

tstoeckler’s picture

+++ b/core/lib/Drupal/Component/Utility/NestedArray.php
@@ -342,4 +342,43 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA
+    $done = FALSE;
...
+        $done = TRUE;

It seems $done is only needed in case there are 2 array keys that are identical to the specified $key. But I don't think this is possible in PHP. Can someone elaborate?

+++ b/core/lib/Drupal/Component/Utility/NestedArray.php
@@ -342,4 +342,43 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA
+      if ($array_key == $key && !$done) {

Can we get some braces around the first condition here please. That would make it less ambiguous.

amontero’s picture

To me, it looks like $done helps at least speed the for loop now by short-circuitting the if as I have now swapped conditions' order. Also, it covers the case if there is no key found at all to insert after/before. However I did not made any further test.
But if there are still valid concerns, some of them could be written as tests, as well. This could make safer later speed enhancements that might arise before RTBC, too.
Reroll of deviantart's patch with braces as per #57 and swapped conditions.
So, the interdiff:

31c42
< +      if ($array_key == $key && !$done) {
---
> +      if (!$done && ($array_key == $key)) {
116a128,130
> -- 

Status: Needs review » Needs work
Issue tags: -Novice, -API addition, -Needs backport to D7

The last submitted patch, drupal--66183--array_insert--58.patch, failed testing.

amontero’s picture

Status: Needs work » Needs review
amontero’s picture

Status: Needs review » Needs work
Issue tags: +Novice, +API addition, +Needs backport to D7

The last submitted patch, drupal--66183--array_insert--58.patch, failed testing.

amontero’s picture

Status: Needs work » Needs review
FileSize
4.42 KB

NestedArrayUnitTest.php file has been moved to NestedArrayTest.php, rebase'n'reroll of #58.

chx’s picture

If we are going to add it to NestedArray shouldn't it be, you know, nested?

amontero’s picture

jsobiecki’s picture

Patch applies for me and seems to work. Tests are all green.

thedavidmeister’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Patch does not apply for me:

tdm:d8 thedavidmeister$ git apply drupal--66183--array_insert--63.patch 
error: patch failed: core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php:160
error: core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php: patch does not apply

But after re-roll I think this is RTBC.

thedavidmeister’s picture

Also, I came up against this problem the other day just generally working with renderable arrays so +1 for a tool to make that easier.

Jalandhar’s picture

Status: Needs work » Needs review
FileSize
3.83 KB

Rerolled patch

thedavidmeister’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Novice, -Needs reroll

RTBC as per #67

tstoeckler’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/lib/Drupal/Component/Utility/NestedArray.php
    @@ -344,4 +344,43 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA
    +   * Values from $insert_array are inserted after (or before) $key in $array. If
    +   * $key is not found, $insert_array is appended to $array using array_merge().
    

    The "in $array" part sounds weird to me. Suggestion "Values from $insert_array are inserted into $array after $key."

    Also is there any specific reason to support a non-existing $key explicitly? I can't come up with a use-case for that, to be honest.

  2. +++ b/core/lib/Drupal/Component/Utility/NestedArray.php
    @@ -344,4 +344,43 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA
    +   *   The array to insert into. Passed by reference and altered in place.
    

    The second sentence is not actually a full sentence. I would suggest simply "The array to insert into, passed by reference."

  3. +++ b/core/lib/Drupal/Component/Utility/NestedArray.php
    @@ -344,4 +344,43 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA
    +   *   The key of $array to insert after
    

    Missing trailing period.

  4. +++ b/core/lib/Drupal/Component/Utility/NestedArray.php
    @@ -344,4 +344,43 @@ public static function mergeDeepArray(array $arrays, $preserve_integer_keys = FA
    +   * @param bool $before
    +   *   If TRUE, insert before the given key, rather than after it.
    +   *   Defaults to inserting after.
    ...
    +  public static function insert(&$array, $key, $insert_array, $before = FALSE) {
    

    I personally dislike parameters that change the behavior of a function (and quite drastically, too).

    I would prefer separate functions insertBefore() and insertAfter().

I also tried to simplify the code logic using PHP's internal array_* functions, but the result was a little bit slower in terms of performance that's why I don't want to "officially" propose doing that, even though I find the code to much more readable. In case anyone is interested the following code achieves the same that the current does:

  public static function insertBefore(&$array, $key, $insert_array) {
    $pos = array_search($key, array_keys($array)) ?: 0;
    $array =
      array_slice($array, 0, $pos) +
      $insert_array +
      array_slice($array, $pos);
  }

Moving back to needs review at least to see if people agree with splitting the method into two. The other nitpicks are only minor and I wouldn't want to hold up this very useful utility any further.

I also realized that after adding these methods we shuld probably rename the NestedArray class to just Array as these methods have nothing to do with nested arrays. Let's leave that for a follow-up, though.

thedavidmeister’s picture

Status: Needs review » Needs work

Rather than introduce insertBefore and insertAfter, why not add an $offset instead of a boolean. The default for $offset 0 would insert after, -1 would be before.

Also, I just had a thought, that it makes no sense to append the insert array at the end of an array if "before" was set.

How much slower is that code really? it's a little difficult when someone says "a little bit slower/faster" without posting their benchmarks/profiling or how to reproduce as the subsequent conversation often ends up taking it as a fact.

The extra legibility could easily be worth a tiny slowdown (I must admit, that foreach did my head in a bit too, and if I had to pause to think I'm sure others will too), I think it's *unlikely* (I won't say impossible :P) to see a function like this on a critical performance path in the wild.

One thing, with the array_slice() approach, the PHP docs say that keys are not preserved without TRUE passed as a fourth parameter.

Consider:

<?php

$array = array('a', 'b', 'c');
$insert_array = array('one' => 'two');
$key = 1;


$pos = array_search($key, array_keys($array)) ?: 0;
$array = array_slice($array, 0, $pos) + $insert_array + array_slice($array, $pos);

var_dump($array);

we get:

array(3) {
  [0]=>
  string(1) "a"
  ["one"]=>
  string(3) "two"
  [1]=>
  string(1) "c"
}
thedavidmeister’s picture

$pos = array_search($key, array_keys($array)) ?: 0;
$array = array_slice($array, 0, $pos, TRUE) + $insert_array + array_slice($array, $pos, NULL, TRUE);

works for the above example

thedavidmeister’s picture

What about this?

function insert(&$array, $key, $insert_array, $offset = 0) {
  $pos = isset($array[$key]) ? array_search($key, array_keys($array)) + $offset : 0;
  $array = array_slice($array, 0, $pos, TRUE) + $insert_array + array_slice($array, $pos, NULL, TRUE);
}

If offset is 0 then the insert array will appear immediately before $key, any numeric offset will move the insert array forwards and backwards (so setting offset = 1 has the same effect as setting $before to FALSE in #69). Setting an offset larger than the size of the array sticks the insert array at the start or end of the array as appropriate.

I used isset() as it is fast and allows us to avoid searching for things that don't exist. If the key does not exist, $insert_array will be added to the start of $array.

thedavidmeister’s picture

hmm, because of use of + for array merging, we get weird behaviour with indexed arrays.

$array = array('a', 'b', 'c');
$insert_array = array('one', 'two');
$key = 1;
$offset = 0;

$pos = isset($array[$key]) ? array_search($key, array_keys($array)) + $offset : 0;
$array = array_slice($array, 0, $pos, TRUE) + $insert_array + array_slice($array, $pos, NULL, TRUE);

var_dump($array);

results in:

array(3) {
  [0]=>
  string(1) "a"
  [1]=>
  string(3) "two"
  [2]=>
  string(1) "c"
}

thedavidmeister’s picture

$pos = isset($array[$key]) ? array_search($key, array_keys($array)) + $offset : 0;
$array = array_merge(array_slice($array, 0, $pos, TRUE), $insert_array, array_slice($array, $pos, NULL, TRUE));

This can handle the cases in #72, #75.

https://ideone.com/j45dxV

I'd suggest that based on how easy it is to make mistakes with array functions, as demonstrated above, we should extend our test coverage to include indexed arrays and not just associative ones.

amontero’s picture

Status: Needs work » Needs review
FileSize
4.42 KB

Reroll.

Status: Needs review » Needs work

The last submitted patch, 77: drupal--66183--array_insert--77.patch, failed testing.

amontero’s picture

Status: Needs work » Needs review
FileSize
4.23 KB

Rebase & reroll.

thedavidmeister’s picture

Status: Needs review » Needs work

Completely ignoring #72 through #76?

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

> Rather than introduce insertBefore and insertAfter, why not add an $offset instead of a boolean. The default for $offset 0 would insert after, -1 would be before.

I think that makes it less legible, not more.

> I would prefer separate functions insertBefore() and insertAfter().

That would certainly be unambiguous. Those could both wrap around a protected method with the common code.

> I also realized that after adding these methods we shuld probably rename the NestedArray class to just Array as these methods have nothing to do with nested arrays. Let's leave that for a follow-up, though.

Can't rename it now, and NestedArray is a good name for the nester array helper methods. We could add an Array class to go alongside it though.

joachim’s picture

... though on reflection, it occurs to me that we probably can't call a class 'Array' as that's reserved in PHP.

Anyone care to bikeshed a better name?

tim.plunkett’s picture

Version: 8.4.x-dev » 8.5.x-dev

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

Brainwave -- how about we call the class AssociativeArray? Since numeric arrays won't need this as they can use array_splice().

joachim’s picture

Title: Add insert() to Drupal\Component\Utility\NestedArray to help inject items into a particular position in arrays » Add helper methods to inject items into a particular position in associative arrays
FileSize
5.56 KB

> Also is there any specific reason to support a non-existing $key explicitly? I can't come up with a use-case for that, to be honest.

Agreed. I've removed that.

> I'd suggest that based on how easy it is to make mistakes with array functions, as demonstrated above, we should extend our test coverage to include indexed arrays and not just associative ones.

I don't think this should cater for indexed arrays. If you want to insert something into an indexed array, can't you just use array_splice()? I don't think there are cases where you'd have an indexed array and want to insert an associative key into it.

> Completely ignoring #72 through #76?

I've left off the changes suggested in those comments. Perhaps they should be added, but I don't currently feel alert enough to parse expressions like:

$array = array_slice($array, 0, $pos, TRUE) + $insert_array + array_slice($array, $pos, NULL, TRUE);

Changes from previous patches:

- code is moved to a new utility class, AssociativeArray, since NestedArray doesn't make sense for this.
- two methods rather than one, insertBefore and insertAfter. I think this makes it much clearer than having a boolean parameter.
- removed support for a non-existent $key, as per previous comments.

tim.plunkett’s picture

Yes, array_splice works fine for numerically indexed arrays. But I think it'd be overall better DX to not have to worry about numeric vs associative, and to provide an InsertArray class (similarly named to DiffArray) that supports both with one method.

joachim’s picture

Assigned: Unassigned » joachim

> But I think it'd be overall better DX to not have to worry about numeric vs associative

Numeric values should work with the current brute force approach code -- one advantage over the use of PHP array functions.

Though

      if (!$done && ($array_key == $key)) {

That should be a strict comparison, in case we have keys 0 and FALSE and ''.

One thing that I'm confused about:

  protected function verifyArrayOrder($array, $expectedArray) {
    reset($array);
    foreach ($expectedArray as $expectedValue) {
      $this->assertEquals(current($array), $expectedValue, 'Arrays contain identical values.');
      next($array);
    }
  }

How is this assertion better/different from assertSame()? Is it just that this code predates the use of PHPUnit in Drupal?

joachim’s picture

> How is this assertion better/different from assertSame()? Is it just that this code predates the use of PHPUnit in Drupal?

Turns out it's the same.

> Numeric values should work with the current brute force approach code -- one advantage over the use of PHP array functions.

They don't... I've switched to the approach from #76, although that too had mistakes with the $offset.

Changes since last patch:

- renamed to InsertArray
- use assertSame() instead of custom helper; be explicit about what we expect rather than faff with existing arrays -- it makes the test harder to read and debug
- added testing for numeric arrays
- throw an exception if the array doesn't contain $key

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

robpowell’s picture

tested #93 in a hook_form_alter where I wanted to add a render array in the form array, works well.

robpowell’s picture

Status: Needs review » Reviewed & tested by the community
jcisio’s picture

Status: Reviewed & tested by the community » Needs work

Re #91, #93: it's not actually the same.

$a = [3 => 3, 2 => 2, 1 => 1];
$b = [4 => 4];
InsertArray::insertBefore($a, '1', $b);
var_export($a);

This does not return the expected result:

// It returns
array (
  0 => 3,
  1 => 2,
  2 => 4,
  3 => 1,
)
// instead of
array (
  3 => 3,
  2 => 2,
  4 => 4,
  1 => 1,
)

To fix it, we can replace the array_merge() with the array addition operator:

// instead of
    $array = array_merge(
      $array_before,
      $insert_array,
      $array_after
    );
// do
    $array = $array_before + $insert_array + $array_after;

However there is a behavior change: when there are duplicate keys, array_merge replaces values in the first array with the later, while the operator keeps values in the first array, and it is even more complicated when merge 3 arrays.

Should we reduce the scope of this class? By saying that 1/ if we support array with numeric keys 2/ what is the expected behaviors when there are duplicated keys. How many places in core that can use of this new utility class? We can then decide on which use cases should it support and how much effort could we spend on this class.

My 2c in this 14 year old issue.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joachim’s picture

Finally taken the time to get my head around #99!

The problem with changing to using the array union + operator instead of array_merge() is that it won't re-index numeric keys when we expect it to. So if we have:

// original
        [
          'first item',
          'third item',
        ],
// insert
        [
          'second item',
        ],
// insert: before 1

because these arrays are numeric, we'd expect to get something with keys 0, 1, 2.

> Should we reduce the scope of this class? By saying that 1/ if we support array with numeric keys 2/ what is the expected behaviors when there are duplicated keys. How many places in core that can use of this new utility class? We can then decide on which use cases should it support and how much effort could we spend on this class.

I'm inclined to say this class should only support associative arrays, that is, any keys that happen to be numbered are treated as if they were strings, and keys will NOT be re-indexed.

> what is the expected behaviors when there are duplicated keys

We should check for that and throw an exception.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.