debuggable

 
Contact Us
 

New fix for array junkies: Set::merge assembles yummy arrays

Posted on 5/4/07 by Felix Geisendörfer

Hi folks,

long time - no post, as always - I suck. I intend to make up for it with a screencast on unit testing in the next days, but meanwhile I want to talk about my favourite data type in PHP again: Arrays. For those of you just tuning in, I already wrote about how Cake 1.2’s Set class eats nested arrays for breakfast a while ago and if you haven't read this post yet, go ahead and do it now ; ). Todays post features a brand new Set function called merge that was a side product of me working on a cool new cake class. If you've done a lot of array work in the past, you've probably have come in situations where you wanted to merge to arrays into a new one. Usually that's a no-brainer in PHP by simply using the array_merge function (or the CakePHP wrapper 'am'):

$a = array(
    'user' => 'jim',
    'pass' => 'secret',
    'friends' => array('bob', 'tom', 'paul')
);
$b = array(
    'pass' => 'new-password',
    'last_login' => 'today'
);
debug(array_merge($a, $b));

/* Output:

Array
(
    [user] => jim
    [pass] => new-password
    [friends] => Array
        (
            [0] => bob
            [1] => tom
            [2] => paul
        )

    [last_login] => today
)
*/

In about 90%++ of all cases, this will be the usual way one uses to merge two (or more) arrays into a new one. However, sometimes array_merge is not going to cut it. That'll mostly be because it does not behave recursive and merging nested arrays can lead to unexpected results:

$a = array(
    'User' => array(
        'name' => 'jim',
        'pass' => 'secret'
    )
);
$b = array(
    'User' => array(
        'pass' => 'new-pw',
        'last_login' => 'new-pass'
    )
);
debug(array_merge($a, $b));

/* Output:

Array
(
    [User] => Array
        (
            [pass] => new-pw
            [last_login] => new-pass
        )

)

*/

This is a little counter-intuitive at least to me. I'd expect only the User.pass key to be overwritten the User.last_login one to be added. But instead array_merge just overwrites the entire 'User' key with $b's value for it. Now wait, isn't there a function called array_merge_recursive for this some of you might object? Well of course there is. However to me it's behavior is even more counter-intuitive then the one of array_merge:

$a = array(
    'user' => array(
        'name' => 'jim',
        'pass' => 'secret'
    )
);
$b = array(
    'user' => array(
        'pass' => 'new-pw',
        'last_login' => 'new-pass'
    )
);
debug(array_merge_recursive($a, $b));

/* Output:

Array
(
    [user] => Array
        (
            [name] => jim
            [pass] => Array
                (
                    [0] => secret
                    [1] => new-pw
                )

            [last_login] => new-pass
        )

)
*/

Now this time all 3 User fields show up in the new array, which is good. If one looks at the User.pass however, one will notice that instead of overwriting $a's value with the one of $b array_merge_recursive has taken both of them and thrown 'em into a indexed array. To me, that's really not what I want most of the times. My main need is to have complex array structures that hold default values (conventions) that I can then overwrite easily on demand (configuration) when calling a function.

Introducing Set::merge

So here comes Set::merge which works like one would expect array_merge_recursive to work before actually trying it out:

$a = array(
  'user' => array(
    'name' => 'jim',
    'pass' => 'secret'
  )
);
$b = array(
  'user' => array(
    'pass' => 'new-pw',
    'last_login' => 'new-pass'
  )
);
debug(Set::merge($a, $b));

/* Output:


Array
(
    [user] => Array
        (
            [name] => jim
            [pass] => new-pw
            [last_login] => new-pass
        )

)
*/

Another important thing to know about the behavior is how it deals with numerically index array items:

$a = array(
    'Users' => array(
        'jim', 'bob',
        'count' => 2
    )
);
$b = array(
    'Users' => array(
        'lisa', 'tina',
        'count' => 4
    )
);
debug(Set::merge($a, $b));

/* Output:

Array
(
    [Users] => Array
        (
            [0] => jim
            [1] => bob
            [count] => 4
            [2] => lisa
            [3] => tina
        )

)
*/

I could provide you a lot more examples but it basically comes down to the following Set::merge behavior:

Set::merge loops through all items of all arrays provided to it, and if it ...

  • ... hits an array value: Acts recursively and merges it's value over the ones of the previous values for the current key
  • ... hits an integer key: Recognizes it as a numerically indexed key and pushes the current value at the end ($a[]) of the current key value
  • ... didn't do the above: Overwrites the value of previous arrays for the current key with the new one.

Oh and the function will also typecast non-array parameters into arrays for you.

So if all of this still leave you unsure about the way this function works you probably should check out the Unit Test Case for Set::merge. It should show you pretty much every imaginable aspect of how the function can be used (including passing other Set class instances to it).

Alright, I hope you my fellow array junkies are going to get a little kick out of this one. Other then that stay tuned for the promised unit testing screencast in case you are interested in getting started with the light side of the force as far as programming goes ; ).

-- Felix Geisendörfer aka the_undefined

 
&nsbp;

You can skip to the end and add a comment.

Matt Bowden (BlenderStyle) said on Apr 05, 2007:

Great post! PHP's array_merge and array_merge_recursive have always frustrated me, but now that I see how great Cake 1.2's Set class is (again), I'll look into more. I have you're previous post about the Set class saved, and I'm going to save this one too. Thanks, Felix.

[...] In a new post today, Felix Geisendorfer shares a method for merging together nested arrays correctly with a little help from functionality in the CakePHP framework. [...]

jean-Rémy Duboc said on Apr 30, 2007:

http://dubocdesign.com/spip.php?article198 :

"Felix Geisendörfer explains us how to use the Set class from the 1.2 version of CakePHP. It proved to be very useful, for instance, associated with the Xml class, to handle a RSS file [...]"

[...] New fix for array junkies [...]

Miltiades said on Feb 05, 2008:

Cool.

Myron said on Feb 05, 2008:

Cool.

Kimon said on Feb 06, 2008:

interesting

Panayotis said on Feb 07, 2008:

Cool.

Lazaros said on Feb 08, 2008:

Nice!

Nick said on Feb 08, 2008:

Sorry :(

[...] jQuery.extend(true, hash1, hash2) is like CakePHP’s Set::merge() (an explanation of Set::merge by Felix Geisendörfer is here) [...]

[...] jQuery.extend(true, hash1, hash2) is like CakePHP’s Set::merge() (an explanation of Set::merge by Felix Geisendörfer is here) [...]

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.