phparraysfilterassociative-arrayarray-difference

array_diff() incorrectly removes matching values despite keys not matching


I was trying to filter date from a form down to only what a user has changed, and started using array_filter as is it seemed to do exactly what I wanted. I tested a few forms and ran into this unexpected behavior. When the "new" value is 1, it is not detected by array_diff. Also unexpected when running this on 3v4l.org, was that the foreach loop was actually faster than array_filter while returning the expected result. I've read the man-page for the functions and understand that it does a string comparison, but all array values are strings to start with so I wouldn't expect it to be a type conversion issue.

I've solved my initial issue and will gladly use the faster foreach loop, but I am interested if anyone can explain why this works this way.

https://3v4l.org/1JggJ

<?php

$array_1 = [
    'id' => '42',
    'base_id' => '23',
    'role_id' => '1',
    'title' => 'Manage Account',
    'slug' => 'manage_account',
    'parent' => '31',
    'order' => '1',
    'visibility' => '1'
];
$array_2 = [
    'id' => '42',
    'base_id' => '23',
    'role_id' => '99999',
    'title' => 'Manage Account',
    'slug' => 'manage_account',
    'parent' => '31',
    'order' => '1',
    'visibility' => '1'
];

var_dump(array_diff($array_1, $array_2));
// Result (unexpected)
// array (size=0)
//   empty        

$diff = [];
foreach ($array_1 as $key => $value) {
    if ((string) $array_1[$key] !== (string) $array_2[$key]) {
        $diff[$key] = $value;
    }
}

var_dump($diff);
// Result (expected)
// array (size=1)
//   'role_id' => string '1' (length=1)

Solution

  • array_diff() looks for exact duplicates for each value of array 1 in array 2, ignoring the keys.

    1 has a duplicate in array 2, e.g. under key order. That's why it's not listed as difference.

    Whether or not this behavior is optimal or obvious is debatable, but that's how it works.


    If you change the 1 to a 3, it will be reported, as array 2 does not contain a value 3:

        $array_1 = [
            'id' => '42',
            'base_id' => '23',
            'role_id' => '3',
            'title' => 'Manage Account',
            'slug' => 'manage_account',
            'parent' => '31',
            'order' => '1',
            'visibility' => '1'
        ];
        $array_2 = [
            'id' => '42',
            'base_id' => '23',
            'role_id' => '99999',
            'title' => 'Manage Account',
            'slug' => 'manage_account',
            'parent' => '31',
            'order' => '1',
            'visibility' => '1'
        ];
    
        var_dump(array_diff($array_1, $array_2));
        // Result (unexpected)
        // array (size=1)
        //   'role_id' => string '3' (length=1)  
    

    If you want the keys to be taken into account, use array_diff_assoc() instead.