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'):

PLAIN TEXT
PHP:
  1. $a = array(
  2.     'user' => 'jim',
  3.     'pass' => 'secret',
  4.     'friends' => array('bob', 'tom', 'paul')
  5. );
  6. $b = array(
  7.     'pass' => 'new-password',
  8.     'last_login' => 'today'
  9. );
  10. debug(array_merge($a, $b));
  11.  
  12. /* Output:
  13. Array
  14. (
  15.     [user] => jim
  16.     [pass] => new-password
  17.     [friends] => Array
  18.         (
  19.             [0] => bob
  20.             [1] => tom
  21.             [2] => paul
  22.         )
  23.     [last_login] => today
  24. )
  25. */
  26.  

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:

PLAIN TEXT
PHP:
  1. $a = array(
  2.     'User' => array(
  3.         'name' => 'jim',
  4.         'pass' => 'secret'
  5.     )
  6. );
  7. $b = array(
  8.     'User' => array(
  9.         'pass' => 'new-pw',
  10.         'last_login' => 'new-pass'
  11.     )
  12. );
  13. debug(array_merge($a, $b));
  14.  
  15. /* Output:
  16. Array
  17. (
  18.     [User] => Array
  19.         (
  20.             [pass] => new-pw
  21.             [last_login] => new-pass
  22.         )
  23. )
  24. */
  25.  

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:

PLAIN TEXT
PHP:
  1. $a = array(
  2.     'user' => array(
  3.         'name' => 'jim',
  4.         'pass' => 'secret'
  5.     )
  6. );
  7. $b = array(
  8.     'user' => array(
  9.         'pass' => 'new-pw',
  10.         'last_login' => 'new-pass'
  11.     )
  12. );
  13. debug(array_merge_recursive($a, $b));
  14.  
  15. /* Output:
  16. Array
  17. (
  18.     [user] => Array
  19.         (
  20.             [name] => jim
  21.             [pass] => Array
  22.                 (
  23.                     [0] => secret
  24.                     [1] => new-pw
  25.                 )
  26.             [last_login] => new-pass
  27.         )
  28. )
  29. */
  30.  

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:

PLAIN TEXT
PHP:
  1. $a = array(
  2.         'user' => array(
  3.                 'name' => 'jim',
  4.                 'pass' => 'secret'
  5.         )
  6. );
  7. $b = array(
  8.         'user' => array(
  9.                 'pass' => 'new-pw',
  10.                 'last_login' => 'new-pass'
  11.         )
  12. );
  13. debug(Set::merge($a, $b));
  14.  
  15. /* Output:
  16. Array
  17. (
  18.     [user] => Array
  19.         (
  20.             [name] => jim
  21.             [pass] => new-pw
  22.             [last_login] => new-pass
  23.         )
  24. )
  25. */
  26.  

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

PLAIN TEXT
PHP:
  1. $a = array(
  2.     'Users' => array(
  3.         'jim', 'bob',
  4.         'count' => 2
  5.     )
  6. );
  7. $b = array(
  8.     'Users' => array(
  9.         'lisa', 'tina',
  10.         'count' => 4
  11.     )
  12. );
  13. debug(Set::merge($a, $b));
  14.  
  15. /* Output:
  16. Array
  17. (
  18.     [Users] => Array
  19.         (
  20.             [0] => jim
  21.             [1] => bob
  22.             [count] => 4
  23.             [2] => lisa
  24.             [3] => tina
  25.         )
  26. )
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.