user2315641
user2315641

Reputation: 171

Compare two arrays of objects by values that relate to differently-named properties

This is probably really simple, but I cannot seem to get it. I have two arrays of objects $a and $b. In $a I have objects with key email and in $b I have objects with user_email (this cannot be changed as it comes from an API). I want as output a third array $c that has all the objects where email == user_email. I've tried using array_udiff like this:

$c = array_udiff($a, $b,
    function ($obj_a, $obj_b) {
        return $obj_a->email - $obj_b->user_email;
    }
);

For some reason, $obj_b is not always an object from array $b as I would have thought. Is there any clean solution for this?

Upvotes: 0

Views: 101

Answers (2)

mickmackusa
mickmackusa

Reputation: 47971

Just like with usort() functions, the array_uintersect() and array_udiff() family of functions do not guarantee that the $a and $b parameters in the callback signature relate to the first and second input arrays. In fact, these functions allow for more than 2 input arrays, but the number of parameters in the callback signature do not increase.

To resolve the issue you are encountering, use the null coalescing operator to fallback to the opposing object's property if the first attempted property access fails.

Code: (Demo)

$emails = [
    (object) ['email' => 'a'],
    (object) ['email' => 'b'],
    (object) ['email' => 'c']
];
$userEmails = [
    (object) ['user_email' => 'c'],
    (object) ['user_email' => 'a'],
    (object) ['user_email' => 'd']
];

var_export(
    array_udiff(
        $emails,
        $userEmails,
        fn($a, $b) => ($a->email ?? $a->user_email) <=> ($b->email ?? $b->user_email)
    )
);

Result:

array (
  1 => 
  (object) array(
     'email' => 'b',
  ),
)

Upvotes: 0

Furgas
Furgas

Reputation: 2844

You are probably looking for array_uintersect. Also, you should compare your strings with strcmp or even better with strcasecmp. Remember that the order in which PHP will pass array elements to the callback is not always the same as the order of arrays.

$a = [(object)['email' => 'a'], (object)['email' => 'b'], (object)['email' => 'c']];
$b = [(object)['user_email' => 'c'], (object)['user_email' => 'a'], (object)['user_email' => 'd']];

$comparer = function($obj_a, $obj_b) {
    $email_a = property_exists($obj_a, 'email')
        ? $obj_a->email
        : $obj_a->user_email;

    $email_b = property_exists($obj_b, 'email')
        ? $obj_b->email
        : $obj_b->user_email;

    return strcasecmp($email_a, $email_b);
};

// only objects with email property
$c = array_uintersect($a, $b, $comparer);

// both objects with email and user_email property
$d = array_merge(
    array_uintersect($a, $b, $comparer),
    array_uintersect($b, $a, $comparer)
);

Testing with property_exists can be changed to testing with instanceof if the arguments are concrete classes.

Upvotes: 1

Related Questions