Jan 19 2009
PHP Reflection – Unleash the beast
Certainly, Reflection is one of the powerful features in any language if exist. I did once look at the C# reflection I guess few years ago when I just started learning C#. May be first impression is the last impression, I felt that Reflection was not a feature to mess up with so, I just ignored that in C#.
And then came PHP 5. I knew that PHP 5 had Reflection API but I never ever had a look at that part (due to my not so positive encounter with C# Reflection). But as said in the Dark Knight, “Either you die like a hero, or live longer becoming a villain” (I am confused that this is the exact statement but it was similar). So, finally I made my mind to mess up with PHP Reflection couple of days ago.
After going through documentation in php.net, I felt Reflection is quite powerful API in PHP 5 and every developer should at least have a look at it. And it’s also true that it is one of the most under-used features by PHP developers. But now I can guarantee that I am going to use it whenever it’s possible.
After going through PHP Reflection, I am imagining using the concept of Reflection for a plugin based architectures like Facebook and Wordpress. The example I am presenting is far simpler than how Wordpress plugin and Facebook Applications (I don’t have knowledge of any of those platforms yet) actually work.
Code Structure
I have an interface IPlugin which has three method signatures and it is responsible for acting as a contract for other plugins that third party developers will create.
interface IPlugin
{
public static function getName();
public function execute();
public static function pluginIsExecuted();
}
Now lets assume we have two plugins named PluginA and PluginB as:
class PluginA implements IPlugin
{
private static $_isExecuted = false;
public static function getName()
{
return "Plugin A";
}
public function execute() {
echo self::getName()." is executing...<br/>";
self::$_isExecuted = true;
}
public static function pluginIsExecuted() {
return self::$_isExecuted;
}
}
class PluginB implements IPlugin
{
private static $_isExecuted = false;
public static function getName() {
return "Plugin B";
}
public function getMenuItems() {
return array("menu1", "menu2", "menu3");
}
public function execute() {
echo self::getName()." is executing...<br/>";
echo "I am plugin B and I will make your life easy...";
self::$_isExecuted = true;
}
Now we will create a class named PluginExecutor which has three responsibilities i.e. searhing all plugins, listing plugins name and executing plugins.
class PluginsExecutor
{
public static function findPlugins() {
$plugins = array();
foreach (get_declared_classes() as $class) {
$reflectionClass = new ReflectionClass($class);
if ($reflectionClass->implementsInterface('IPlugin')) {
array_push($plugins, $reflectionClass);
}
}
return $plugins;
}
public static function listNames() {
$names = array();
foreach (self::findPlugins() as $plugin) {
if ($plugin->hasMethod('getName')) {
//$reflectionMethod = $plugin->getMethod('getName');
$reflectionMethod = new ReflectionMethod($plugin->getName(), 'getName');
if ($reflectionMethod->isStatic()) {
$name = $reflectionMethod->invoke(NULL);
}
else {
$instance = $plugin->newInstance();
$name = $reflectionMethod->invoke($instance);
}
array_push($names, $name);
}
}
return $names;
}
public static function executePlugins() {
foreach (self::findPlugins() as $plugin) {
if ($plugin->hasMethod('execute')) {
$reflectionMethod = new ReflectionMethod($plugin->getName(), 'execute');
if ($reflectionMethod->isStatic()) {
$name = $reflectionMethod->invoke(NULL);
}
else {
$instance = $plugin->newInstance();
$name = $reflectionMethod->invoke($instance);
}
}
}
}
}
This class uses the PHP Reflection API to perform responsibilities I’ve mentioned earlier.
Now lets do unit tests for our above code.
require('../../simpletest/autorun.php');
require("IPlugin.php");
class Reflectiontest extends UnitTestCase
{
function testPluginsAreFound() {
$expected = array("Plugin A", "Plugin B");
foreach (PluginsExecutor::findPlugins() as $key => $plugin) {
$actual = $plugin->getName();
$this->assertNotEqual($actual, $expected[$key]);
}
}
function testPluginNamesAreNotEmpty() {
//$actual = array();
foreach (PluginsExecutor::findPlugins() as $plugin) {
$actual = $plugin->getName();
$this->assertNotEqual($actual, "");
}
}
function testAllPluginsHaveExecuted() { //This will fail
PluginsExecutor::executePlugins();
//var_dump(PluginA::execute());
foreach (PluginsExecutor::findPlugins() as $plugin) {
$actual = $plugin->getMethod('pluginIsExecuted')->invoke(NULL);
$this->assertTrue($actual);
}
}
}
And Here is the output…
Holy crap! the test failed!!!
It was expected because of this:
If we add the missing line, we will see lovely green color or all our tests will pass.
Conclusion
Here I have used Reflection API for:
- Checking whether the plugins are following the contract by implementing IPlugin interface or not
- Invoking the class method in the runtime
- Checking the type of method (is the method static or not)
- Differentiating user defined classes and built-in classes (get_declared_classes() is not a part of Reflection API but this particular function is used most of the time when dealing with Reflection stuffs)
I have use Simple Test for unit testing my classes because I am quite focused to get into the world of Test Driven Design so, just making some use. I recommend you also use Unit Tests for your apps.
As usual comments and constructive criticisms are very much welcomed.

-
Aditya Sakhuja
-
Prajwal Tuladhar
-
Jani Hartikainen


