Using the PHP Reflection API for fun and profit

When PHP got a real object oriented system in 5.0, it also got a neat feature taken from Java land. Reflection allows you to introspect & reverse engineer functions, classes, and extensions. In addition you can use reflection to extraction of documentation from classes and functions. In PHP Reflection is done using a number of Reflection classes.

Using reflection

Reflection starts by creating a new Reflector, you do this like so

Show Plain Text
  1.  
  2. $modelReflector = new ReflectionClass('Model');
  3. $debugReflector = new ReflectionFunction('debug');
  4.  

Reflection constructors take the name of the class or function you wish to reflect. Once you have a reflection of the class you want to look at there are quite a few things you can do with it. You can find out what properties it has with $reflector->getProperties(), see what methods it has with $reflector->getMethods() and so on. In addition to getting methods, you can find out a great deal about the methods. You can find out what arguments they take, and which arguments are required, and which are optional.

Show Plain Text
  1. $modelReflector = new ReflectionClass('Model');
  2. $method = $modelReflector->getMethod('find');
  3. $parameters = $method->getParameters();

Will give you a list of ReflectionParameter objects, that you could iterate over and find which are required and get any type hints that are provided. If we were to dump the $method object we created we would see the following.

Show Plain Text
  1. /**
  2.  * Returns a result set array.
  3.  *
  4.  * Also used to perform new-notation finds, where the first argument is type of find operation to perform
  5.  * (all / first / count / neighbors / list / threaded ),
  6.  * second parameter options for finding ( indexed array, including: 'conditions', 'limit',
  7.  * 'recursive', 'page', 'fields', 'offset', 'order')
  8.  *
  9.  * Eg: find('all', array(
  10.  *                  'conditions' => array('name' => 'Thomas Anderson'),
  11.  *                  'fields' => array('name', 'email'),
  12.  *                  'order' => 'field3 DESC',
  13.  *                  'recursive' => 2,
  14.  *                  'group' => 'type'));
  15.  *
  16.  * Specifying 'fields' for new-notation 'list':
  17.  *  - If no fields are specified, then 'id' is used for key and 'model->displayField' is used for value.
  18.  *  - If a single field is specified, 'id' is used for key and specified field is used for value.
  19.  *  - If three fields are specified, they are used (in order) for key, value and group.
  20.  *  - Otherwise, first and second fields are used for key and value.
  21.  *
  22.  * @param array $conditions SQL conditions array, or type of find operation (all / first / count / neighbors / list / threaded)
  23.  * @param mixed $fields Either a single string of a field name, or an array of field names, or options for matching
  24.  * @param string $order SQL ORDER BY conditions (e.g. "price DESC" or "name ASC")
  25.  * @param integer $recursive The number of levels deep to fetch associated records
  26.  * @return array Array of records
  27.  * @access public
  28.  * @link http://book.cakephp.org/view/449/find
  29.  */
  30. Method [ <user> public method find ] {
  31.   @@ /usr/LOCAL/lib/php5/cakephp1.2/cake/libs/model/model.php 1908 - 1978
  32.  
  33.   - Parameters [4] {
  34.     Parameter #0 [ <optional> $conditions = NULL ]
  35.     Parameter #1 [ <optional> $fields = Array ]
  36.     Parameter #2 [ <optional> $order = NULL ]
  37.     Parameter #3 [ <optional> $recursive = NULL ]
  38.   }
  39. }

Which gives us a nice summary, including the doc block, and the parameter list, as well as describing where the method lives on the file system.

Well that’s very nice, but what can I do with this.

Well there are few things you can do with Reflection. The easiest is make documentation!, Reflection powers the current API at http://api.cakephp.org. Another good use of reflection would be dynamically invoking methods that a class has. By using reflection you can find out if a method with a given name exists, and invoke it.

Show Plain Text
  1. function trigger($event, $args) {
  2.     foreach ($this->_listeners as $listener) {
  3.         $reflector = new ReflectionClass(get_class($listener));
  4.         $methods = $reflector->getMethods();
  5.         foreach ($methods as $method) {
  6.             if ('on' . $event == $method->getName()) {
  7.                 $method->invoke($listener, $args);
  8.             }
  9.         }
  10.     }
  11. }

Sure you could probably recreate this simple example with method_exists however, reflection will more elegantly handle argument detection for the methods about to be invoked. In addition to looking at methods and properties and invoking methods, you can set static properties. In PHP 5.3 and beyond you will be able to generate closures of methods, as well as coerce properties to public for reading purposes.

Show Plain Text
  1. $object = new MyClass();
  2.  
  3. $myClassReflection = new ReflectionClass('MyClass');
  4. $secret = $myClassReflection->getProperty('__secret');
  5. $secret->setAccessible(true);
  6. var_dump($secret->getValue($object));

If you were to try and write MyClass::$__secret you would get an exception. However, read access allows you to create an effective Object Freezer. In addition to freezing objects, you could easily use Reflection to generate mock objects of classes if you so wished. By introspecting on the methods and required parameters you could effectively emulate any objects interface and generate a compatible object.

So while Reflection may be a fairly specialized tool it has a lot of potential and can really shine under the right applications.

Comments

There are no Comments, Be the First!

Have your say:

*
* (Never published, I promise)
* You can use Textile markup, but be reasonable