How to Expose PHP’s Private Parts

By | | PHP

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) === "\000*\000") {
        $values['protected:' . substr($key['value'], 3)] = $value;
      } elseif ($pos = strrpos($key['value'], "\000")) {
        $values['private:' . substr($key['value'], $pos + 1)] = $value;
      } else {
        $values[str_replace("\000", ':', $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.

Written By:

Troels Knak-Nielsen

Troels has been crafting web applications, as a freelancer and while employed by companies of various sizes, since around the time of the IT-bubble burst. Nowadays, he's working on backend systems for a Danish ISP. In his spare time, he develops and maintains Konstrukt, a web application framework for PHP, and is the organizer of a monthly PHP-meetup in Copenhagen.

 

{ 21 comments }

TerrorKalle November 26, 2008 at 3:08 am

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

:)

Duh. November 16, 2008 at 7:00 am

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

// Win

Ren November 14, 2008 at 2:49 am

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

mattymcg November 13, 2008 at 9:56 am

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!

Nick November 13, 2008 at 9:25 am

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.)

Troels Knak-Nielsen November 13, 2008 at 9:01 am

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 November 13, 2008 at 8:40 am

My apologies about the formatting there.

Nick November 13, 2008 at 8:38 am

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
                )
        )
)

Troels Knak-Nielsen November 13, 2008 at 8:23 am

@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 November 13, 2008 at 8:06 am

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.

Tarh November 13, 2008 at 6:42 am

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 November 12, 2008 at 9:40 am

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.

Nick November 12, 2008 at 9:23 am

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

Nick November 12, 2008 at 9:19 am

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

Josh Johnston November 12, 2008 at 5:17 am

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

Troels Knak-Nielsen November 12, 2008 at 2:00 am

@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.

Ken Guest November 12, 2008 at 12:47 am

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!

PP November 12, 2008 at 12:01 am

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…

Wolfgang Stengel November 11, 2008 at 9:17 pm

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.

Troels Knak-Nielsen November 11, 2008 at 7:34 pm

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.

p.vernooij November 11, 2008 at 7:25 pm

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)

Comments on this entry are closed.