Mapping associative arrays in PHP

Posted on by Chris Warburton

Trying to solve the same problem, I came across this Stack Overflow question about accessing keys in PHP’s array_map. The accepted answer uses array_keys, but this is unsatisfactory since it requires giving the array a name (I wouldn’t have been Googling for a solution if it was so easy!).

Turns out there’s no great solution, so I added an answer listing a few alternatives. I’m shamelessly re-posting it here:

The Problem

When mapping an anonymous function over an anonymous array, there is no way to access the keys:

array_map(
    function($val) use ($foo) { /* ... */ },
    array(key1 => val1,
          key2 => val2,
          /* ... */));

array_reduce doesn’t get access to the keys either. array_walk can access keys, but the array is passed by reference, which we can’t do with anonymous values.

Solutions

Array of pairs

This is bad, since we’re changing the original array, which might require pre-processing. Also, the boilerplate array() calls increase linearly with the length of the array`:

array_map(
    function($pair) use ($foo) {
        list($key, $val) = $pair;
        /* ... */
    },
    array(array(key1, val1),
          array(key2, val2),
          /* ... */));

Temporary variable

We don’t need to pre-process the array, and the boilerplate is constant, but we can accidentaly clobber an existing variable:

$i_hope_this_does_not_conflict = array(key1 => val1,
                                       key2 => val2,
                                       /* ... */);
array_map(
    function($key, $val) use ($foo) { /* ... */ },
    array_keys($i_hope_this_does_not_conflict),
    $i_hope_this_does_not_conflict);
unset($i_hope_this_does_not_conflict);

One-shot function

We can use a function to restrict the scope of our temporary variable and prevent clobbering existing names. One downside is that we need to add an extra use clause for the values we’ll need:

call_user_func(
    function($arr) use ($foo) {
        return array_map(function($key, $val) use ($foo) { /* ... */ },
                         array_keys($arr),
                         $arr);
    },
    array(key1 => val1,
          key2 => val2,
          /* ... */));

Multi-argument one-shot function

We define the function we’re mapping in the original scope to prevent the use boilerplate:

call_user_func(
    function($f, $arr) {
        return array_map($f, array_keys($arr), $arr);
    },
    function($key, $val) use ($foo) { /* ... */ },
    array(key1 => val1,
          key2 => val2,
          /* ... */));

New function

The interesting thing to note is that our last one-shot function has a nice, generic signature and looks a lot like array_map. We might want to give this a name so we can re-use it:

function array_mapk($f, $arr) {
    return array_map($f, array_keys($arr), $arr);
}

Our application code then gets back to where we started, but in the process we’ve gained a useful function for our library:

array_mapk(
    function($key, $val) use ($foo) { /* ... */ },
    array(key1 => val1,
          key2 => val2,
          /* ... */));