Merge multiple multidimensional arrays by value

Problem

I have the following array, consisting of N different services, where each entry consists of an identifier and a unique (user)name.

$input = [
    'service_1' => [
        '1234' => 'John_Doe_1',
        '4567' => 'Jane Doe X',
        '7891' => 'J.Doe1',
    ],
    'service_2' => [
        '100001' => 'Jane Doe X',
        '100002' => 'John_Doe_1',
        '100003' => 'J.Doe1',
    ],
    'service_N' => [
        '07faed21-2920-4d7d-a263-88deba9c422c' => 'John_Doe_1',
        '1160178c-dfbf-4091-b4c0-a8ec55c22800' => 'J.Doe1',
    ],
];

Now I'm looking for a way to format it in a way that I get the identifiers across each (user)name for the different services:

$output = [
    'John_Doe_1' => [
        'service_1' => '1234',
        'service_2' => '100002',
        'service_N' => '07faed21-2920-4d7d-a263-88deba9c422c',
    ],
    'Jane Doe X' => [
        'service_1' => '4567',
        'service_2' => '100001',
        'service_N' => null, // either value should be null or key should not exist
    ],
    'J.Doe1' => [
        'service_1' => '7891',
        'service_2' => '100003',
        'service_N' => '1160178c-dfbf-4091-b4c0-a8ec55c22800',
    ],
];

I'm looking for a flexible way (with N services) to do this but I can't come up with a good solution.


The code below should do the trick. This is how it works.

  1. Loops over the keys of the outer array to get the keys for the output array.
  2. Loops over the key value pair within every inner array.
  3. Creates an empty array in the output array with the username as key if it does not exist.
  4. Adds the 'ID' of the server under the name that we created earlier under the key it's currently looping through.

This should also still work within a reasonable time if your array input gets really big (e.g. 10000 elements).

$output = [];

// Loop over service_1, service_2, service_N etc.
foreach(array_keys($input) as $service_name)
{
    // Loop over the inner key value pair (e.g. 10001 => John Doe X)
    foreach($input[$service_name] as $service_id => $username)
    {
        // Create a key with the name if it does not exist in the output.
        if(!isset($output[$username]))
        {
            $output[$username] = [];
        }

        // Add the key value pair to the correct output name.
        $output[$username][$service_name] = $service_id;
    }
}

That code will produce the following output.

Array
(
    [John_Doe_1] => Array
        (
            [service_1] => 1234
            [service_2] => 100002
            [service_N] => 07faed21-2920-4d7d-a263-88deba9c422c
        )

    [Jane Doe X] => Array
        (
            [service_1] => 4567
            [service_2] => 100001
        )

    [J.Doe1] => Array
        (
            [service_1] => 7891
            [service_2] => 100003
            [service_N] => 1160178c-dfbf-4091-b4c0-a8ec55c22800
        )

)

I've been on a functional programming kick recently and figured I'd dive into PHP to see what I could come up with. Here's a nested array_walk method that seems to do the trick!

$output = Array();
array_walk($input, function($item, $key) use (&$output) {
    array_walk($item, function($item, $key, $parent_key) use (&$output) {
        $output[$parent_key][$item] = $key;
    }, $key);
});
var_dump($output);