Question

I'm trying to compute difference of two arrays of objects with array_udiff(). My object structure is complicated and I can not rely on quantitative properties, because those properties of objects in both arrays may have same values, but they are stored as different instances (it is expected behavior).

So, here is my question: is there any way to detect same instances in both arrays using reference detection?

What have I tried?

I've tried this:

<?php
header('Content-Type: text/plain; charset=utf-8');

$a = new stdClass;
$b = new stdClass;
$c = new stdClass;

$a->a = 123;
$b->b = 456;
$c->c = 789;

$x = [ $a, $c ];
$y = [ $b, $c ];

$func   = function(stdClass &$a, stdClass &$b){
    $replacer   = time();
    $buffer     = $a;

    $a = $replacer;

    $result = $a === $b;

    $a = $buffer;

    return $result;
};

$diff   = array_udiff($x, $y, $func);

print_r($diff);
?>

And got unsuccessful results, because if I try to replace value for $x element, php will not remove reference from $y.

I have same output for:

$func   = function(stdClass &$a, stdClass &$b){
    return $a === $b;
};

and for

$func   = function(stdClass &$a, stdClass &$b){
    return $a == $b;
};

It is an empty array.

Any suggestions?

Was it helpful?

Solution

Here's your problem, from the manual page:

The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.

You're returning a boolean (false), because you're comparing a timestamp to an object. (int) false is zero, so each and every object will be seen as equal, hence your $diff Array is empty.
Change your callback function to:

$func = function(stdClass $a, stdClass $b)
{//don't pass by reference, it's quite dangerous
    $replacer = time();
    $buffer = $a;
    $a = $replacer;
    $result = $a === $b ? 0 : -1;//equal? return zero!
    $a = $buffer;
    return $result;
};

That'll work, but $diff will now, obviously contain all objects. And your callback is a bit messy, consider:

$func = function(stdClass $a, stdClass $b)
{//don't pass by reference, it's quite dangerous
    return $a === $b ? 0 : -1;//compare objects
    //or, if you want to compare to time, still:
    return time() === $b ? 0 : -1;
};

That's a hell of a lot cleaner, and shorter, is it not?

Note
You will have to return -1 in case the two objects don't equate. Returning 1 in such cases implies that the object you're comparing is already greater than the values you're comparing it to. In that case, PHP will just stop looking, and will simply assume the value is not present in the array you're comparing the first array to... ok, this is getting rather complicated. Take your example:

[$a, $c] compare to [$b, $c]:
$a === $b => false: returns 1
    PHP assumes $a > $b, so $a > $c is implied, $a is pushed into $diff
$c === $b => false returns 1
   PHP assumes $c > $b, and is pushed to $diff, it's never compared to the next elem...

Returning -1 on false, however:

[$a, $c] compare to [$b, $c]:
$a === $b => false: returns -1
    PHP assumes $a < $b, so:
$a === $c => false: returns -1
    No more elems left, push $a to $diff
$c === $b => false returns -1
   PHP assumes $c < $b, moving on:
$c === $c => true returns 0
   Match found, $c is NOT pushed to $diff

OTHER TIPS

Despite, that I've accepted @Elias Van Ootegem answer, it will not help with any possible object combinations in diff array.

To detect references you may use === or !== comparison operators:

when using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.

So here is the function, which accepts two arrays, but might be improved to accept more:

function array_exclude_instances(array $source, array $excludes){
    foreach($source as $index => $current){
        foreach($excludes as $exclude){
            if($exclude !== $current)continue 1;

            unset($source[$index]);

            continue 2;
        }
    }

    return array_values($source);
}

Test:

$a = new stdClass;
$b = new stdClass;
$c = new stdClass;

$a->x = 123;
$b->x = 123;
$c->x = 456;

$x = [$a, $c];
$y = [$c, $b];

$result = array_exclude_instances($x, $y);

Results:

Array
(
    [0] => stdClass Object
        (
            [x] => 123
        )

)

Online test @ 3v4l.

what happens if you replace:

$func   = function(stdClass &$a, stdClass &$b)

to:

function func(stdClass &$a, stdClass &$b)

and call like this:

$diff   = array_udiff($x, $y, 'func');
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top