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.
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.
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.
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With