var_dump'ing recursive object structures

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

Post Reply
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

var_dump'ing recursive object structures

Post by Ambush Commander »

Let's take this example:

Code: Select all

class A {
  var $b;
}

class B {
  var $a;
}

$a =& new A;
$b =& new B;

$a->b =& $b;
$b->a =& $a;

var_dump($a);
What happens?

You get something like this:

Code: Select all

object(a)
  'b' => 
    object(b)
      'a' => 
        object(a)
          'b' => 
            object(b)
              'a' => 
                object(a)
                  'b' => 
                    object {
Eventually, the var_dump is smart enough to truncate the object stack, but looping through it almost three times severely restricts readability.

print_r is a bit smarter...

Code: Select all

a Object
(
    [b] => b Object
        (
            [a] => a Object
                (
                    [b] =>  *RECURSION*
                )

        )

)
But it's still pretty bad.

I was wondering: should I roll my own variant of var_dump using get_object_vars() that only goes, say, 2 levels deep (or even one level deep) for debugging bidirectional object structures? Should I make it smart and make it immediately truncate the branch when it detects an object it has seen before (not sure how to do that though...)
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

AmbCom wrote: Should I make it smart and make it immediately truncate the branch when it detects an object it has seen before (not sure how to do that though...)
It's easy: to check two objects for identity just set some previously unset property in first obj and then check if it appeared in second obj.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

But when you have multiple objects recursing, you'd have to go through each one of them and see whether or not there's an identity. Plus, you've developed an uncertainty phenomena: whenever you observe the class, you modify it! :-)

Perhaps you name recursive fields specially...
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Post by josh »

You could look at the checksum of the object

Code: Select all

sha1(serialize($obj))
It wouldn't work if you had anything major going on in __sleep()
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

try this:

Code: Select all

// from php manual comments
class php {
  # in === on objects in php4 does a dumb recusrive check instead
  function CompareObject(&$a,&$b) {
   $value='Bah! Stupid===';
   $key="bah".rand(0,1000);
   while(isset($a->$key) || isset($b->$key)) $key.=rand(0,9);

   $a->$key=$value;
   $result=($a->$key===$b->$key);
   unset($a->$key);
   if(!$result) // objs are not identical, we need to clean up the second obj
      unset($b->$key);
   return $result;
  }
}

function my_var_dump($object, $level = 0) {
  static $objs = array();

  if(is_object($object))
     $objs[] = &$object;

  foreach((array) $object as &$elt) {
      if(is_object($elt)) {
        // check for recursion
        foreach($objs as $obj) {
            if(php::CompareObject($object, $obj)) {
               echo "recursion at $level";
               continue 2;
            }
        }
        // there was no recursion
        my_var_dump($elt, $level+1);
      } else {
          echo $elt;
      }
  }
}
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Post by josh »

That works but he implied he doesn't want to make modifications to the object just to observe it. As long as your __sleep function does not modify the object the code I posted above will work without making any modifications at all to the object.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

The root of the problem is that PHP4's identitical operator doesn't figure out whether or not the two objects are references. If we have two identical objects THAT ARE NOT references, jshpro2's check will put them out as the same.

Besides... this is what happens when you serialize a recursive object structure:

Code: Select all

O:1:"a":1:{s:1:"b";O:1:"b":1:{s:1:"a";O:1:"a":1:{s:1:"b";R:2;}}}
Thinking about it, here's another way:

Enforce strict naming conventions about recursive references, name em all _r_*. Then all the function has to do is check names and not expand _r_* ones.

Weirdan's approach will leave the two objects the same way they were before, so I don't think I'll mind using his method. But you still need to know which two objects to compare.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Post by josh »

The function weirdan found does leave the objects the same, but it does make modifications to them, if you don't mind that I would recommend that over taking a checksum just for speed issues alone
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

Ambush Commander wrote: But you still need to know which two objects to compare.
For that I used static array of references to those objects that are already visited (it's perfectly fine for single-threaded scripts but problems may arise when using tick).
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

But on each object you have to check every other object ever visited to see if they're references. That's an O(n^2) complexity (I think).
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

That's an O(n^2) complexity (I think).
I didn't claim it was efficient =) That was just a quick example, you're certainly free to choose a different approach.
Post Reply