4

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)
NikiC
  • 100,734
  • 37
  • 191
  • 225
Rockstar04
  • 411
  • 5
  • 16
  • 2
    FYI, `array_diff_assoc()` seems to work correctly. – AbraCadaver Jan 29 '18 at 21:01
  • the value "1" is in both arrays, if you change array 1 to have `'role_id' => "99999"`, then you get what you expected –  Jan 29 '18 at 21:07
  • 1
    `array_diff()` only looks at the *values* in the arrays. As an example, when looking at the `role_id` (value: `1`) of the first array, the function can see equal values (in keys `order` and `visibility`) in the second array, so there's no difference. On the other hand, `array_diff_assoc()` takes both the value *and key* into consideration. (e.g. [an example with your arrays](https://3v4l.org/MgKEX).) – salathe Jan 29 '18 at 21:08
  • You can also use `array_diff($array_2, $array_1)` To see the `99999`. – AbraCadaver Jan 29 '18 at 21:09
  • @AbraCadaver not if the changed value in `$array_2` appears anywhere in `$array_1`, for the same reason as above. – salathe Jan 29 '18 at 21:12
  • @salathe: Well it doesn't in this case, that's why I said that. – AbraCadaver Jan 29 '18 at 21:15
  • A good read about [the different `array_*diff_*()` functions in PHP](https://stackoverflow.com/a/70817582/2943403). – mickmackusa Feb 22 '22 at 22:45

1 Answers1

5

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.

TimoStaudinger
  • 41,396
  • 16
  • 88
  • 94