Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 888
Lorenzo Polidori Avatar asked Jan 15 '13 09:01

Lorenzo Polidori


People also ask

How can we check whether both arrays are same or not in PHP?

The array_diff() function compares the values of two (or more) arrays, and returns the differences. This function compares the values of two (or more) arrays, and return an array that contains the entries from array1 that are not present in array2 or array3, etc.

How can I find matching values in two arrays in PHP?

The array_intersect() function compares the values of two (or more) arrays, and returns the matches. This function compares the values of two or more arrays, and return an array that contains the entries from array1 that are present in array2, array3, etc.

What is use of In_array () function in PHP?

PHP in_array() Function The in_array() function searches an array for a specific value. Note: If the search parameter is a string and the type parameter is set to TRUE, the search is case-sensitive.


1 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.

like image 159
RobMasters Avatar answered Oct 08 '22 01:10

RobMasters