Reflection is a metaprogramming construct that allows a program to look into itself and do a multitude of different things - gain meaning, watch execution, call code, or even provide feedback. In PHP this sort of thing is more oriented to frameworks, but the power of reflection can be harnessed to do a lot of different things.
With PHP 5, PHP gained a robust reflection class that allows a developer to gain access to just about every aspect of an object and interact with it. The key is figuring out what is available, and then exploiting it to gain additional benefits.
Part of PhpORM, an object-relational-mapper project that I've been working on, that I've wanted to do is make it easy to create databases based on PHP objects without a lot of overhead. Doctrine does this well in 1.2 (which is really the only version I've used) by allowing a developer to generate schemas from yaml files and compile them into PHP objects. One thing I don't like about Doctrine is that it gets kind of confusing pretty quickly and was a major turn off for me the first few times I tried to use it. I also like to keep as much in my PHP code as I can.
One feature of PhpORM is the SQL generation comments (which is somewhat of a misnomer - they are used right now for SQL generation, but will be expanded into more). Let's set up a basic entity so that we have something to work with:
class Person {
/**
* For the sake of demonstration, we're setting this private
*/
private $_allowDynamicAttributes = false;
/** type=primary_autoincrement */
protected $id = 0;
/** type=varchar length=255 null */
protected $name;
/** type=text null */
protected $biography;
}
The ReflectionClass
One of the main components in reflection programming in PHP is the ReflectionClass. This core class allows a developer to starting digging through an object and pulling out information. What kind of information? Here's a small taste:
Constants
Property Names
Method Names
Static properties
Namespace
If the Class is abstract or final
That's just a small sampling.
Getting Started
So how do we start reflecting on a class to get this information? Just pass it the name of a class:
$class = new ReflectionClass('Person');
Really, that's all there is too it. The $class object has now associated itself with a specific class (in this case our Person class) and we can start inquiring on it.
Getting Properties
Let's find out what properties are in our class and print them out. To do this, we just call getProperties(), which returns an array of ReflectionProperty objects:
$properties = $class->getProperties();
foreach($properties as $property) {
echo $property->getName()."\n";
}
// Output:
// _allowDynamicAttributes
// id
// name
// biography
By default the ReflectionClass will return all of the members of a class, not just public ones. You can alter this by passing a parameter to getProperties(). Valid parameter values are ReflectionProperty::IS_STATIC, ReflectionProperty::IS_PUBLIC, ReflectionProperty::IS_PROTECTED, and ReflectionProperty::IS_PRIVATE.
$private_properties = $class->getProperties(ReflectionProperty::IS_PRIVATE);
Working with Properties
Now that we have a list of properties, we can start to get more information about them instead of the class as a whole. As we can see above, we can get the name of a property via getName(). We can also get the DocBlock comments, the status of a property, and get or set values on objects. Since we're looking to get information about the properties in Person, and that metadata is stored in the DocBlock, let's extract that information.
foreach($properties as $property) {
if($property->isProtected()) {
$docblock = $property->getDocComment();
preg_match('/ type\=([a-z_]*) /', $property->getDocComment(), $matches);
echo $matches[1]."\n";
}
}
// Output:
// primary_autoincrement
// varchar
// text
Since the _allowDynamicAttributes is private, it was skipped (not that it would have matched the regex anyway).
Getting Methods
Getting Methods and working with them is just like with Properties. You can use getMethods() to get an array of ReflectionMethod objects, and from those get their names, comments, prototypes, statuses, etc.
Let's say that we had put getters (getId(), getName(), getBio()) and setters (setId(), setName(), setBio()) on our entity class. We want to set each property and retrieve it. If the setter or getter doesn't exist, throw an exception.
$data = array('id' => 1, 'name' => 'Chris', 'bio' => 'I am am a PHP developer');
foreach($data as $key => $value) {
if(!$class->hasProperty($key)) {
throw new Exception($key.' is not a valid property');
}
if(!$class->hasMethod('get'.ucfirst($key))) {
throw new Exception($key.' is missing a getter');
}
if(!$class->hasMethod('set'.ucfirst($key))) {
throw new Exception($key.' is missing a setter');
}
// Make a new object to interact with
$object = new Person();
// Get the getter method and invoke it with the value in our data array
$setter = $class->getMethod('set'.ucfirst($key));
$setter->invoke($object, $value);
// Get the setter method and invoke it
$setter = $class->getMethod('get'.ucfirst($key));
$objValue = $setter->invoke($object);
// Now compare
if($value != $objValue) {
echo 'Getter or Setter has modified the data.';
} else {
echo 'Getter and Setter does not modify the data.';
}
Depending on what exactly happens with the getter and setter methods will determine the comparison result. If we're missing any getters and setters, this test will alert us and we can fix it.
Start Reflecting On Your Code
See what I did there? Yeah, I'm sorry too. Anyway, hopefully this sheds a bit of light on the Reflection powers that are inherent in PHP. You can reflect on more than just Classes. There are reflection bits for functions, extensions, objects, even parameters. Once you get a grasp on reflection and what it can do, you gain just that extra bit of power of your code.
If you want to see reflection in action, take a look at PhpORM_Cli_GenerateSql. It uses reflection to build a CREATE TABLE statement based upon an entity, all using reflection.