🤩 Access a heap of free courses with a SitePoint account

How to Expose PHP’s Private Parts

    Troels Knak-Nielsen
    Share

    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.