【问题标题】:PHP function to check that two arrays are the same while ignoring the values of specified keysPHP函数检查两个数组是否相同,同时忽略指定键的值
【发布时间】:2013-01-15 09:51:23
【问题描述】:

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

实际上,数组必须具有相同的结构,但有些值可以忽略。

例如,考虑以下两个数组:

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

如果我们忽略键 id 的值,它们是相同的。

我也想考虑嵌套数组的可能性:

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

因为我需要它进行测试,所以我提出了以下扩展 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 遍历其中一个数组并递归断言这两个数组具有相同的键。它还会检查它们是否具有相同的值,除非当前键在要忽略的键列表中。

为确保两个数组具有完全相同数量的元素,在迭代期间删除每个元素,并且在循环结束时,函数检查两个数组是否为空。

用法:

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

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

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

【问题讨论】:

  • array_diff 怎么样?如果返回空数组则表示数组所在的地方相同。
  • @PLB 是的,这可能会有所帮助,但array_diff 只检查 n 维数组的一维,这意味着如果我想的话,无论如何我都必须在递归循环中使用它支持嵌套数组。另外,在我的情况下,如果来自array_diff 的结果数组不为空,我将不得不检查该数组的键是否在忽略列表中。
  • 你真的在写测试吗?或者你在代码中需要这个?我的意思是,您的问题意味着您“只”需要该功能,但是您不应该使用 testcase/assert 类和功能。您是否检查了 array_diff 手册页上的 cmets?多维示例在某处.. php.net/manual/en/function.array-diff.php
  • @Nanne 我需要它进行测试,但我认为这无关紧要。如果有一个很好的通用函数可以开箱即用,我可以在我的 TestCase 类中使用它,这就是我要求一个函数的原因。这个问题不一定与 PHPUnit 相关。关于array_diff,请看我之前的评论。手册页中提出的所有多维解决方案都很笨拙、效率低下或仅适用于二维数组。
  • @testing:我同意,我的意思是相反的:如果你测试,那么使用你正在使用的类会很奇怪:)。最后,比较多个提及的数组将需要某种形式的深度。我不确定您是否可以提高效率。底线是您需要测试(嵌套)数组的所有键/值对。我不确定你是否可以不尴尬。

标签: php arrays phpunit


【解决方案1】:

我不确定这是否比您已经在使用的解决方案更好,但在我有这个确切需求之前,我使用过类似的课程。它能够给您一个简单的真假响应,并且不与测试框架耦合,这对您来说可能是好事,也可能不是好事。

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

还有一些基于您提供的数组的示例:

$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

编辑

使用这样一个单独的类的好处是可以直接对这个类进行单元测试,以确保它的行为符合预期。如果您不能保证工具正常工作,您就不想依赖工具进行测试。

【讨论】:

  • 我喜欢。就我而言,我只会在 if (in_array($key, $this->ignoredKeys)) { continue; } 之前移动 if (!array_key_exists($key, $b)) { return false; } 以确保两个数组具有完全相同的键 - 我只想忽略元素的值,而不是键。
  • 好吧。您可能还想删除 array_diff 部分然后只返回 empty($b) ,因为听起来您希望其他键即使被忽略也有所不同。
  • 没错,return empty($b);
  • 还有一件事:(in_array($key, $this->ignoredKeys, true)) 对密钥进行严格检查(否则 0 的值可能会误报)。
  • 要在上面的评论中添加更多详细信息:查看in_array手册页上的第一条评论:php.net/manual/en/function.in-array.php
【解决方案2】:

你可以 foreach 数组元素

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 2014-02-03
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    • 2019-11-25
    相关资源
    最近更新 更多