How to make an object array have only unique values?

Asked

Viewed 1,152 times

2

If I have a collection with the same object in one array, how could I make this collection with only objects of unique instances?

Example:

$a = new stdClass;

$a->id = 1;

$b = $a; // mesmo objeto

$c = new stdClass;

$c->id = 2;


$colecao = [];

$colecao[] = $a;
$colecao[] = $b;
$colecao[] = $c;

I want to return only objects that are not the same (because $a in this case you have the same hash (internal id in php) as $b).

If I try to do this, I’ll have a mistake:

$unicos = array_unique($colecao);

Object of class stdClass could not be converted to string

It seems that array_unique only for values of the type scalar.

How could it cause the above result to be similar below?

[$a, $c]
  • I can already predict answers with spl_object_hash and SplObjectStorage :) :) :) :)

3 answers

5

Try it like this:

$unicos = array_unique($colecao, SORT_REGULAR);

The function array_unique() takes as arguments two parameters (the second parameter is optional):

array array_unique ( array $array [, int $sort_flags = SORT_STRING ] )

array: The array you want to order.

sort_flags: this parameter is optional but important because it allows changing the default behavior of the function. It can take one of the following values:

  • SORT_REGULAR - "normal" comparison (without type changes)
  • SORT_NUMERIC - numerical comparison
  • SORT_STRING - compares items as strings (default option)
  • SORT_LOCALE_STRING - compares items as strings (LOCALE dependent)

By default, the function will try to convert objects to string to perform the comparison, hence you see the message:

Object of class stdClass could not be converted to string

You can check in action here: http://3v4l.org/3O810

  • Almost this, but he removed the "different instances". You know that PHP automatically considers passing by reference an instance assignment to an object. Therefore, it should return 2 elements, not 1. But I gave +1 because the answer was interesting (only lacks explanations)

  • And the English php manual says nothing array_unique accepts a second parameter, but it worked here in my test!

  • I included in the answer a "fiddle". It seems to be working because it returns two elements. You can check?

  • That’s because objects have different values. But if I had two instances of the same object, I would have problems if they had the same values. See the answer I gave and you will understand what I meant by "collection of objects of the same instance, but only instances - excluding references"

2

I’m sure the @Runo response is better, but as I already did I’ll post it anyway, for didactics.

You could create a function:

function ClearArray($arr, $KeepKey = FALSE){
    if (!is_array($arr)) return FALSE; // Se não for array retorna FALSE
    if (count($arr) == 0) return $arr; // Se estiver vazio retorna-o
    $narr = Array(); // Array de Retorno
    $c = 0; // Novos indices
    foreach($arr as $k => $v){
        // Se mantém o índice original usa-o senão usa o novo
        $k = ($KeepKey ? $k : $c);          
        $keep = TRUE; // Mantém o item

        foreach($narr as $k2 => $v2){ // Percorre o novo array
            // Verifica se já foi adicionado um item igual
            if ($v === $v2){
                $keep = FALSE; // Não mantém o registro
                break;
            }

        }

        if($keep){ // Se mantém o item
            $narr[$k] = $v; // Adiciona-o no novo array
            $c++;
        }

    }

    return $narr; // Retorna
}

Use:

$a = new stdClass;
$a->id = 1;

$b = $a; // mesmo objeto

$c = new stdClass;
$c->id = 2;

$colecao = Array();

$colecao[] = $a;
$colecao[] = $b;
$colecao[] = $c;

// Array Original
var_dump($colecao);

// Array com itens unicos
$arr = ClearArray($colecao, true);

echo '-------------------------'.PHP_EOL;

var_dump($arr);
$unicos = array_unique($colecao, SORT_REGULAR);

Exit:

array(3) {
  [0]=>
  object(stdClass)#1 (1) {
    ["id"]=>
    int(1)
  }
  [1]=>
  object(stdClass)#1 (1) {
    ["id"]=>
    int(1)
  }
  [2]=>
  object(stdClass)#2 (1) {
    ["id"]=>
    int(2)
  }
}
-------------------------
array(2) {
  [0]=>
  object(stdClass)#1 (1) {
    ["id"]=>
    int(1)
  }
  [2]=>
  object(stdClass)#2 (1) {
    ["id"]=>
    int(2)
  }
}

0


I would like to thank you all for your answers, but I shall give you my reply in order to clarify.

How objects work in PHP?

First, let’s reinforce some terms:

Object is an instance of a class.

Second, let’s clarify something: in PHP, I can have two instances of stdClass ($a and $b), but if I match $c to $a, the two will be the same object working in the same instance (which is changed in $c implies change in $a).

That is to say:

$a, $b and $c are instances of stdClass, however $c and $a is the same object, since $c refers to $a - in php the assigned objects are automatically passed by reference, according to the manual.

Suppose the following scenario:

$a = new stdClass;

$b = new stdClass;

$c = $a;

That:

[$a, $b, $c]

It would be the same thing as that:

[$a, $b, $a]

For $c become a new instance of stdClass, independent of the instance held in $a, we’d have to use the keyword clone.

Thus:

$c = clone $a

Splobjectstorage - The Solution

So, to return an array of objects with unique instances (instances of the same object, but not repeating these objects), we could do so:

$a = new stdClass;

$a->id = 1;

$b = $a;

$b->id = 2;

$c = new stdClass;

$c->id = 3;

$storage = new SplObjectStorage();

$storage->attach($a);
$storage->attach($b);
$storage->attach($c);

print_r(iterator_to_array($storage));

The result would be:

Array
(
    [0] => stdClass Object
        (
            [id] => 2
        )

    [1] => stdClass Object
        (
            [id] => 3
        )

)

Note that the id which was 1, became 2.

To understand what I say, see what happens in this example:

$a = new stdClass;

$a->id = 1;

$b = $a;

$b->id = 2;

var_dump($a->id, $b->id); // int 2 e int 2

http://3v4l.org/nCG4J

Because it happened?

Because, internally, the SplObjectStorage, uses the object hash as the index to assign it to a array. This is done through a function called spl_object_hash:

Take this example:

var_dump(spl_object_hash($a));
var_dump(spl_object_hash($b));
var_dump(spl_object_hash($c));

var_dump(spl_object_hash($a) === spl_object_hash($b));

var_dump(spl_object_hash($a) === spl_object_hash($c));

var_dump(spl_object_hash($b) === spl_object_hash($c));

The result is:

string '0000000076e70b6400007fac140b77fd' (length=32)

string '0000000076e70b6400007fac140b77fd' (length=32)

string '0000000076e70b6500007fac140b77fd' (length=32)

boolean true

boolean false

boolean false

This makes it clear that $c and $a, are the same. Then I leave my small contribution. Thank you for the answers!

Why not array_unique?

Because he doesn’t use the same rule of "oneness" as the SplObjectStorage uses.

Take this example:

$a = new stdClass;

$a->id = 2;

$b = new stdClass;

$b->id = 2;

$case1 = array_unique([$a, $b], SORT_REGULAR);

$storage = new SplObjectStorage();

$storage->attach($a);
$storage->attach($b);

$case2 = iterator_to_array($storage);


var_dump($case1, $case2);

See the results:

#array_unique
   array (size=1)
      0 => 
        object(stdClass)[6]
          public 'id' => int 2
#SplObjectStorage com iterator_to_array
    array (size=2)
      0 => 
        object(stdClass)[6]
          public 'id' => int 2
      1 => 
        object(stdClass)[7]
          public 'id' => int 2

Example: http://3v4l.org/hQKL5

It seems that the array_unique took into consideration the fact that id have the same value in both cases, however, as stated earlier, SplObjectStorage took into account the internal identification of the object.

If I’m wrong about something, you can fix me :). But that’s what I mean by objects in PHP!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.