PHP函数检查两个数组是否相同而忽略指定键的值

时间:2021-10-17 14:04:32

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

我需要一个PHP函数,可以断言两个数组是相同的,同时忽略指定键集的值(只有值,键必须匹配)。

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.

如果我们忽略密钥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:

因为我需要它进行测试,所以我提出了以下扩展PHPUnit_Framework_TestCase并使用其断言函数的类:

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.

doAssertArraysSame遍历其中一个数组,并递归断言两个数组具有相同的键。它还会检查它们是否具有相同的值,除非当前键位于要忽略的键列表中。

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?

我的问题是:有没有更好或更简单的方法来完成这项任务,也许使用一些已经可用的PHP / PHPUnit函数?

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.

编辑:请记住我不一定需要PHPUnit的解决方案,如果有一个普通的PHP函数可以做到这一点,我可以在我的测试中使用它。

2 个解决方案

#1


5  

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.

使用诸如此类的单独类的好处是,它可以直接对此类进行单元测试,以确保其行为符合预期。如果您不能保证它们正常工作,您不希望依赖于测试工具。

#2


0  

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');
}   

#1


5  

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.

使用诸如此类的单独类的好处是,它可以直接对此类进行单元测试,以确保其行为符合预期。如果您不能保证它们正常工作,您不希望依赖于测试工具。

#2


0  

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');
}