views:

57

answers:

4

Is it possible in PHP to extract values from an array with a particular key path and return an array of those values? I'll explain with an example:

$user =
  array (
    array(
      'id' => 1,
      'email' =>'[email protected]',
      'project' => array ('project_id' => 222, 'project_name' => 'design')
    ),
    array(
      'id' => 2,
      'email' =>'[email protected]',
      'project' => array ('project_id' => 333, 'project_name' => 'design')
    )
  );

/** I have to write a function something like: */
$projectIds = extractValuesWithKey($user, array('project', 'project_id'));
print_r($projectIds);

Output:

Array(
[0] => 222,
[1] => 333
)
+1  A: 

Well, that's easier than you think.

function extractValuesWithKey($array, $parts) {
    $return = array();
    $rawParts = $parts;
    foreach ($array as $value) {
        $tmp = $value;
        $found = true;
        foreach ($parts as $key) {
            if (!is_array($tmp) || !isset($tmp[$key])) {
                $found = false;
                continue;
            } else {
                $tmp = $tmp[$key];
            }
        }
        if ($found) {
            $return[] = $tmp;
        }
    }
    return $return;
}
ircmaxell
+1  A: 

If the 'key path' isn't dynamic, you can do a one-liner with array_map:

$projectIds = array_map(function($arr) { return $arr['project']['project_id']; }, $user);

Alternatively, for dynamic paths:

function extractValuesWithKey($users, $path) {
 return array_map(function($array) use ($path) {
  array_walk($path, function($key) use (&$array) { $array = $array[$key]; });
  return $array;
 }, $users);
}

The closures/anonymous functions only work with PHP 5.3+, and I've no idea how this would compare performance-wise to a double foreach loop. Note also that there's no error checking to ensure that the path exists.

Chris Smith
+1. Accepted the double foreach loop as it runs on PHP 5.2 also.
Sabya
+2  A: 

I would have gone for a different approach (not that there's anything wrong with the array-function-based answers) by using a recursive iterator to flatten the array which makes the key-path comparison fairly simple.

function extractValuesWithKey($array, $keys) {
    $iterator   = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
    $keys_count = count($keys);
    // No point going deeper than we have to!
    $iterator->setMaxDepth($keys_count);

    $result = array();
    foreach ($iterator as $value) {
        // Skip any level that can never match our keys
        if ($iterator->getDepth() !== $keys_count) {
            continue;
        }
        // Build key path to current item for comparison
        $key_path = array();
        for ($depth = 1; $depth <= $keys_count; $depth++) {
            $key_path[] = $iterator->getSubIterator($depth)->key();
        }
        // If key paths match, add to results
        if ($key_path === $keys) {
            $result[] = $value;
        }
    }
    return $result;
}

To make the whole thing more useful, you could even wrap the code into a custom FilterIterator rather than a basic function, but I guess that's probably a different question entirely.

salathe
A: 

I also used a similiar function in one of my projects, maybe you find this useful:

    function extractValuesWithKey($data, $path) {
        if(!count($path)) return false;

        $currentPathKey = $path[0];

        if(isset($data[$currentPathKey])) {
            $value = $data[$currentPathKey];
            return is_array($value) ? extractValuesWithKey($value, array_slice($path, 1)) : $value;
        }
        else {
            $tmp = array();

            foreach($data as $key => $value) {
                if(is_array($value)) $tmp[] = extractValuesWithKey($value, $path);
            }

            return $tmp;
        }
    }
Max