0

I am calling array_uintersect_assoc() with a callback function of strpbrk() to demonstrate a point about how different return values are evaluated based on type.

Given an associative array of haystacks like:

[
    'one' => '0',
    'two' => '1',
    'three' => '0',
    'four' => '1',
    'five' => 'a',
    'six' => 'b',
    'seven' => 'a',
    'eight' => 'b'
]

And an associative array of needles like:

[
    'one' => '1',
    'two' => '0',
    'three' => '0',
    'four' => '1',
    'five' => 'a',
    'six' => 'b',
    'seven' => 'a'
    'eight' => 'b',
]

Code:

var_export(array_uintersect_assoc($array1, $array2, 'strpbrk'));

Output:

['four' => '1']

Because returned string values a and b are truthy, why aren't elements with keys seven and eight included in the results?

Related array_uintersect_assoc() pages that do not specifically address this concern:

mickmackusa
  • 43,625
  • 12
  • 83
  • 136

1 Answers1

1

The PHP Manual states that the return value will be evaluated as an int type value not a boolean type value.

callback(mixed $a, mixed $b): int

This native function is designed to expect the return value from a three-way comparison. This means that elements that return zero values are retained and everything else is removed.

At the comparison of the seven-keyed values, (strpbrk('a', 'a')) the returned value is string value a. (The following explanation will apply for the comparison of the eight-keyed values.) While it is correct that a is considered "truthy" because when it is cast as a boolean value, it becomes true. It is also correct that a boolean true value when cast to an int value will become 1. However, when a is directly cast as an int value, it becomes 0 -- this is how array_unintersect_assoc() is evaluating the callback's return value and this explains why elements seven and eight are not retained in the result array. (Comprehensive demo of this task and the below snippet)

var_export([
    // haystack, then needle
    '0:1' => strpbrk('0', '1'), // false
    '1:0' => strpbrk('1', '0'), // false
    '0:0' => strpbrk('0', '0'), // '0'
    '1:1' => strpbrk('1', '1'), // '1'
    'a:b' => strpbrk('a', 'b'), // false
    'b:a' => strpbrk('b', 'a'), // false
    'a:a' => strpbrk('a', 'a'), // 'a'
    'b:b' => strpbrk('b', 'b'), // 'b'
    'a as bool' => (bool)'a', // true
    'a as int' => (int)'a', // 0
    'a as bool then int' => (int)(bool)'a', // 1
]);

Because so many snippets that demonstrate array_uintersect_assoc() (including the manual) all use strcasecmp() as the callback, I'll offer a few other callbacks to provide more context to researchers.

Ultimately, it is not enough to understand how different variable types are juggled to booleans. It is imperative that developers understand how different value types convert to ints.

It is a little bit awkward/conterintuitive to retain values that return a zero integer -- because zero is a falsey value. For anyone using str_contains(), str_starts_with(), str_ends_with(), etc. that return a boolean result, you will need to invert the native function from uintersect to udiff so that the true boolean return value is handled correctly.

strcmp()

Code: (Breakdown Demo)

var_export(
    array_uintersect_assoc([
        ['one' => 'a', 'two' => 'aa', 'three' => 'a', 'four' => 'aa'], // string1s
        ['one' => 'aa', 'two' => 'a', 'three' => 'a', 'four' => 'aa'], // string2s
        'strcmp'
    ])
);
// ['three' => 'a', 'four' => 'aa']

trim()

Code: (Breakdown Demo)

var_export(
    array_uintersect_assoc([
        ['one' => 'a', 'two' => 'ab', 'three' => '0', 'four' => '1'], // strings
        ['one' => 'ab', 'two' => 'a', 'three' => '1', 'four' => '0'], // masks
        'trim'
    ])
);
// ['one' => 'a', 'two' => 'ab', 'three' => '0']

strcspn()

Code: (Breakdown Demo)

var_export(
    array_uintersect_assoc([
        ['one' => 'a', 'two' => 'ba', 'three' => 'a', 'four' => 'ba'], // strings
        ['one' => 'ba', 'two' => 'a', 'three' => 'a', 'four' => 'ba'], // masks
        'strcspn'
    ])
);
// ['one' => 'a', 'three' => 'a', 'four' => 'ba']

str_repeat()

Code: (Breakdown Demo)

var_export(
    array_uintersect_assoc([
        ['one' => 0, 'two' => 1, 'three' => 0, 'four' => 1], // strings
        ['one' => 1, 'two' => 0, 'three' => 0, 'four' => 1], // times
        'str_repeat'
    ])
);
// ['one' => 0, 'two' => 1, 'three' => 0]

str_contains()

Code: (Breakdown Demo)

var_export(
    array_uintersect_assoc([
        ['one' => 'a', 'two' => 'aa', 'three' => 'a', 'four' => 'aa'], // haystacks
        ['one' => 'aa', 'two' => 'a', 'three' => 'a', 'four' => 'aa'], // needles
        'str_contains'
    ])
);
// ['one' => 'a']
mickmackusa
  • 43,625
  • 12
  • 83
  • 136