Skip to content

Assert::isEqual() can compare objects that are recursive - #93

Closed
fprochazka wants to merge 2 commits into
nette:masterfrom
fprochazka:feature/comparing-objects-recursively
Closed

fprochazka wants to merge 2 commits into
nette:masterfrom
fprochazka:feature/comparing-objects-recursively

Conversation

@fprochazka

Copy link
Copy Markdown
Contributor

Without this pullrequest

use Nette\Forms\Controls\TextInput;

Assert::equal(new TextInput(), new TextInput());
// Exception('Nesting level too deep or recursive dependency.');

and with

Assert::equal(new TextInput(), new TextInput());
// success

@fprochazka

Copy link
Copy Markdown
Contributor Author

@milo

milo commented Feb 16, 2014

Copy link
Copy Markdown
Member

@fprochazka Sorry, but I'm not sure if it can be implemented this way. It is a question about "what equality means" in this case.

Following code pass even reference points to other structure part.

$r1 = new stdClass;
$r1->a = new stdClass;
$r1->a->a = $r1;

$r2 = new stdClass;
$r2->a = new stdClass;
$r2->a->a = $r2->a;

Maybe references should be replaced by structure path before comparing. And it would be great for arrays too :)

@fprochazka

Copy link
Copy Markdown
Contributor Author

@milo technically, you're checking equality of given structures, and because they don't have any other properties, they are equal.

$r1 = new stdClass;
$r1->b = 'b';
$r1->a = new stdClass;
$r1->a->a = $r1;
$r1->a->c = 'c';

$r2 = new stdClass;
$r2->b = 'b';
$r2->a = new stdClass;
$r2->a->a = $r2->a;
$r2->a->c = 'c';

Assert::equal($r1, $r2);

If you add values to the structure it's not equal anymore.

@fprochazka

Copy link
Copy Markdown
Contributor Author

@dg what do you think?

@dg

dg commented Feb 17, 2014

Copy link
Copy Markdown
Member

I think that "equal" is used in cases, when we want to ignore objects's identity. So in @milo structure should be ignored identity of $rx->a (so they are equal), but in @fprochazka they are not equal, because $r1->a->a->b = 'b' and $r2->a->a->b = undefined

@fprochazka

Copy link
Copy Markdown
Contributor Author

@dg what is your suggestion? There is no reason we should be throwing recursion exception if it can be checked this easily.

In fact, those two structures are perfectly equal untill you add other values to them (just like you did). @milo's example is equal, mine is not, yours is not.

There is infinite recursion of objects that have only one property a and that property equals to instance of stdClass that has only one property a and that property equals to instance of stdClass, ...

They are equal untill you add other properties to them. I haven't had a high school math so I might be wrong, but in what universe those two structures are not equal?


These are equal

$r1 = new stdClass;
$r1->a = new stdClass;
$r1->a->a = $r1;

$r2 = new stdClass;
$r2->a = new stdClass;
$r2->a->a = $r2->a;

snmek obrazovky pozen 2014-02-17 19 06 43

These are not

$r1 = new stdClass;
$r1->b = 'b';
$r1->a = new stdClass;
$r1->a->a = $r1;

$r2 = new stdClass;
$r2->b = 'b';
$r2->a = new stdClass;
$r2->a->a = $r2->a;

snmek obrazovky pozen 2014-02-17 19 07 20


The only think I did was that if the algorithm finds an object that is already beeing checked (in higher call) it assumes that the object is equal to the other side, because if not, it will fail on the higher check.

It simply prevents double checking of the object properties (and as a side effect fixes object-recursion problem)

@milo

milo commented Feb 17, 2014

Copy link
Copy Markdown
Member

I think that base problem is definition of back-reference equality. Imagine it as paths:

$r1 = new stdClass;
$r1->a = new stdClass;
$r1->a->a = $r1; # = /

$r2 = new stdClass;
$r2->a = new stdClass;
$r2->a->a = $r2->a; # = /a/

I prepared another point of view solution. I'll send it during evening.

@castamir

Copy link
Copy Markdown

Problem is "where to stop" (its halting problem). It basically means that you may stop but you might not.

In your case, it will stop, but in another (basically the same) it might not (you will probably run out of memory before you find any relation between nested objects):

$r1 = new stdClass;
$r1->a = new stdClass;
$r1->a->a = new stdClass;
$r1->a->a->a = new stdClass;
$r1->a->a->a->a = new stdClass;
$r1->a->a->a->a->a = new stdClass;
...
$r1->a->a->a->a->a->a...->a = $r1;

$r2 = new stdClass;
$r2->a = new stdClass;
$r2->a->a = $r2->a;

@fprochazka

Copy link
Copy Markdown
Contributor Author

@castamir in that case the Assert::equals() method will say there is recursion that cannot be solved and throws exception.

I don't see myself solving such absurd situations, I just wanna be able to compare equality of two normal objects (like TextInput).

The beauty of this is that we don't have to solve all possible structures there ever will be, we have to only solve few standard cases and in the rest we can throw new NotImplementedException("Wait for it... or implement it yourself").

@milo

milo commented Feb 17, 2014

Copy link
Copy Markdown
Member

@fprochazka @dg What about this solution?

@fprochazka

Copy link
Copy Markdown
Contributor Author

@milo my brain hurts from all those testcases but I guess it makes sense :) And it works in my use-case (I've tested it), so as far as my vote goes, I'm in :)

@dg

dg commented Feb 18, 2014

Copy link
Copy Markdown
Member

I would rather prefer @milo's approach, #99.

@fprochazka

Copy link
Copy Markdown
Contributor Author

@dg I like the hashes more, I was thinking about them before I started writing the code, but I had a hunch you might wanna avoid them (I don't know how fast they are).

@dg

dg commented Feb 18, 2014

Copy link
Copy Markdown
Member

Speed absolutely doesn't matter here. But I changed it to SplObjectStorage for better readability.

Btw simpler your solution, without hashes:

            if ($expected === $actual || in_array([$expected, $actual], $checked, TRUE)) {
                return TRUE;
            }
            $checked[] = [$expected, $actual];

@fprochazka

Copy link
Copy Markdown
Contributor Author

Closing in favor of #99

@fprochazka fprochazka closed this Feb 18, 2014
@fprochazka
fprochazka deleted the feature/comparing-objects-recursively branch February 18, 2014 12:40
dg added a commit that referenced this pull request Feb 19, 2014
Assert::isEqual: can compare recursive objects [Closes #93]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants