Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why array_unique does not detect duplicate objects?

Tags:

php

I can't seem to figure out what magic is happening behind the PHP scene and why array_unique cannot detect my duplicates.

In my specific situation, I have 2 collections of users, which I am merging into one and then keeping only unique entries. For that I am converting both collections into arrays, array_merge() them and then based on parameter apply array_unique(..., SORT_REGULAR) so that they are compared as objects without any conversions. I realise that comparing objects is a slippery slope, but in this case it's weirder than I though.

After merge but before the uniqueness check I have this state: enter image description here

As you can see, items 4 and 11 are the same User entity (both non-strict and strict comparison agree on that). Yet after array_unique() they both remain in the list for some reason: enter image description here

As you can see, items 7-10 were detected and removed, but 11 wasn't.

How is that possible? What am I not seeing here?

Currently running PHP 7.4.5

Code is from project using Symfony 4.4.7 and Doctrine ORM 2.7.2 (although I think this should be irrelevant, if the objects are equal both by == and === comparisons).

Fun fact for bonus points - applying array_unique twice in a row gives actually unique results: enter image description here

Mind = blown

UPDATE: I have added throw new \RuntimeException() in my User::__toString() method, to be extra sure noone is doing conversion to string.

Please do not suggest converting to string - that is neither a solution to my problem, nor what this question is about.

like image 537
mkilmanas Avatar asked May 20 '20 10:05

mkilmanas


People also ask

How to remove duplicates from PHP array?

The array_unique() function removes duplicate values from an array. If two or more array values are the same, the first appearance will be kept and the other will be removed. Note: The returned array will keep the first array item's key type.

How to check duplicates in array PHP?

The array_unique() is a built-in function in PHP and this function removes duplicate values from an array. If there are multiple elements in the array with same values then the first appearing element will be kept and all other occurrences of this element will be removed from the array.

How to get unique values from two arrays in PHP?

The array_diff() (manual) function can be used to find the difference between two arrays: $array1 = array(10, 20, 40, 80); $array2 = array(10, 20, 100, 200); $diff = array_diff($array1, $array2); // $diff = array(40, 80, 100, 200);


1 Answers

For your issue at hand, I am really suspecting this is coming from the way array_unique is removing elements out of the array, when using the SORT_REGULAR flag, by:

  1. sorting it
  2. removing adjacent items if they are equal

And because you do have a Proxy object in the middle of your User collection, this might cause you the issue you are currently facing.

This seems to be backed up by the warning of the sort page of PHP documentation, as pointed out be Marvin's comment.

Warning Be careful when sorting arrays with mixed types values because sort() can produce unexpected results, if sort_flags is SORT_REGULAR.

Source: https://www.php.net/manual/en/function.sort.php#refsect1-function.sort-notes


Now for a possible solution, this might get you something more Symfony flavoured.

It uses the ArrayCollection filter and contains methods in order to filter the second collection and only add the elements not present already in the first collection.
And to be fully complete, this solution is also making use of the use language construct in order to pass the second ArrayCollection to the closure function needed by filter.

This will result in a new ArrayCollection containing no duplicated user.

public static function merge(Collection $a, Collection $b, bool $unique = false): Collection {
  if($unique){
    return new ArrayCollection(
      array_merge(
        $a->toArray(),
        $b->filter(function($item) use ($a){
          return !$a->contains($item);
        })->toArray()
      )
    );
  }

  return new ArrayCollection(array_merge($a->toArray(), $b->toArray()));
}
like image 51
β.εηοιτ.βε Avatar answered Sep 23 '22 07:09

β.εηοιτ.βε