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