3

I just read that question about strange php behaviour, and even though I could research a bit more, I'm nowhere near understanding it.

I assume the reader has read the original question and is aware of OP's code block and sample, but in short, OP is trying to compare those two arrays, and while the result is good, the compare function seems to be called erratically:

$chomik = new chomik('a');
$a = array(5, $chomik, $chomik, $chomik);
$b = array($chomik, 'b', 'c', 'd');
array_diff_uassoc($a, $b, 'compare');

The documentation is a bit obscure... but it does state that:

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.

As I understand it, that means that the compare() function should be more like this:

function compare($a, $b) {
    echo("$a : $b<br/>");
    if($a === $b) return 0;
    else if ($a > $b) return 1;
    else return -1;
}

however this still gives very strange results, with even more "duplicates"

1 : 0
1 : 2
3 : 1
2 : 1
3 : 2
1 : 0
1 : 2
3 : 1
2 : 1
3 : 2
0 : 0
1 : 0
1 : 1
2 : 0
2 : 1
2 : 2
3 : 0
3 : 1
3 : 2
3 : 3

faced with many doubts, I read the compat php function, and the part where the check actually happens is interesting:

foreach ($args[0] as $k => $v) {
    for ($i = 1; $i < $array_count; $i++) {
        foreach ($args[$i] as $kk => $vv) {
            if ($v == $vv) { // compare keys only if value are the same
                $compare = call_user_func_array($compare_func, array($k, $kk));
                if ($compare == 0) {
                    continue 3; // value should not be added to the result
                }
            }
        }
    }
    $result[$k] = $v;
}

here's the actual source (per comment)

The way this code executes the compare function should not be outputting the result we see. Foreach is not able to move back and forth in the keys (AFAIK???), as seems to be the case in the order of the first key here:

1 : 2
3 : 1
2 : 1

moreover, it shouldn't check the keys if the value do not match, so why do all these are checked:

1 : 2
3 : 1
2 : 1
3 : 2
etc...

How can the topmost foreach() in the source code loop back and forth through the keys?!

Why are keys whose values do not match still compared?

Do foreach loops actually continue executing even when they've been continued?

Is this an example of concurrency? can call_user_func_array somehow be launched and actually execute the echo("$a : $b<br/>"); of the compare function not in the same order they were "launched"??

Community
  • 1
  • 1

2 Answers2

1

I believe you've pinpointed a bug, my friend. I just ran the code in the question you referenced, and sure enough, it compared keys for values that weren't the same. However, I wanted to test if the source code itself contained the mistake, so I added the official source for array_diff_uassoc this to the top his code, inside my own namespace:

<?php

namespace mine;

// Code obtained from https://pear.php.net/reference/PHP_Compat-latest/__filesource/fsource_PHP_Compat__PHP_Compat-1.6.0a3CompatFunctionarray_diff_uassoc.php.html

function array_diff_uassoc()


{

    // Sanity check

    $args = func_get_args();

    if (count($args) < 3) {

        user_error('Wrong parameter count for array_diff_uassoc()', E_USER_WARNING);

        return;

    }



    // Get compare function

    $compare_func = array_pop($args);

    if (!is_callable($compare_func)) {

        if (is_array($compare_func)) {

            $compare_func = $compare_func[0] . '::' . $compare_func[1];

        }

        user_error('array_diff_uassoc() Not a valid callback ' .

            $compare_func, E_USER_WARNING);

        return;

    }



    // Check arrays

    $array_count = count($args);

    for ($i = 0; $i !== $array_count; $i++) {

        if (!is_array($args[$i])) {

            user_error('array_diff_uassoc() Argument #' .

                ($i + 1) . ' is not an array', E_USER_WARNING);

            return;

        }

    }



    // Compare entries

    $result = array();

    foreach ($args[0] as $k => $v) {

        for ($i = 1; $i < $array_count; $i++) {

            foreach ($args[$i] as $kk => $vv) {

                if ($v == $vv) {

               //   echo ("$v\n");
                    // echo ("$vv\n");
               //   echo ("$k\n");
                    // echo ("$kk\n");
                    // die();

                    $compare = call_user_func_array($compare_func, array($k, $kk));

                    if ($compare == 0) {

                        continue 3;

                    }

                }

            }

        }



        $result[$k] = $v;

    }

    return $result;

}

class chomik {

    public $state = 'normal';
    public $name = 'no name';

    public function __construct($name) {
        $this->name = $name;
    }

    public function __toString() {
        return $this->name . " - " . $this->state;
    }
}

function compare($a, $b) {
    echo("$a : $b\n");
    if($a != $b) {
        return 0;
    }
    else return 1;
}

$chomik = new chomik('a');
$a = array(5, $chomik, $chomik, $chomik);
$b = array($chomik, 'b', 'c', 'd');
array_diff_uassoc($a, $b, 'mine\compare');

This time, it only compared keys for values that were equal:

1 : 0
2 : 0
3 : 0

Strange, huh?

wavemode
  • 2,076
  • 1
  • 19
  • 24
  • 2
    I'm not sure what code you're posting here, but the code for PHP's various array_diff functions is [here](https://github.com/php/php-src/blob/master/ext/standard/array.c#L3938) and is written in C++. – Powerlord Apr 02 '15 at 21:18
  • 1
    @Powerlord This code is from the [PEAR reference site](https://pear.php.net/reference/PHP_Compat-latest/__filesource/fsource_PHP_Compat__PHP_Compat-1.6.0a3CompatFunctionarray_diff_uassoc.php.html), which is PHP code that should reflect the behavior of the actual functions (which, you're correct, are written in C). The fact that the C code behaves differently than the documented PHP version is a bug, in my book. – wavemode Apr 02 '15 at 21:25
  • The compatibility function in PHP has the same API and behavior as the original function written in C. And that is all the PHP_Compat package claims `PHP_Compat provides drop-in functions and constants for compatibility with newer versions of PHP, environment emulation, and an API to allow for version independent authoring.` So I don't see how you could call it a bug. Why would you `echo` in that `compare` function in the first place? – Pevara Apr 02 '15 at 21:31
  • @Pevara to pinpoint the fact that the callback function is called with no relation to the tests which are made in the `array_diff_uassoc()` ?? – Félix Adriyel Gagnon-Grenier Apr 03 '15 at 14:07
  • @Pevara moreover... how can you write `The compatibility function in PHP has the same API and behavior as the original function written in C`. haven't you read the answer you're commenting on? They **do not** have the same behaviour... Evidently the compare function should be called three times in our case, and it is called *much* more often when using php built in array_diff... – Félix Adriyel Gagnon-Grenier Apr 03 '15 at 16:40
  • 1
    behavior may not be the right word. What I meant is that the API is the same. They both accept the same input parameters and they return the same output. That is all you should care about. Both functions, the C version and the PHP_Compat version, are interchangable. You should never rely on the internal workings of a function, that would just be a bug waiting to happen. You can not claim `Evidently the compare function should be called three times in our case` and you just shouldn't care – Pevara Apr 03 '15 at 17:43
  • I think I got carried away. What you say makes much sense. – Félix Adriyel Gagnon-Grenier Apr 03 '15 at 21:13
0

From this comment by powerlord:

Judging from the fact that the custom compare function asks you to return -1; 0; or 1, it seems like its doing a sort either before or at the same time as a comparison between the two arrays.

I was encouraged to go and read the actual php_array_diff() source, the registered function for array_diff_uassoc(), and discovered it uses the compare function a lot of times, which I wrongly interpreted as foreach going back and forth through the keys.


How can the topmost foreach() in the source code loop back and forth through the keys?!

it doesn't. It's just that the user-supplied function

diff_data_compare_func = php_array_user_compare;

is used multiple times to sort and evaluate the data.

[...]
zend_sort((void *) lists[i], hash->nNumOfElements,
            sizeof(Bucket), diff_data_compare_func, (swap_func_t)zend_hash_bucket_swap);
[...]
while (Z_TYPE(ptrs[i]->val) != IS_UNDEF && (0 < (c = diff_data_compare_func(ptrs[0], ptrs[i])))) {
[...]
if (diff_data_compare_func(ptrs[0], ptr) != 0) {
[...]

Why are keys whose values do not match still compared?

It is true the compat pear code posted in the question hints that if the valus do not match, the compare function should not even be run, but php_array_diff() acts differently.


Do foreach loops actually continue executing even when they've been continued? Is this an example of concurrency?

Nonsense. If I were you, I'd edit that out.

Community
  • 1
  • 1