4.5 Reflection
4.5.1 Introduction
New to PHP 5 are its reflection capabilities (also referred to as introspection). These features enable you to gather information about your script at runtime; specifically, you can examine your functions, classes, and more. It also enables you to access such language objects by using the available meta-data. In many cases, the fact that PHP enables you to call functions indirectly (using $func(...) ) or instantiate classes directly (new $classname(...) ) is sufficient. However, in this section, you see that the provided reflection API is more powerful and gives you a rich set of tools to work directly with your application.
4.5.2 Reflection API
The reflection API consists of numerous classes that you can use to introspect your application.The following is a list of these items. The next section gives examples of how to use them.
interface Reflector static export(...) class ReflectionFunction implements Reflector __construct(string $name) string __toString() static mixed export(string $name [,bool $return = false]) bool isInternal() bool isUserDefined() string getName() string getFileName() int getStartLine() int getEndLine() string getDocComment() mixed[] getStaticVariables() mixed invoke(mixed arg0, mixed arg1, ...) bool returnsReference() ReflectionParameter[] getParameters() class ReflectionMethod extends ReflectionFunction implements Reflector bool isPublic() bool isPrivate() bool isProtected() bool isAbstract() bool isFinal() bool isStatic() bool isConstructor() bool isDestructor() int getModifiers() ReflectionClass getDeclaringClass() class ReflectionClass implements Reflector string __toString() static mixed export(string $name [,bool $return = false]) string getName() bool isInternal() bool isUserDefined() bool isInstantiable() string getFileName() int getStartLine() int getEndLine() string getDocComment() ReflectionMethod getConstructor() ReflectionMethod getMethod(string $name) ReflectionMethod[] getMethods(int $filter) ReflectionProperty getProperty(string $name) ReflectionProperty[] getProperties(int $filter) mixed[] getConstants() mixed getConstant(string $name) ReflectionClass[] getInterfaces() bool isInterface() bool isAbstract() bool isFinal() int getModifiers() bool isInstance($obj) object newInstance(mixed arg0, arg1, ...) ReflectionClass getParentClass() bool isSubclassOf(string $class) bool isSubclassOf(ReflectionClass $class) mixed[] getStaticProperties() mixed[] getDefaultProperties() bool isIterateable() bool implementsInterface(string $ifc) bool implementsInterface(ReflectionClass $ifc) ReflectionExtension getExtension() string getExtensionName() class ReflectionParameter implements Reflector static mixed export(mixed func, int/string $param [,bool $return = false]) __construct(mixed func, int/string $param [,bool $return = false]) string __toString() string getName() bool isPassedByReference() ReflectionClass getClass() bool allowsNull() class ReflectionExtension implements Reflector static export(string $ext [,bool $return = false]) __construct(string $name) string __toString() string getName() string getVersion() ReflectionFunction[] getFunctions() mixed[] getConstants() mixed[] getINIEntries() ReflectionClass[] getClasses() String[] getClassNames() class ReflectionProperty implements Reflector static export(string/object $class, string $name, [,bool $return = false]) __construct(string/object $class, string $name) string getName() mixed getValue($object) setValue($object, mixed $value) bool isPublic() bool isPrivate() bool isProtected() bool isStatic() bool isDefault() int getModifiers() ReflectionClass getDeclaringClass() class Reflection static mixed export(Reflector $r [, bool $return = 0]) static array getModifierNames(int $modifier_value) class ReflectionException extends Exception
4.5.3 Reflection Examples
As you may have noticed, the reflection API is extremely rich and allows you to retrieve a large amount of information from your scripts. There are many situations where reflection could come in handy, and realizing this potential requires you to play around with the API on your own and use your imagination. In the meanwhile, we demonstrate two different ways you can use the reflection API. One is to give you runtime information of a PHP class (in this case an intrernal class), and the second is to implement a delegation model using the reflection API.
4.5.3.1 Simple Example
The following code shows a simple example of using the ReflectionClass::export() static method to extract information about the class ReflectionParameter. It can be used to extract information of any PHP class:
ReflectionClass::export("ReflectionParameter");
The result is
Class [ <internal> class ReflectionProperty implements Reflector ] { - Constants [0] { } - Static properties [0] { } - Static methods [1] { Method [ <internal> static public method export ] { } } - Properties [0] { } - Methods [13] { Method [ <internal> final private method __clone ] { } Method [ <internal> <ctor> public method __construct ] { } Method [ <internal> public method __toString ] { } Method [ <internal> public method getName ] { } Method [ <internal> public method getValue ] { } Method [ <internal> public method setValue ] { } Method [ <internal> public method isPublic ] { } Method [ <internal> public method isPrivate ] { } Method [ <internal> public method isProtected ] { } Method [ <internal> public method isStatic ] { } Method [ <internal> public method isDefault ] { } Method [ <internal> public method getModifiers ] { } Method [ <internal> public method getDeclaringClass ] { } } }
As you can see, this function lists all necessary information about the class, such as methods and their signatures, properties, and constants.
4.5.4 Implementing the Delegation Pattern Using Reflection
Times arise where a class ( One ) is supposed to do everything another class ( Two ) does and more. The preliminary temptation would be for class One to extend class Two , and thereby inheriting all of its functionality. However, there are times when this is the wrong thing to do, either because there isn't a clear semantic is-a relationship between classes One and Two , or class One is already extending another class, and inheritance cannot be used. Under such circumstances, it is useful to use a delegation model (via the delegation design pattern), where method calls that class One can't handle are redirected to class Two . In some cases, you may even want to chain a larger number of objects where the first one in the list has highest priority.
The following example creates such a delegator called ClassOneDelegator that first checks if the method exists and is accessible in ClassOne ; if not, it tries all other objects that are registered with it. The application can register additional objects that should be delegated to by using the addObject($obj) method. The order of adding the objects is the order of precedence when Class OneDelegator searches for an object that can satisfy the request:
class ClassOne { function callClassOne() { print "In Class One\n"; } } class ClassTwo { function callClassTwo() { print "In Class Two\n"; } } class ClassOneDelegator { private $targets; function __construct() { $this->target[] = new ClassOne(); } function addObject($obj) { $this->target[] = $obj; } function __call($name, $args) { foreach ($this->target as $obj) { $r = new ReflectionClass($obj); if ($method = $r->getMethod($name)) { if ($method->isPublic() && !$method->isAbstract()) { return $method->invoke($obj, $args); } } } } } $obj = new ClassOneDelegator(); $obj->addObject(new ClassTwo()); $obj->callClassOne(); $obj->callClassTwo();
Running this code results in the following output:
In Class One In Class Two
You can see that this example uses the previously described feature of overloading method calls using the special __call() method. After the call is intercepted, __call() uses the reflection API to search for an object that can satisfy the request. Such an object is defined as an object that has a method with the same name, which is publicly accessible and is not an abstract method.
Currently, the code does nothing if no satisfying function is found. You may want to call ClassOne by default, so that you make PHP error out with a nice error message, and in case ClassOne has itself defined a __call() method, it would be called. It is up to you to implement the default case in a way that suits your needs.