Diffing, flattening and expanding multidimensional arrays in PHP

PHP has functions that can compute the difference between two arrays built in. The comments sections for those functions are filled with people trying to figure out the best way to do the same thing with multidimensional arrays, and almost all of them are recursive diffing functions that try to walk the tree and do a diff at each level.

The problem with this approach are 1) they are unreliable as they usually don’t account for all data types at each level, and 2) they’re slow, due to multiple calls to array_diff at each level of the tree.

A better approach, I think, is to flatten a multidimensional array into a single dimension, make a single call to array_diff, then (if needed) expand it back out if you really need the resulting diff to be multidimensional.

Lets look at some code. The following recursive function flattens a multidimensional array into a single dimension.

<?php
function flatten($arr, $base = "", $divider_char = "/") {
    $ret = array();
    if(is_array($arr)) {
        foreach($arr as $k = $v) {
            if(is_array($v)) {
                $tmp_array = flatten($v, $base.$k.$divider_char, $divider_char);
                $ret = array_merge($ret, $tmp_array);
            } else {
                $ret[$base.$k] = $v;
            }
        }
    }
    return $ret;
}
?>

The following function (based on this function found here) reinflates the array back up after it’s been deflated.

<?php
function inflate($arr, $divider_char = "/") {
    if(!is_array($arr)) {
        return false;
    }

    $split = '/' . preg_quote($divider_char, '/') . '/';

    $ret = array();
    foreach ($arr as $key = $val) {
        $parts = preg_split($split, $key, -1, PREG_SPLIT_NO_EMPTY);
        $leafpart = array_pop($parts);
        $parent = &$ret;
        foreach ($parts as $part) {
            if (!isset($parent[$part])) {
                $parent[$part] = array();
            } elseif (!is_array($parent[$part])) {
                $parent[$part] = array();
            }
            $parent = &$parent[$part];
        }

        if (empty($parent[$leafpart])) {
            $parent[$leafpart] = $val;
        }
    }
    return $ret;
}
?>

Now, with arrays in flat form, it’s easy to use the built-in functions to diff:

<?php
$arr1_flat = array();
$arr2_flat = array();

$arr1_flat = flatten($arr1);
$arr2_flat = flatten($arr2);

$ret = array_diff_assoc($arr1_flat, $arr2_flat);

$diff = inflate($ret);
?>
Did something I wrote help you out?

That's great! I don't earn any money from this site - I run no ads, sell no products and participate in no affiliate programs. I do this solely because it's fun; I enjoy writing and sharing what I learn.

COVID-19 has taken the world by storm and left a lot of brokenness in its wake. A lot of people are suffering. If you feel so inclined, please make a donation to your local food bank or medical charity. Order take-out from your local Chinese restaurant. Help buy groceries for an unemployed friend. Help people make it through to the other side.

But if you found this article helpful and you really feel like donating to me specifically, you can do so below.

Read More

Using Phinx Programmatically

Phinx is a really cool database migration package that allows you to write changes to your database as code. It keeps track of which changes have been applied and allows you the option of rolling back if you hit an issue. All the documentation on Phinx describes a typical setup where you would run the phinx command to do your migrations. And that is all fine and good in most projects. But what happens if you are integrating Phinx into an existing project that already has a lot of the usual scaffolding in place?

Monitoring for Filesystem Changes using PHP and Laravel

Let’s say you have a Laravel application that does some data processing, and you want to monitor a directory for incoming changes, that you can then process using queued jobs. There are a couple of ways you could do something like this. You could scan those directories on a schedule using a cronjob. It’s doable. But what happens if you want to monitor a few thousand directories for changes? You can use tools like incron. Also doable, but another dependency. But what if I told you you could do it all with PHP. And within Laravel, no less?