Home » Php » PHP function to check that two arrays are the same while ignoring the values of specified keys

PHP function to check that two arrays are the same while ignoring the values of specified keys

Posted by: admin July 12, 2020 Leave a comment

Questions:

I need a PHP function that can assert that two arrays are the same while ignoring the values of a specified set of keys (only the value, the keys must match).

In practice, the arrays must have the same structure, but some values can be ignored.

For example, considering the following two arrays:

Array
(
    [0] => Array
        (
            [id] => 0
            [title] => Book1 Title
            [creationDate] => 2013-01-13 17:01:07
            [pageCount] => 0
        )
)

Array
(
    [0] => Array
        (
            [id] => 1
            [title] => Book1 Title
            [creationDate] => 2013-01-13 17:01:07
            [pageCount] => 0
        )
)

they are the same if we ignore the value of the key id.

I also want to consider the possibility of nested arrays:

Array
(
    [0] => Array
        (
            [id] => 0
            [title] => Book1 Title
            [creationDate] => 2013-01-13 17:01:07
            [pageCount] => 0
        )

    [1] => Array
        (
            [id] => 0
            [title] => Book2 Title
            [creationDate] => 2013-01-13 18:01:07
            [pageCount] => 0
        )
)

Array
(
    [0] => Array
        (
            [id] => 2
            [title] => Book1 Title
            [creationDate] => 2013-01-13 17:01:07
            [pageCount] => 0
        )

    [1] => Array
        (
            [id] => 3
            [title] => Book2 Title
            [creationDate] => 2013-01-13 18:01:07
            [pageCount] => 0
        )
)

Since I need it for testing, I have come up with the following class that extends PHPUnit_Framework_TestCase and uses its assert functions:

class MyTestCase extends PHPUnit_Framework_TestCase
{
    public static function assertArraysSame($expected, $actual, array $ignoreKeys = array())
    {
        self::doAssertArraysSame($expected, $actual, $ignoreKeys, 1);
    }

    private static function doAssertArraysSame($expected, $actual, array $ignoreKeys = array(), $depth, $maxDepth = 256)
    {
        self::assertNotEquals($depth, $maxDepth);
        $depth++;

        foreach ($expected as $key => $exp) {
            // check they both have this key
            self::assertArrayHasKey($key, $actual);

            // check nested arrays 
            if (is_array($exp))
                self::doAssertArraysSame($exp, $actual[$key], $ignoreKeys, $depth);

            // check they have the same value unless the key is in the to-ignore list
            else if (array_search($key, $ignoreKeys) === false)
                self::assertSame($exp, $actual[$key]);

            // remove the current elements
            unset($expected[$key]);
            unset($actual[$key]);
        }

        // check that the two arrays are both empty now, which means they had the same lenght
        self::assertEmpty($expected);
        self::assertEmpty($actual);
    }
}

doAssertArraysSame iterates through one of the arrays and asserts recursively that the two arrays have the same keys. It also checks that they have the same values unless the current key is in the list of the keys to ignore.

To make sure the two arrays have exactly the same number of elements, each element is removed during the iteration and, at the end of the loop, the function checks that both arrays are empty.

Usage:

class MyTest extends MyTestCase
{
    public function test_Books()
    {
        $a1 = array('id' => 1, 'title' => 'the title');
        $a2 = array('id' => 2, 'title' => 'the title');

        self::assertArraysSame($a1, $a2, array('id'));
    }
}

My question is: is there a better or simpler way to accomplish this task, maybe using some already available PHP/PHPUnit functions?

EDIT: please bear in mind I don’t necessarily want a solution for PHPUnit, if there was a plain PHP function that can do this, I can use it in my tests.

How to&Answers:

I’m not sure if this is a better solution than what you’re already using, but I’ve used a similar class before when I had this exact need. It is able to give you a simple true or false response and isn’t coupled to a testing framework, which may or may not be a good thing for you.

class RecursiveArrayCompare
{
    /**
     * @var array
     */
    protected $ignoredKeys;

    /**
     *
     */
    function __construct()
    {
        $this->ignoredKeys = array();
    }

    /**
     * @param array $ignoredKeys
     * @return RecursiveArrayCompare
     */
    public function setIgnoredKeys(array $ignoredKeys)
    {
        $this->ignoredKeys = $ignoredKeys;

        return $this;
    }

    /**
     * @param array $a
     * @param array $b
     * @return bool
     */
    public function compare(array $a, array $b)
    {
        foreach ($a as $key => $value) {
            if (in_array($key, $this->ignoredKeys)) {
                continue;
            }

            if (!array_key_exists($key, $b)) {
                return false;
            }

            if (is_array($value) && !empty($value)) {
                if (!is_array($b[$key])) {
                    return false;
                }

                if (!$this->compare($value, $b[$key])) {
                    return false;
                }
            } else {
                if ($value !== $b[$key]) {
                    return false;
                }
            }

            unset($b[$key]);
        }

        $diff = array_diff(array_keys($b), $this->ignoredKeys);

        return empty($diff);
    }
}

And some examples based on your provided array:

$arr1 = array(
    'id' => 0,
    'title' => 'Book1 title',
    'creationDate' => '2013-01-13 17:01:07',
    'pageCount' => 0
);

// only difference is value of ignored key
$arr2 = array(
    'id' => 1,
    'title' => 'Book1 title',
    'creationDate' => '2013-01-13 17:01:07',
    'pageCount' => 0
);

// has extra key
$arr3 = array(
    'id' => 1,
    'title' => 'Book1 title',
    'creationDate' => '2013-01-13 17:01:07',
    'pageCount' => 0,
    'extra_key' => 1
);

// has extra key, which is ignored
$arr4 = array(
    'id' => 1,
    'title' => 'Book1 title',
    'creationDate' => '2013-01-13 17:01:07',
    'pageCount' => 0,
    'ignored_key' => 1
);

// has different value
$arr5 = array(
    'id' => 2,
    'title' => 'Book2 title',
    'creationDate' => '2013-01-13 17:01:07',
    'pageCount' => 0
);

$comparer = new RecursiveArrayCompare();
$comparer->setIgnoredKeys(array('id', 'ignored_key'));

var_dump($comparer->compare($arr1, $arr2)); // true
var_dump($comparer->compare($arr1, $arr3)); // false
var_dump($comparer->compare($arr1, $arr4)); // true
var_dump($comparer->compare($arr1, $arr5)); // false

EDIT

The benefit to using a separate class such as this is that it’s straight forward to unit test this class as well to ensure it behaves as expected. You don’t want to rely on tools for your tests if you can’t guarantee that they’re working properly.

Answer:

You could foreach the array elements

foreach ($array1 as $index => $subArray)
{
    $this->assertEquals($array1[$index]['title'], $array2[$index]['title');
    $this->assertEquals($array1[$index]['creationDate'], $array2[$index]['creationDate');
    $this->assertEquals($array1[$index]['pageCount'], $array2[$index]['pageCount');
}