107

Is there any method like the array_unique for objects? I have a bunch of arrays with 'Role' objects that I merge, and then I want to take out the duplicates :)

BenMorel
  • 34,448
  • 50
  • 182
  • 322

15 Answers15

191

array_unique works with an array of objects using SORT_REGULAR:

class MyClass {
    public $prop;
}

$foo = new MyClass();
$foo->prop = 'test1';

$bar = $foo;

$bam = new MyClass();
$bam->prop = 'test2';

$test = array($foo, $bar, $bam);

print_r(array_unique($test, SORT_REGULAR));

Will print:

Array (
    [0] => MyClass Object
        (
            [prop] => test1
        )

    [2] => MyClass Object
        (
            [prop] => test2
        )
)

See it in action here: http://3v4l.org/VvonH#v529

Warning: it will use the "==" comparison, not the strict comparison ("===").

So if you want to remove duplicates inside an array of objects, beware that it will compare each object properties, not compare object identity (instance).

Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
  • 12
    This answer is much better then the accepted answer. However the example does not show the difference between the comparison on value (`==`) or identity (`===`) because of `$bam->prop = 'test2';` (should be `'test1'` to showcase the difference). See http://codepad.viper-7.com/8NxWhG for an example. – Flip May 19 '14 at 09:03
  • @vishal have a look at the official documentation: http://php.net/manual/en/function.array-unique.php – Matthieu Napoli Nov 05 '14 at 22:21
  • 1
    Should be the accepted answer. Also this a lot faster than using __toString(). See: http://sandbox.onlinephpfunctions.com/code/21152125900b0c139dce9a144c9842056b0cd1d8 for comparison result. – LucaM Apr 07 '16 at 08:17
  • 2
    Watch out comparing objects with array_unique(), the function will apply a deep comparison, that may result in your server crashing if it involves too much recursion—I'm looking at you Doctrine entities. Better identify what makes your object unique and index your objects with it. For instance, if your objects have a string identifier, build an array with that identifier as key. – olvlvl Jan 06 '17 at 14:27
  • Will this work on fields using all modifier types(`private, protected, public`)? – james Jun 05 '17 at 18:18
  • array_unique( array_merge($array1, $array2), SORT_REGULAR); that magic SORT_REGULAR does this perfectly. That alone should be marked as the correct answer, so many answers describe complex methods of filtering or adding functions most of which don't work as described. Kudos for this. – Ian Tearle Apr 27 '23 at 21:43
109

Well, array_unique() compares the string value of the elements:

Note: Two elements are considered equal if and only if (string) $elem1 === (string) $elem2 i.e. when the string representation is the same, the first element will be used.

So make sure to implement the __toString() method in your class and that it outputs the same value for equal roles, e.g.

class Role {
    private $name;

    //.....

    public function __toString() {
        return $this->name;
    }

}

This would consider two roles as equal if they have the same name.

shakaran
  • 10,612
  • 2
  • 29
  • 46
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 2
    @Jacob because neither `array_unique` nor `__toString()` compare anything. `__toString()` defines how an object instance is supposed to behave when used in a string context and `array_unique` returns the input array with duplicate values removed. It just *uses* comparison for this internally. – Gordon Mar 11 '10 at 16:13
  • 1
    @Jacob Relkin: It isn't comparator. It is the string representation of the object. I think they use this as you can convert any type, object, etc. into a string. But the string method itself on an object is not only used by this function. E.g. `echo $object` also uses the `__toString` method. – Felix Kling Mar 11 '10 at 16:16
  • 4
    Adding `__toString()` methods to all your objects is much more painful then just adding a `SORT_REGULAR` flag to array_unique, see Matthieu Napoli his answer. Besides a `__toString()` method has many other use cases then being used for object comparison, so this might not even be possible. – Flip May 19 '14 at 09:06
36

This answer uses in_array() since the nature of comparing objects in PHP 5 allows us to do so. Making use of this object comparison behaviour requires that the array only contain objects, but that appears to be the case here.

$merged = array_merge($arr, $arr2);
$final  = array();

foreach ($merged as $current) {
    if ( ! in_array($current, $final)) {
        $final[] = $current;
    }
}

var_dump($final);
Nathan Arthur
  • 8,287
  • 7
  • 55
  • 80
salathe
  • 51,324
  • 12
  • 104
  • 132
  • 1
    Works nice, it might be faster than the other one (dont really know) but i will use yours because i dont have to take make an extra function for it :D – Gigala Jan 15 '13 at 13:14
  • When comparing objects they have to have the same amount of fields and have to be identical key/value pairs to be considered the same correct? what im getting at is....if i have 2 objects and one of them has one extra field will those object not be considered "the same" – ChuckKelly Sep 18 '13 at 21:29
  • 2
    `in_array` should use the `$strict` parameter! Else you you comparing objects using "==" instead of "===". More here: http://fr2.php.net/manual/fr/function.in-array.php – Matthieu Napoli Mar 14 '14 at 11:11
  • 2
    *Not* using the strict parameter was a deliberate choice here. I wanted to find "equal" objects, not necessarily *the same instance* of an object. This is explained in the link mentioned in the answer, which says, "*When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values, and are instances of the same class.*" – salathe Mar 14 '14 at 15:35
17

Here is a way to remove duplicated objects in an array:

<?php
// Here is the array that you want to clean of duplicate elements.
$array = getLotsOfObjects();

// Create a temporary array that will not contain any duplicate elements
$new = array();

// Loop through all elements. serialize() is a string that will contain all properties
// of the object and thus two objects with the same contents will have the same
// serialized string. When a new element is added to the $new array that has the same
// serialized value as the current one, then the old value will be overridden.
foreach($array as $value) {
    $new[serialize($value)] = $value;
}

// Now $array contains all objects just once with their serialized version as string.
// We don't care about the serialized version and just extract the values.
$array = array_values($new);
Blackhole
  • 20,129
  • 7
  • 70
  • 68
yankee
  • 38,872
  • 15
  • 103
  • 162
  • This is for me the best solution! I'm using this solution for my website searchengine ( merge 2 queryresults from a database). First I have the resuls for all the searchterms, and I merge them with the results of some of the searchterms. With this solution I have the most important results first, added by unique other solutions.. – Finduilas Jul 30 '19 at 10:41
13

You can also serialize first:

$unique = array_map( 'unserialize', array_unique( array_map( 'serialize', $array ) ) );

As of PHP 5.2.9 you can just use optional sort_flag SORT_REGULAR:

$unique = array_unique( $array, SORT_REGULAR );
Littm
  • 4,923
  • 4
  • 30
  • 38
Remon
  • 159
  • 1
  • 4
12

You can also use they array_filter function, if you want to filter objects based on a specific attribute:

//filter duplicate objects
$collection = array_filter($collection, function($obj)
{
    static $idList = array();
    if(in_array($obj->getId(),$idList)) {
        return false;
    }
    $idList []= $obj->getId();
    return true;
});
Arvid Vermote
  • 433
  • 5
  • 6
6

From here: http://php.net/manual/en/function.array-unique.php#75307

This one would work with objects and arrays also.

<?php
function my_array_unique($array, $keep_key_assoc = false)
{
    $duplicate_keys = array();
    $tmp         = array();       

    foreach ($array as $key=>$val)
    {
        // convert objects to arrays, in_array() does not support objects
        if (is_object($val))
            $val = (array)$val;

        if (!in_array($val, $tmp))
            $tmp[] = $val;
        else
            $duplicate_keys[] = $key;
    }

    foreach ($duplicate_keys as $key)
        unset($array[$key]);

    return $keep_key_assoc ? $array : array_values($array);
}
?>
Silver Light
  • 44,202
  • 36
  • 123
  • 164
2

If you have an indexed array of objects, and you want to remove duplicates by comparing a specific property in each object, a function like the remove_duplicate_models() one below can be used.

class Car {
    private $model;

    public function __construct( $model ) {
        $this->model = $model;
    }

    public function get_model() {
        return $this->model;
    }
}

$cars = [
    new Car('Mustang'),
    new Car('F-150'),
    new Car('Mustang'),
    new Car('Taurus'),
];

function remove_duplicate_models( $cars ) {
    $models = array_map( function( $car ) {
        return $car->get_model();
    }, $cars );

    $unique_models = array_unique( $models );

    return array_values( array_intersect_key( $cars, $unique_models ) );
}

print_r( remove_duplicate_models( $cars ) );

The result is:

Array
(
    [0] => Car Object
        (
            [model:Car:private] => Mustang
        )

    [1] => Car Object
        (
            [model:Car:private] => F-150
        )

    [2] => Car Object
        (
            [model:Car:private] => Taurus
        )

)
1

sane and fast way if you need to filter duplicated instances (i.e. "===" comparison) out of array and:

  • you are sure what array holds only objects
  • you dont need keys preserved

is:

//sample data
$o1 = new stdClass;
$o2 = new stdClass;
$arr = [$o1,$o1,$o2];

//algorithm
$unique = [];
foreach($arr as $o){
  $unique[spl_object_hash($o)]=$o;
}
$unique = array_values($unique);//optional - use if you want integer keys on output
Roman Bulgakov
  • 174
  • 2
  • 7
1

This is very simple solution:

$ids = array();

foreach ($relate->posts as $key => $value) {
  if (!empty($ids[$value->ID])) { unset($relate->posts[$key]); }
  else{ $ids[$value->ID] = 1; }
}
1

You can also make the array unique using a callback function (e.g. if you want to compare a property of the object or whatever method).

This is the generic function I use for this purpose:

/**
* Remove duplicate elements from an array by comparison callback.
*
* @param array $array : An array to eliminate duplicates by callback
* @param callable $callback : Callback accepting an array element returning the value to compare.
* @param bool $preserveKeys : Add true if the keys should be perserved (note that if duplicates eliminated the first key is used).
* @return array: An array unique by the given callback
*/
function unique(array $array, callable $callback, bool $preserveKeys = false): array
{
    $unique = array_intersect_key($array, array_unique(array_map($callback, $array)));
    return ($preserveKeys) ? $unique : array_values($unique);
}

Sample usage:

$myUniqueArray = unique($arrayToFilter,
    static function (ExamQuestion $examQuestion) {
        return $examQuestion->getId();
    }
);
Blackbam
  • 17,496
  • 26
  • 97
  • 150
0

array_unique version for strict (===) comparison, preserving keys:

function array_unique_strict(array $array): array {
    $result = [];
    foreach ($array as $key => $item) {
        if (!in_array($item, $result, true)) {
            $result[$key] = $item;
        }
    }
    return $result;
}

Usage:

class Foo {}
$foo1 = new Foo();
$foo2 = new Foo();
array_unique_strict( ['a' => $foo1, 'b' => $foo1, 'c' => $foo2] ); // ['a' => $foo1, 'c' => $foo2]
hejdav
  • 1,267
  • 15
  • 19
-1

This is my way of comparing objects with simple properties, and at the same time receiving a unique collection:

class Role {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

$roles = [
    new Role('foo'),
    new Role('bar'),
    new Role('foo'),
    new Role('bar'),
    new Role('foo'),
    new Role('bar'),
];

$roles = array_map(function (Role $role) {
    return ['key' => $role->getName(), 'val' => $role];
}, $roles);

$roles = array_column($roles, 'val', 'key');

var_dump($roles);

Will output:

array (size=2)
  'foo' => 
    object(Role)[1165]
      private 'name' => string 'foo' (length=3)
  'bar' => 
    object(Role)[1166]
      private 'name' => string 'bar' (length=3)
Gander
  • 1,854
  • 1
  • 23
  • 30
-1

array_unique works by casting the elements to a string and doing a comparison. Unless your objects uniquely cast to strings, then they won't work with array_unique.

Instead, implement a stateful comparison function for your objects and use array_filter to throw out things the function has already seen.

jricher
  • 2,663
  • 1
  • 18
  • 17
-1

If you have array of objects and you want to filter this collection to remove all duplicates you can use array_filter with anonymous function:

$myArrayOfObjects = $myCustomService->getArrayOfObjects();

// This is temporary array
$tmp = [];
$arrayWithoutDuplicates = array_filter($myArrayOfObjects, function ($object) use (&$tmp) {
    if (!in_array($object->getUniqueValue(), $tmp)) {
        $tmp[] = $object->getUniqueValue();
        return true;
    }
    return false;
});

Important: Remember that you must pass $tmp array as reference to you filter callback function otherwise it will not work

Krzysztof Raciniewski
  • 4,735
  • 3
  • 21
  • 42