Reputation: 8096
I'm trying to find the elements of one array that are not in another array in PHP. I am under the impression that I should be using the array_udiff
function with a custom defined callback.
The first array is an array of objects with an id property. The second array is an array of objects with a name property. The name properties contain an ID number within the string that makes up their name.
My goal is to make sure each object's ID in the first array has a corresponding object in the second array that contains it's ID within the name. Here's my code:
<?php
class NameObj {
public $name;
function __construct($name){
$this->name = $name;
}
}
class IdObj{
public $id;
function __construct($id){
$this->id = $id;
}
}
$idArray = array(
new IdObj(1),
new IdObj(2),
new IdObj(3)
);
$nameArray = array(
new NameObj('1 - Object 1 Name'),
new NameObj('2 - Object 2 Name')
);
function custom_diff($oId, $oName){
$splitName = explode(' - ', $oName->name);
$idFromName = $splitName[0];
$id = $oId->id;
if($idFromName == $id) return 0;
return $idFromName > $id ? 1 : -1;
}
$missing_objects = array_udiff($idArray, $nameArray, 'custom_diff');
print_r($missing_objects);
?>
I would expect to see an array containing only the third object from the first array, but instead I get this:
PHP Notice: Undefined property: IdObj::$name in /home/ubuntu/test2.php on line 33
PHP Notice: Undefined property: IdObj::$name in /home/ubuntu/test2.php on line 33
PHP Notice: Undefined property: NameObj::$id in /home/ubuntu/test2.php on line 36
PHP Notice: Undefined property: IdObj::$name in /home/ubuntu/test2.php on line 33
PHP Notice: Undefined property: IdObj::$name in /home/ubuntu/test2.php on line 33
Array
(
[1] => IdObj Object
(
[id] => 2
)
[2] => IdObj Object
(
[id] => 3
)
)
What am I missing here? Am I using the array_udiff()
function incorrectly?
Upvotes: 1
Views: 238
Reputation: 47991
As mentioned in Jon's answer, the parameters defined in the callback of array_u*()
functions DO NOT relate directly to the passed-in array parameters. And how could they, given that these functions allow for more than 2 arrays to be passed into the function signature. This the reason that so many scripts use the ambiguous/meaningless variable names $a
and $b
-- because there is no way to be sure which array they came from.
For this reason, the callback function of array_u*()
functions must assume that each variable can contain the data from any supplied array. To disambiguate the variables and access the correct object property in this scenario, just check if the variable is an instance of one of the known classes, then access the correct property name and perform any required data preparation before implementing the three-way comparison (with the spaceship operator). Demo
class NameObj
{
function __construct(public string $name) {}
}
class IdObj
{
function __construct(public string $id) {}
}
$idArray = [
new IdObj(1),
new IdObj(2),
new IdObj(3)
];
$nameArray = [
new NameObj('1 - Object 1 Name'),
new NameObj('2 - Object 2 Name')
];
function custom_diff($a, $b)
{
return ($a instanceof IdObj ? $a->id : strstr($a->name, ' - ', true))
<=>
($b instanceof IdObj ? $b->id : strstr($b->name, ' - ', true));
}
var_dump(array_udiff($idArray, $nameArray, 'custom_diff'));
Output:
array(1) {
[2]=>
object(IdObj)#3 (1) {
["id"]=>
string(1) "3"
}
}
Upvotes: 0
Reputation: 437604
You are using it incorrectly. Specifically, you are assuming that it will always be called with an item from the first array and an item from the second array, in that order, as arguments. But PHP does not make any such guarantee.
Now you could certainly make your comparison detect the types of its arguments and act accordingly, but that's not the best idea because it's swimming against the current. array_udiff
is supposed to operate on similarly structured arrays, which these are not. Additionally, you don't really need to check each element of the first array against each element of the second to achieve your goal.
Here's how I would do it (borrowing a bit of your code):
$extractor = function($o) { return explode(' - ', $o->name)[0]; };
$idsFromNames = array_flip(array_map($extractor, $nameArray));
foreach ($idArray as $k => $o) {
if (!isset($idsFromNames[$o->id])) {
unset($idArray[$k]);
}
}
Upvotes: 1