skip to Main Content

Using PHP 8.4, given the following functions:

function test_array_diff($all, $filtered)
{
    return array_diff($all, $filtered);
}
function test_array_udiff($all, $filtered)
{
    return array_udiff($all, $filtered, fn($item1, $item2) => $item1 != $item2);
}
function test_in_array($all, $filtered)
{
    $diff = [];

    foreach ($all as $case) {
        if (!in_array($case, $filtered, true)) {
            $diff[] = $case;
        }
    }

    return $diff;
}

The following enum:

enum MyEnum
{
    case FOO;
    case BAR;
}

And those variables:

$all = MyEnum::cases();
$filtered = [MyEnum::BAR];

Why do subsequent calls work as expected:

var_dump(test_array_udiff($all, $filtered));
var_dump(test_in_array($all, $filtered));

But this one throws:

var_dump(test_array_diff($all, $filtered));

Fatal error: Uncaught Error: Object of class MyEnum could not be converted to string

Shouldn’t array_diff() be able to work "out of the box" with enum arrays in the same way as in_array() or array_udiff()?

2

Answers


  1. Chosen as BEST ANSWER

    Although my implementation using array_udiff() was bad (it only works for a subset of cases), using array_udiff() to filter elements is a bad idea anyway because of the underlying sorting algorithms applied to the arrays.

    Nevertheless, I like the RFC's idea, highlighted by @mickmackusa, of being able to use certain array operations on enumeration arrays.

    The implementation below using array_udiff() with the spaceship operator and the enumeration name works now correctly, but is nevertheless very slow with large arrays.

    array_udiff($all, $filtered, fn($item1, $item2) => $item1->name <=> $item2->name) ;
    

    I performed performance tests with the following functions using a hundred-value enumeration class:

    • foreach and in_array()
    • array_reduce() and in_array()
    • array_filter() and in_array()
    • array_walk() and in_array()
    • array_udiff()

    On my hardware with PHP 8.4, using the foreach loop in conjunction with the in_array() function gives the fastest results, while using the array_reduce() and array_filter() functions instead is about ~20% slower, the array_walk() function is about ~30% slower, and using the array_udiff() function is by far much slower, by over 380%.

    array_reduce($all, function ($carry, $case) use ($filtered) {
        if (!in_array($case, $filtered, true)) {
            $carry[] = $case;
        }
        return $carry;
    }, []);
    
    array_filter($all, fn($case) => !in_array($case, $filtered, true));
    
    $diff = [];
    array_walk($all, function ($value) use ($filtered, &$diff) {
        if (!in_array($value, $filtered, true)) {
            $diff[] = $value;
        }
    }, $diff);
    

  2. From the documentation of array_diff():

    Two elements are considered equal if and only if (string) $elem1 === (string) $elem2. That is, when the string representation is the same.

    Since enums can’t be converted to strings, this test is not possible.

    The equivalent test_udiff() would be:

    function test_array_udiff($all, $filtered)
    {
        return array_udiff($all, $filtered, fn($item1, $item2) => (string)$item1 <=> (string)$item2);
    }
    

    Note also that you should use the <=> comparison operator. The callback for array_udiff should return a negative, positive, or zero value to indicate the relationship between the values, not a boolean.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search