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.
Frequently Asked Questions about Exposing PHP’s Private Parts
What is the main purpose of exposing PHP’s private parts?
Exposing PHP’s private parts is primarily done to allow access to private methods and properties in a class. This is particularly useful in unit testing where you might need to test a private method directly. It’s also beneficial when you need to debug or modify a class without changing its source code. However, it’s important to note that this should be done with caution as it can potentially break the encapsulation principle of object-oriented programming.
How does exposing PHP’s private parts differ from other languages?
In some languages like Java, private methods and properties are strictly private and cannot be accessed outside the class. However, in PHP, you can use reflection to access these private parts. This provides more flexibility but also requires careful handling to maintain the integrity of the class.
What is the role of the ReflectionClass in PHP?
The ReflectionClass in PHP is a built-in class that allows you to introspect a class, its properties, and methods. It’s a powerful tool that can be used to expose and manipulate private parts of a class. You can use it to get information about a class, create a new class instance, clone an object, or even modify a class’s properties and methods.
How can I use the ReflectionClass to access private properties?
To access private properties using ReflectionClass, you first need to create an instance of the ReflectionClass with the class name as a parameter. Then, you can use the getProperty method to get a ReflectionProperty instance for the private property. After that, you can use the setAccessible method to make the property accessible and then use the getValue and setValue methods to get or set the property’s value.
Can I use the ReflectionClass to access private methods?
Yes, you can use the ReflectionClass to access private methods in a similar way to accessing private properties. You need to use the getMethod method to get a ReflectionMethod instance for the private method. Then, you can use the setAccessible method to make the method accessible and then use the invoke or invokeArgs methods to call the method.
What are the risks of exposing PHP’s private parts?
Exposing PHP’s private parts can potentially break the encapsulation principle of object-oriented programming. This can lead to code that is harder to maintain and debug. It can also lead to unexpected behavior if the private methods or properties are not used correctly. Therefore, it’s recommended to use this technique sparingly and with caution.
Are there alternatives to using ReflectionClass for exposing PHP’s private parts?
While ReflectionClass is the most common way to expose PHP’s private parts, there are other techniques like using closures or debug_backtrace function. However, these techniques are more complex and may not be suitable for all situations.
Can I use ReflectionClass with inherited classes?
Yes, you can use ReflectionClass with inherited classes. You can access the private properties and methods of the parent class using the same techniques. However, you need to create a ReflectionClass instance for the parent class.
Can I use ReflectionClass to modify private constants?
No, you cannot use ReflectionClass to modify private constants. Constants in PHP are immutable, meaning they cannot be changed once they are defined. This applies to both class constants and global constants.
Can I use ReflectionClass to expose private parts of an interface or abstract class?
No, you cannot use ReflectionClass to expose private parts of an interface or abstract class. Interfaces and abstract classes in PHP do not have properties, and their methods are public by default. Therefore, there are no private parts to expose.
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.