New fix for array junkies: Set::merge assembles yummy arrays
April 5, 2007 at 5:23 pm · Filed under CakePHP, Best Practices
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 the usual way 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
-
)
-
)