How to Expose PHP’s Private Parts

I’ve been tinkering with dumping PHP objects, and have found myself constantly running into a brick wall. The output from print_r and friends is fine in some contexts, but for larger structures, it would be nice to tidy the output up a bit and wrap it in some HTML.

However, these functions have a certain privilege in that they can access private/protected variables. This is not something that can be circumvented in plain PHP (save through some exotic extensions, which I’d rather not rely on). It seems that 5.3.0 may introduce a functionality in the Reflection API, but nobody knows when 5.3.0 will be out.

So I realised that it is possible to serialize any object to a string, which will include private and protected variables alike. All I had to do was then to parse the serialized string, and I’d gain that arcane insight. One would perhaps assume that such a parser already exists. Maybe it does, but I couldn’t find it (I did find a Java implementation though). The format is fairly simple though, so I spend half a Sunday afternoon on it. One very nice bonus, compared to print_r is that the format handles recursion, which is very handy
for dumping the output in a presentable form.

So, without further ado, here’s the code:

< ?php
/**
* Exports variable information, including private/protected variables
and with recursion-protection.
* Since this is built upon PHP serialization functionality,
unserializable objects may cause trouble.
*/
class XrayVision {
  protected $id;
  function export($object) {
    $this->id = 1;
    list($value, $input) = $this->parse(serialize($object));
    return $value;
  }
  protected function parse($input) {
    if (substr($input, 0, 2) === 'N;') {
      return array(array('type' => 'null', 'id' => $this->id++,
'value' => null), substr($input, 2));
    }
    $pos = strpos($input, ':');
    $type = substr($input, 0, $pos);
    $input = substr($input, $pos + 1);
    switch ($type) {
    case 's':
      return $this->s($input);
    case 'i':
      return $this->i($input);
    case 'd':
      return $this->d($input);
    case 'b':
      return $this->b($input);
    case 'O':
      return $this->o($input);
    case 'a':
      return $this->a($input);
    case 'r':
      return $this->r($input);
    }
    throw new Exception("Unhandled type '$type'");
  }
  protected function s($input) {
    $pos = strpos($input, ':');
    $length = substr($input, 0, $pos);
    $input = substr($input, $pos + 1);
    $value = substr($input, 1, $length);
    return array(array('type' => 'string', 'id' => $this->id++,
'value' => $value), substr($input, $length + 3));
  }
  protected function i($input) {
    $pos = strpos($input, ';');
    $value = (integer) substr($input, 0, $pos);
    return array(array('type' => 'integer', 'id' => $this->id++,
'value' => $value), substr($input, $pos + 1));
  }
  protected function d($input) {
    $pos = strpos($input, ';');
    $value = (float) substr($input, 0, $pos);
    return array(array('type' => 'float', 'id' => $this->id++, 'value'
=> $value), substr($input, $pos + 1));
  }
  protected function b($input) {
    $pos = strpos($input, ';');
    $value = substr($input, 0, $pos) === '1';
    return array(array('type' => 'boolean', 'id' => $this->id++,
'value' => $value), substr($input, $pos + 1));
  }
  protected function r($input) {
    $pos = strpos($input, ';');
    $value = (integer) substr($input, 0, $pos);
    return array(array('type' => 'recursion', 'id' => $this->id++,
'value' => $value), substr($input, $pos + 1));
  }
  protected function o($input) {
    $id = $this->id++;
    $pos = strpos($input, ':');
    $name_length = substr($input, 0, $pos);
    $input = substr($input, $pos + 1);
    $name = substr($input, 1, $name_length);
    $input = substr($input, $name_length + 3);
    $pos = strpos($input, ':');
    $length = (int) substr($input, 0, $pos);
    $input = substr($input, $pos + 2);
    $values = array();
    for ($ii=0; $ii < $length; $ii++) {
      list($key, $input) = $this->parse($input);
      $this->id--;
      list($value, $input) = $this->parse($input);
      if (substr($key['value'], 0, 3) === "00*00") {
        $values['protected:' . substr($key['value'], 3)] = $value;
      } elseif ($pos = strrpos($key['value'], "00")) {
        $values['private:' . substr($key['value'], $pos + 1)] = $value;
      } else {
        $values[str_replace("00", ':', $key['value'])] = $value;
      }
    }
    return array(
      array('type' => 'object', 'id' => $id, 'class' => $name, 'value'
=> $values),
      substr($input, 1));
  }
  protected function a($input) {
    $id = $this->id++;
    $pos = strpos($input, ':');
    $length = (int) substr($input, 0, $pos);
    $input = substr($input, $pos + 2);
    $values = array();
    for ($ii=0; $ii < $length; $ii++) {
      list($key, $input) = $this->parse($input);
      $this->id--;
      list($value, $input) = $this->parse($input);
      $values[$key['value']] = $value;
    }
    return array(
      array('type' => 'array', 'id' => $id, 'value' => $values),
      substr($input, 1));
  }
}
?>

There are at least two known issues with this technique. The first is that resources are serialized into integers. For a dump, this doesn’t really matter, since a resource is meaningless outside the running process. The other problem is with objects that implements __sleep. Since this function may have side-effects, you can potentially mess up your program for objects that use this feature. In my experience, it’s a seldom used functionality anyway, so it doesn’t really bother me that much.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • p.vernooij

    Interesting! I’d like to add it to my debug tool set, but there is a parse error:

    Line 75:
    for ($ii=0; $ii parse($input);
    
    (Parse error: syntax error, unexpected T_STRING, expecting ';' in /Users/pvernooij/Sites/XrayVision.php on line 77)
  • Troels Knak-Nielsen

    Sorry about that. WordPress keeps messing with my code. I’ve posted it to pastebin now, where it doesn’t get hurt.

    Seems we got it working now.

  • Wolfgang Stengel

    I have similar functionality in my debug toolset, but it works with a simple cast of objects to an array: $subject=(array)$object;. This exposes the private variables as well.

  • PP

    You really should try __toString() instead of some serialization magic : http://www.hudzilla.org/phpbook/read.php/6_14_5

    Don’t loose your sunday afternoon for that…

  • Ken Guest

    hmm. you could have saved yourself a lot of work by simply using PEAR’s var_dump package ( http://pear.php.net/package/Var_Dump ) with it you can render to text, html or xml and carry on with the important work!

  • Troels Knak-Nielsen

    @Wolfgang
    That’s a very nice little secret trick, you got there! Thanks for sharing – Now I’m just a bit annoyed, I didn’t know about this beforehand.

  • Josh Johnston

    Your code doesn’t work. Using your code, the object is nothing. Using print_r you can at least see that there is one NULL protected property

    $x = new XrayVision;
    
    echo '<pre>', print_r($x, 1), '<pre>---';
    echo '<pre>', $x->export($x), '<pre>';
    

    Output:

    XrayVision Object(
        [id:protected] => 
    )
    
    ---
    
    Array
  • Nick

    Or just install xdebug (which you should have installed on development environments already) and use var_dump().

  • Nick

    Also, ReflectionProperty is not part of 5.3. It’s already in 5.2.

  • Nick

    To finish up, explain how this class helps with your stated issue:

    The output from print_r and friends is fine in some contexts, but for larger structures, it would be nice to tidy the output up a bit and wrap it in some HTML.

    Because I see no functionality for pretty-printing here anywhere.

    And lastly, if I interpret your confusing English correctly, you’re asserting that regular ol’ print_r() has some issue preventing it from accessing private and protected variables? Are you sure this is true? Because I’m pretty sure it’s not true, and the burden of proof is on you here, my friend.

  • Tarh

    I’d love to see more PHP blog posts here, but it’s quite clear that there’s a lot of hostility around here. I never before considered the PHP community elitist, but some of the comments on this blog are quite convincing.

    Obviously it’s good to have reader feedback on easier ways to do things, but there’s no reason for those suggestions to border on personal attacks like a lot of the responses to Troels’s posts. It really makes a case for disabling comments and sharing feedback through PMs only . . .

  • Nick

    I definitely do not mean any personal offense to the author. But the article is misleading, confusingly written, the code example inadequate to address the issues raised as the article’s thesis, the example solves a non-existant problem in a very inefficient manner, and there are some factual errors.

  • Troels Knak-Nielsen

    @Ken Guest
    Thanks for mentioning it – I haven’t seen that package for some reason. I’m a bit surprised they took the long road and parsed var_dump‘s output, when a serialized string is so much easier to parse.

    @Josh Johnston
    I probably should have mentioned explicitly, but this class just generates an array structure, which describes the variable. This can subsequently be transformed into html or any other kind of output. I left that part out though, since it’s rather trivial. One use case could be to json-encode the structure and send it to the Firebug console. Try the following, to see what I mean:

    $x = new XrayVision();
    print_r($x->export($x));
    

    Which will give you:

    Array
    (
        [type] => object
        [id] => 1
        [class] => XrayVision
        [value] => Array
            (
                [protected:id] => Array
                    (
                        [type] => integer
                        [id] => 2
                        [value] => 1
                    )
    
            )
    
    )
    

    @Nick
    Burden of proof? I didn’t know I was on trial.

    @Tarh
    Thanks, though I don’t think it’s fair to judge the entire PHP community on the grounds of one raging madman.

  • Nick

    Don’t get reflexively defensive. Correct me if I’m wrong, but the basis of this article is: print_r() does not print out private and protected variables of an object. Hence the need for this class you wrote. Correct?

    I was pointing out that this assertion about print_r() is not true.

    Let’s try an example, this will be using PHP 5.2.4:

    
    class AClass
    {
        protected $pro2;
        private $pri2;
        function __construct() {
            $this->pro2 = "parent class protected var";
            $this->pri2 = "parent class private var";
        }
    }
    
    class MyClass extends AClass
    {
        public $pub;
        private $pri;
        protected $pro;
        function __construct() {
            parent::__construct();
            $this->pub = "public var";
            $this->pri = "private var";
            $this->pro = "protected var";
        }
    }
    
    $c = new MyClass();
    
    print_r($c);
    exit;
    
    MyClass Object
    (
        [pub] => public var
        [pri:private] => private var
        [pro:protected] => protected var
        [pro2:protected] => parent class protected var
        [pri2:private] => parent class private var
    )

    Explain how that is substantively different than the output of your class’s export() method:

    Array
    (
        [type] => object
        [id] => 1
        [class] => MyClass
        [value] => Array
            (
                [pub] => Array
                    (
                        [type] => string
                        [id] => 2
                        [value] => public var
                    )
    
                [private:pri] => Array
                    (
                        [type] => string
                        [id] => 3
                        [value] => private var
                    )
    
                [protected:pro] => Array
                    (
                        [type] => string
                        [id] => 4
                        [value] => protected var
                    )
    
                [protected:pro2] => Array
                    (
                        [type] => string
                        [id] => 5
                        [value] => parent class protected var
                    )
    
                [private:pri2] => Array
                    (
                        [type] => string
                        [id] => 6
                        [value] => parent class private var
                    )
    
            )
    
    )
  • Nick

    My apologies about the formatting there.

  • Troels Knak-Nielsen

    Don’t get reflexively defensive. Correct me if I’m wrong, but the basis of this article is: print_r() does not print out private and protected variables of an object. Hence the need for this class you wrote. Correct?

    Defensive? I find your tone borderline rude, but to let you have the benefit of doubt, I’ll answer none the less.

    No, my assertion was the opposite — Namely that print_r is able to do something that is impossible through the reflection API — At least until 5.3.0 comes out. What I wanted to do, was to generate some pretty HTML representation of variables, rather than the text-based output from print_r. Something similar to this for example.

  • Nick

    My apologies, honestly, not trying to be rude. If it’s rude to deconstruct a technical article for accuracy, then we might as well all go home and take up farming for a living.

    I understand that I misread the article and you are simply trying to show that parsing a serialized string is easier than formatting the output from print_r().

    That being the case, I’d like to re-emphasize that this kind of debugging is aided and enhanced greatly by the xdebug extension, which comes with a nice feature to format the var_dump() output for you with nice HTML and pretty colors. (among many other much more powerful features.)

  • http://www.magain.com/ mattymcg

    Thanks everyone for being considerate and professional when posting here—getting personal doesn’t help anyone. Also worth noting that we have a “comment preview”, so you can check what your comments look like before posting. I’ve cleaned up a few people’s comments. Hope that helps!

  • Ren

    The (array) casting of objects is useful. One particular use was to map objects to an array of parameters for prepared sql.


    class Person
    {
    protected $id;
    public $firstName;
    public $lastName;

    function __construct($firstName, $lastName, $id = null)
    {
    $this->id = $id;
    $this->firstName = $firstName;
    $this->lastName = $lastName;
    }
    }

    $person = new Person('Homer', 'Simpson');

    $map = array(
    "*id" => 'id',
    "firstName" => 'firstName',
    "lastName" => 'lastName');

    $params = array_combine($map, array_intersect_key((array)$person, $map));

    var_dump($params);

    No idea how your supposed to get code displaying nicely here

  • Duh.

    $arr = (array)$object;
    var_dump($arr);

    // Win

  • TerrorKalle

    Casting an object to an array like stated above will expose its private properties aswell as its private properties. Its quite simple:

    http://www.phpfi.com/382350

    :)