Prajwal Tuladhar’s Blog
 
programming, life and some random thoughts

Jan 19 2009

PHP Reflection – Unleash the beast

Published by Prajwal Tuladhar at 1:20 am under PHP

Heath Ledger as the Joker.  The Joker's scruff...    

Image via Wikipedia

 

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…

simple-test-result

Holy crap! the test failed!!!

It was expected because of this:

missing-line

If we add the missing line, we will see lovely green color or all our tests will pass.

passed-test

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.

Technorati Tags:

  • Aditya Sakhuja
    A very good post indeed. ! Btw, since you have tried with reflection, could you figure out a way to access private methods of a class using reflection ?

    thanks,
    Aditya
  • Thanks!
    But I am sorry to say that reflection won't let you invoke private, protected and and/or abstract methods. Generally, unit testing frameworks can be considered nice examples of Reflection in use and if you have used any of such frameworks in any languages, you may have noticed that only public methods are allowed to be used. This is because, reflection can only access public methods.

    You can get in depth information about invoking methods in reflection here: http://php.net/manual/en/language.oop5.reflecti...
  • This is quite an interesting approach. Do you know what kind of performance implications this will cause? From what I understand, PHP's reflection isn't exactly very fast, so going through all declared classes (there could be LOTS if you're using something like Zend Framework) may be pretty slow.

    Also, why don't you simply have a plugin registry object or something, which is available to user plugins, which can be then registered with it? I believe this would be a more optimal way to achieve this, as it would not require a full scan of all classes, not to mention that it would be easier to control which plugins actually get executed.
blog comments powered by Disqus

RSS Feed
Subscribe by email
Follow me @ Twitter