Flatten Multidimensional Arrays in PHP

Sometimes when dealing with deeply nested, multidimensional arrays, it can be tedious to write out a full reference to an array element.  In the case of a recursive folder tree, we could see an array that looks something like this:

Array
(
    [dir1] => Array
        (
            [dir1_sub1] => Array
                (
                    [0] => file1.txt
                    [1] => file2.txt
                    [2] => file3.txt
                    [3] => file4.txt
                )

        )

    [dir2] => Array
        (
            [dir2_sub1] => Array
                (
                    [0] => file1.txt
                )

            [dir2_sub2] => Array
                (
                    [0] => file1.txt
                    [1] => file2.txt
                    [2] => file3.txt
                    [3] => file4.txt
                )

        )

    [dir3] => Array
        (
            [dir3_sub1] => Array
                (
                    [0] => file1.txt
                )

            [dir3_sub2] => Array
                (
                    [0] => file1.txt
                    [1] => file2.txt
                    [2] => file3.txt
                    [3] => file4.txt
                )

            [dir3_sub3] => Array
                (
                    [0] => file1.txt
                )

            [dir3_sub4] => Array
                (
                    [0] => file1.txt
                    [1] => file2.txt
                    [2] => file3.txt
                    [3] => file4.txt
                    [4] => file5.txt
                    [5] => file6.txt
                    [6] => file7.txt
                )

        )

)

 

The problem

Now, if we know that we want to get all the files in the dir3 > dir3_sub2 directory, we can of course do this:

$files = $filesystem['dir3']['dir3_sub2'];

But lets say we have a variable which holds the path to the directory we want:

$path = 'dir3/dir3_sub2';

How would we tell PHP to get the array of files in that nested directory from our $filesystem array?  We could use some loops to iterate through the nested arrays and compare the keys to segments of the path until we find the element we need.  However, if the array doesn’t need to be modified and the nested structure of the array isn’t critical, flattening the array may be the simplest approach.  A flattened version of the above $filesystem array would look something like this:

Array
(
    [dir1/dir1_sub1] => Array
        (
            [0] => file1.txt
            [1] => file2.txt
            [2] => file3.txt
            [3] => file4.txt
        )

    [dir2/dir2_sub1] => Array
        (
            [0] => file1.txt
        )

    [dir2/dir2_sub2] => Array
        (
            [0] => file1.txt
            [1] => file2.txt
            [2] => file3.txt
            [3] => file4.txt
        )

    [dir3/dir3_sub1] => Array
        (
            [0] => file1.txt
        )

    [dir3/dir3_sub2] => Array
        (
            [0] => file1.txt
            [1] => file2.txt
            [2] => file3.txt
            [3] => file4.txt
        )

    [dir3/dir3_sub3] => Array
        (
            [0] => file1.txt
        )

    [dir3/dir3_sub4] => Array
        (
            [0] => file1.txt
            [1] => file2.txt
            [2] => file3.txt
            [3] => file4.txt
            [4] => file5.txt
            [5] => file6.txt
            [6] => file7.txt
        )

)

We lose the nested structure of the original array, but it’s much easier to get an element from a string path in the flatted version.  Now we can do this to get the list of files in a path:

$path = 'dir3/dir3_sub2';
die('<pre>'.print_r($flatFilesystem[$path], true).'</pre>');

This will output:

Array
(
    [0] => file1.txt
    [1] => file2.txt
    [2] => file3.txt
    [3] => file4.txt
)

 

The flatten function

We can use a custom created function to take a multidimensional array and recursively flatten it down to a simpler form.  The function to flatten the array looks like this:

// Flatten an array of data with full-path string keys
function flat($array, $separator = '|', $prefix = '', $flattenNumericKeys = false) {
    $result = [];

    foreach($array as $key => $value) {
        $new_key = $prefix . (empty($prefix) ? '' : $separator) . $key;

        // Make sure value isn't empty
        if(is_array($value)) {
            if(empty($value)) $value = null;
            else if(count($value) == 1 && isset($value[0]) && is_string($value[0]) && empty(trim($value[0]))) $value = null;
        }

        $hasStringKeys = is_array($value) && count(array_filter(array_keys($value), 'is_string')) > 0;
        if(is_array($value) && ($hasStringKeys || $flattenNumericKeys)) $result = array_merge($result, flat($value, $separator, $new_key, $flattenNumericKeys));
        else $result[$new_key] = $value;
    }

    return $result;
}

The function requires an array to flatten, and also includes some other options:

$separator – (Optional, defaults to |) A string used to separate each key of the flattened array.  In our case we we’re dealing with filesystem items, so we used / as our separator value.

$prefix – (Optional, no default) A string which will be prepended to the flattened keys.  We don’t need to set a value for this in example mentioned above.

$flattenNumericKeys – (Optional, defaults to false) A boolean to determine if we want numeric-key elements flattened in addition to string-key elements.  In the above example, we left this as the default false.

Here is the function call made to give the output mentioned above:

$flatFilesystem = flat($filesystem, '/');

If we set the $flattenNumericKeys to true, then the output would be:

Array
(
    [dir1/dir1_sub1/0] => file1.txt
    [dir1/dir1_sub1/1] => file2.txt
    [dir1/dir1_sub1/2] => file3.txt
    [dir1/dir1_sub1/3] => file4.txt
    [dir2/dir2_sub1/0] => file1.txt
    [dir2/dir2_sub2/0] => file1.txt
    [dir2/dir2_sub2/1] => file2.txt
    [dir2/dir2_sub2/2] => file3.txt
    [dir2/dir2_sub2/3] => file4.txt
    [dir3/dir3_sub1/0] => file1.txt
    [dir3/dir3_sub2/0] => file1.txt
    [dir3/dir3_sub2/1] => file2.txt
    [dir3/dir3_sub2/2] => file3.txt
    [dir3/dir3_sub2/3] => file4.txt
    [dir3/dir3_sub3/0] => file1.txt
    [dir3/dir3_sub4/0] => file1.txt
    [dir3/dir3_sub4/1] => file2.txt
    [dir3/dir3_sub4/2] => file3.txt
    [dir3/dir3_sub4/3] => file4.txt
    [dir3/dir3_sub4/4] => file5.txt
    [dir3/dir3_sub4/5] => file6.txt
    [dir3/dir3_sub4/6] => file7.txt
)

 

Wrap it up

Converting a multidimensional array into a flat or semi-flat array can be a convenient way to access deeply nested elements by their key path.  The function used to flatten the array is shown above.  Here is also a link to the function code on GitLab:

https://gitlab.com/totaldev-instructables/php-flatten-arrays

Enjoy!

Leave a Reply

avatar
  Subscribe  
Notify of