Who implemented this method?!

It’s been a while since I wrote anything on this blog, so I thought it was about time to get back on the scene! Today I’ll write a post about how to figure out which class actually implemented a method in PHP.

Consider the following classes:

<?php
abstract class My_Plugin_Abstract {
    public function onLoad() {}
    public function onConnect() {}
    public function onTick() {}
    public function onExit() {}
}

class My_Plugin_Something extends My_Plugin_Abstract {
    public function onLoad() {
        // Do something magic on load
    }
}

class My_Plugin_Magic extends My_Plugin_Abstract {
    public function onTick() {
        // Do some magic on every tick
    }
}

class My_Plugin_End extends My_Plugin_Abstract {
    public function onExit() {
        // Do something fun on exit
    }
}
&#91;/sourcecode&#93;

As you can see we have an abstract class with some non-abstract methods. The plugins can then choose which of the methods to implement.

Now, let's imagine that we have an application that runs in a loop. In each iteration of the loop we will fetch an event that the plugins can hook onto. As you can see from the code above the plugins only implement one method each, and not all four that the abstract plugin has. Since we might only want to run the methods that is actually implemented in a plugin we need to figure out which class that implemented it; the plugin itself or the abstract plugin.

PHP has a couple of functions that does something similar: <a href="http://www.php.net/method_exists" target="_blank">method_exists()</a> and <a href="http://www.php.net/get_class_methods" target="_blank">get_class_methods()</a>. The problem is that whenever a class extends another one, all methods of the parent class is available in the child class (with some exceptions), so method_exists() for instance will return boolean true even if the child did not implement the method.

To be able to do what we want we have to use the <a href="http://www.php.net/reflection">reflection API</a>. By using this we can "reverse-engineer" our classes and find out some more about which methods to execute.

Back to our application.


<?php
// Register plugins and use the class names as keys
$plugins = array();
$plugins&#91;'My_Plugin_Something'&#93; = new My_Plugin_Something();
$plugins&#91;'My_Plugin_Magic'&#93; = new My_Plugin_Magic();
$plugins&#91;'My_Plugin_End'&#93; = new My_Plugin_End();

// Main loop
while (true) {
    // Fetch an event
    $event = magicFunctionThatReturnsAnEvent(); // Imagine this returns "Load", "Connect", "Tick" or "Exit"

    // Method to execute
    $method = 'on' . $event;

    // Loop through the plugins and run the current method
    foreach ($plugins as $className => $plugin) {
        $plugin->$method();
    }
}

Imagine that the loop runs 4 times, and each time we get a different event. This means that the following function calls have been generated:

My_Plugin_Something::onLoad
My_Plugin_Magic::onLoad // This is implemented
My_Plugin_End::onLoad
My_Plugin_Something::onConnect
My_Plugin_Magic::onConnect
My_Plugin_End::onConnect
My_Plugin_Something::onTick // This is implemented
My_Plugin_Magic::onTick
My_Plugin_End::onTick
My_Plugin_Something::onExit
My_Plugin_Magic::onExit // This is implemented
My_Plugin_End::onExit

Out of 12 function calls, only three are of use. If we extend the foreach loop a little, we can do something like this:

foreach ($plugins as $className => $plugin) {
    $ref = new ReflectionMethod($className, $method);
    if ($ref->getDeclaringClass()->name === $className) {
        $plugin->$method();
    }
}

First we make a ReflectionMethod object by specifying the name of the plugin class and then the method. The reflection object gives us loads of methods to use, but the one we are interested in is the one called getDeclaringClass() which gives us a ReflectionClass object that is a reflection of the class that declared the method. In most cases above the declaring class is My_Plugin_Abstract which only holds placeholders for the methods that the plugins can implement.

If we run our application again the following methods are run:

My_Plugin_Magic::onLoad // This is implemented
My_Plugin_Something::onTick // This is implemented
My_Plugin_Magic::onExit // This is implemented

Looks good! But (there had to be one right?), using the reflection API costs quite a bit. In most cases it’s faster just to make all the function calls above instead of using reflection to check the declaring class and so forth. In some cases it might be worth using reflection for this, and if you have such a case at least you know how to do it. You can thank me later!

Advertisements
This entry was posted in Technology and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s