Creating dynamic plugins for PHP Classes

I’m interested in hearing how you guys would go about creating classes that support plugins. I’m really keen to hear ways in which you can have plugins respond to events that are triggered inside the class they are plugged into and how you actually load and contain the plugin. But generally, I’m just looking for a healthy discussion on plugin development for PHP Classes :slight_smile:

I’ve just yesterday done something like this. It contains event handlers which trigger responses from the plugins. Obviously, in this little example it’s not even worth writing the code for but on a large scale I can imagin this would give plugin authors incredible control over the class they plug in to.


class baseClass
{
    private $plugins = array();

    public $word = '';

    public function loadPlugin(&$object)
    {
        $plugin_name = $object->identifier;
        $object->loadBaseObject(&$this);
        $this->plugins[$plugin_name] =& $object;
    }

    //This sets of logic in the plugin
    private function triggerEvents($event)
    {
        foreach ($this->plugins as $name => $object)
        {
            if (method_exists($object, $event))
            {
                $this->plugins[$name]->$event();
            }
        }
    }

    private function speak($word)
    {
        $this->word = $word;
        $this->triggerEvents('onSpeak');
        echo $this->word;
    }

    public function &getPlugin($name)
    {
        return $this->plugins[$name];
    }
}

class dummyPlugin
{
    private $baseInstance;

    public $identifier = 'dummy'; //The name of the plugin

    public function loadBaseObject(&$object)
    {
        $this->baseInstance =& $object;
    }

    public function test()
    {
        echo 'Test called...';
    }

    //Only runs when baseClass allows it to
    public function onSpeak()
    {
        $this->baseInstance->word .= '... touched by a plugin';
    }
}

$base = new baseClass;
$base->speak('foo'); //Foo

$base->loadPlugin(new dummyPlugin);

$base->speak('bar'); //bar ... touched by a plugin

$base->getPlugin('dummy')->test(); // Test called

I can see a big problem with the above though, what if a user loads two plugins that each try to alter the same property in some fashion? How would you go about minimizing that risk without forcing users to only load one plugin?

Has anyone got any cool and interesting ideas to do things like above?

Perhaps this would interest you :
http://www.sebastian-bergmann.de/AspectPHP/

Thanks, that looks really awesome :slight_smile: Yeah that’s basically the sort of thing I’d like to acheive but purely in a vanilla PHP installation. It looks as though what I was doing above was almost like a sloppy, procedural way of including pointcuts in the code. I’ll keep reading around this topic in PHP and how other people have attempted AOP-style programming with it.

I guessed this thread wouldn’t get many responses… I posted it on another forum I moderate/admin but it hasn’t had much interest there neither :frowning: I live for this sort of discussion on theory/design.

Have you done similar things yourself?

Here’s something experimental I threw together a year ago. Oh, how this makes me wish PHP had closures…

/**
 * Delegate interface. Describes a kind of refined callback.
 *
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		Sep 6, 2005
 */
interface IDelegate
{
	/**
	 * Invoke Delegate
	 * @param	array	arguments, optional
	 */
	public function invoke($args = array());
}

/**
 * Calls a method on an object upon invocation.
 *
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		Sep 6, 2005
 */
class Delegate implements IDelegate
{
	private $subordinate = NULL;
	private $method = NULL;
	
	public function __construct($subordinate, $method)
	{
		$this->subordinate = $subordinate;
		$this->method = $method;
	}
	
	public function invoke($args = array())
	{
		$this->subordinate = Handle::resolve($this->subordinate);
		$callback = array(&$this->subordinate, $this->method);
		return call_user_func_array($callback, $args);
	}
}
/**
 * "A Handle represents an uninstantiated object that takes the place of a
 * given object and can be swapped out for the object.
 * Implements lazy loading for composing object heirarchies."
 *
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		Jul 12, 2005
 * @see			http://wact.sourceforge.net/index.php/ResolveHandle
 */
class Handle
{
	/**
	 * @var	string	class name
	 */
	protected $class = NULL;
	/**
	 * @var	array	class constructor arguments
	 */
	protected $args = array();
	
	/**
	 * @param	string	class name
	 * @param	array	class constructor arguments, optional
	 */
	public function __construct($class, $args = array())
	{
		$this->class	= (string)	$class;
		$this->args		= (array)	$args;
	}
	
	/**
	 * Resolves a Handle; replaces a Handle instance with its identified class
	 * @param	object	Handle
	 * @return	object
	 */
	static public function resolve($handle)
	{
		if ($handle instanceof self)
		{
			$handle = call_user_constructor_array($handle->getClass(), $handle->getArgs());
		}
		return $handle;
	}
	
	public function getClass() { return $this->class; }
	public function getArgs() { return $this->args; }
}
/**
 * Simple Event: attach a Delegate and trigger.
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		1.10.2005
 */
class Event
{
	private $listener = NULL;
	
	/**
	 * @param	object	IDelegate
	 * @return	boolean	overridden
	 */
	public function attach(IDelegate $listener)
	{
		$overridden = isset($this->listener);
		$this->listener = $listener;
		return $overridden;
	}
	
	/**
	 * @param	array	invocation arguments
	 * @return	mixed	results
	 */
	public function trigger($args = array())
	{
		return $this->listener->invoke($args);
	}
	
	/**
	 * Get attached listener
	 * @return	object	IDelegate
	 */
	public function getAttached()
	{
		return $this->listener;
	}
}
/**
 * Multicast Event: attach multiple Delegates.
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		1.10.2005
 */
class MulticastEvent extends Event
{
	private $listeners = array();
	
	/**
	 * @param	object	IDelegate
	 */
	public function attach(IDelegate $delegate)
	{
		$this->listeners[] = $delegate;
	}
	
	/**
	 * @param	array	invocation arguments
	 * @return	array	results
	 */
	public function trigger($args = array())
	{
		$return = array();
		foreach($this->listeners as $listener)
		{
			$return[] = $listener->invoke($args);
		}
		return $return;
	}
	
	/**
	 * Get attached listeners
	 * @return	array	IDelegate
	 */
	public function getAttached()
	{
		return $this->listeners;
	}
}
/**
 * Handle groups of events
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		26.9.2005
 */
class EventHandler
{
	private $args = array();
	private $events = array();
	
	/**
	 * EventHandler consturctor
	 * @param	mixed	default invocation argument
	 * ...
	 */
	public function __construct()
	{
		$this->args = func_get_args();
	}
	
	/**
	 * Attach a listener to an event
	 * @param	string	event name
	 * @param	object	IDelegate
	 */
	public function attach($event, IDelegate $listener)
	{
		if(empty($this->events[$event]))
		{
			$this->events[$event] = $this->getEvent();
		}
		$this->events[$event]->attach($listener);
	}
	
	/**
	 * Trigger an event
	 * @param	string	event name
	 * @param	array	arguments, optional
	 */
	public function trigger($event, $args = array())
	{
		if(empty($args))
		{
			$args = $this->getArgs();
		}
		$return = NULL;
		if(!empty($this->events[$event]))
		{
			$return = $this->events[$event]->trigger((array) $args);
		}
		return $return;
	}
	
	/**
	 * @return	array	default invocation arguments
	 */
	protected function getArgs()
	{
		return $this->args;
	}
	
	/**
	 * @return	object	a fresh Event
	 */
	protected function getEvent()
	{
		return new Event;
	}
	
	/**
	 * Shortuct for trigger()
	 */
	public function __call($event, $args)
	{
		return call_user_func_array(array($this, 'trigger'), array($event, $args));
	}
	
	/**
	 * Shortcut for attach()
	 */
	public function __set($event, $args)
	{
		return call_user_func_array(array($this, 'attach'), array($event, $args));
	}
}
/**
 * Extend EventHandler to use MulticastEvents
 *
 * @author		Ezku (dmnEe0@gmail.com)
 * @since		26.9.2005
 */
class MulticastEventHandler extends EventHandler
{
	protected function getEvent()
	{
		return new MulticastEvent;
	}
}

It’s supposed to be used like this (a total nonsense example, bear with me):

$events = new EventHandler;
$events->onload = new Delegate(new Handle('Controller'), 'execute');
$events->onload(Context::getInstance); // lazy-loads and creates a Controller instance, calls execute() with the arguments given

Here’s a slightly more elaborate example, albeit on an older version of the code.

The whole idea is hacky to begin with, but aspect oriented programming in PHP seems doubly so to me. Just gives me a bad vibe. :slight_smile:

Edit:

If you intend to use this, it would likely be a good idea to implement call_user_constructor_array() ;), as well as get rid of Handle in favour of a LazyDelegate. Delegate using a static method in Handle really creates an ugly dependency.

~Ezku, I love that code. Yeah, you refer to it as “hacky” but it’s damn good given the language it’s implemented in. If we all sat around building forms and validating POST data all day we’d never move anywhere. Do you mind if I copy that code and play around with it a little bit myself?

Don’t mind at all. It’s not like it wouldn’t be trivial once you got the idea. :wink: The idea is basically from WACT, as far as I can remember, but I was thinking more of Javascript events when I implemented my version.

JavaScript was exactly what was on my mind when I got thinking about plugins supporting event handlers yesterday :slight_smile: Although I love PHP to bits, the more advanced theory I learn, the more I wish PHP could “do more”.

Thanks, I will play around with this. I followed the code in your example above, but I’d just like to play around with it in a more practical situation (a mailer class I’m working on).

I would end up going for an eventhandling / observer pattern…

I’m aware of the fact that in .NET delegates are typesafe callbacks / eventhandlers but i’m still not sure how useful this concept is for PHP applications.

Hi tim :smiley: Fancy meeting you here :stuck_out_tongue:

Yeah you’re right, it’s not really well suited for PHP Applications but there’s still times when I’ve thought it would be useful to be able to trigger events. Still pretty cool to toss the ideas around all the same.

That’s exactly how I feel also about event driven apps in PHP and my reason not to go further with PRADO, which I actually liked because of the component based architecture.

But isn’t this exactly that, only isolated to a separate component instead of being an integral part of the observers and observees themselves? I’m asking merely out of curiosity.

Must say i agree Ezku… nice code …

Sorry to dig this up again but you got me thinking about closures and I’ve been bored today. I came up with a nast dirty evil little hack that sort of works like a basic closure… I’d never use the concept I doubt though - I just like to experiment a lot with ideas :slight_smile:


function myClosure($arg1, $arg2)
{
	$localVar = 8;

	$exampleReturned = create_function('$innerArg', '
	return (('.$arg1.' + '.$arg2.')/($innerArg + '.$localVar.'));
	');

	return $exampleReturned;
}

$globalFunc = myClosure(2, 4);

echo $globalFunc(4); //0.5

OK ignore me, that’s not even worth looking at, you can’t write back again so it pointless.

It’s possible to make closures with create_function. I have used the following implementation, if you’re interested :


function curry($fnc, $arg /* [, $arg ... ] */) {
	if (is_string($fnc)) {
		$fnc = addcslashes($fnc, "\\0");
	}
	$args = func_get_args();
	array_shift($args);
	$callargs = Array();
	foreach ($args as $arg) {
		$callargs[] = var_export($arg, TRUE);
	}
	$lambda = sprintf("\\$args = func_get_args();\
return call_user_func_array(\\"%s\\", array_merge(Array(%s), \\$args));", $fnc, implode(",",$callargs));
	return create_function('', $lambda);
}

It’s limited by only accepting primitives (eg. no objects or resources), but if you wanted, I suppose that could be archieved by storing in a global location. I don’t use it a lot though, since functional programming is really a bit half-arsed in PHP

You know almost every time I create a plugin API for a project, I do it differently. A text book observer/obervable works well for cases where you have an object that you want to make plugins for, but not so well for cases where you may have many objects you want accessible to plugins or even entire applications [at least not the textbook sort of case].

The one time for a generic service plugin API that covered an entire application, I had the service plugin, which loaded and called the plugins, initialized it and placed it in a registry, and then just placed virtual calls to it wherever I felt they were needed or would be useful. I used __call() magic, so the calls were in the form $service_plugins->doSomeAction($param-1, … $param-n);

d11wtq, I was intrigued by your first example which gives the plugin a base object it can work with. So expanding upon that, and the idea of point cuts, I just wanted to see what I could come up with this time around, so just whipped this up. Mind you I haven’t had much time to think about it, and this approach is bound to be flawed in several ways, but it was a fun exercise.

Here is the real heart of it, first the Plugins base class, made abstract to allow more than one “plugin group” in the app:

abstract class Plugins {
    protected $plugins = array();

    public function __construct($plugins) {
        foreach ($plugins as $plugin) {
            $plug = new $plugin();
            if ($plug instanceof Plugin) {
                $this->plugins[] = $plug;
            }
        }
    }

    public function __call($method, $args) {
        $baseObject = false;

        if ((sizeof($args) > 0) && is_object($args[0])) {
            $baseObject = array_shift($args);
        }

        foreach ($this->plugins as $key => $plugin) {
            if (!method_exists($plugin, $method)) {
                continue;
            }

            if ($baseObject !== false) {
                $this->plugins[$key]->loadBaseObject($baseObject);
            } else {
                $this->plugins[$key]->loadBaseObject(NULL);
            }

            call_user_func_array(array(&$this->plugins[$key], $method), $args);
        }
    }
}

The the individual Plugin core:

abstract class Plugin {
    protected $baseInstance = NULL;

    public function loadBaseObject($object = NULL) {
        $this->baseInstance = $object;
    }

    public function __call($method, $args) {
        if ($this->baseInstance === NULL) {
            return false;
        }
        if (preg_match('/^do(\\w+)$/', $method, $matches)) {
            $method = strtolower($matches[1]{0}) . substr($matches[1], 1);
            $return = NULL;

            $plugin_method = ucfirst($method);
            $plugin_args = $args;
            array_unshift($plugin_args, $this->baseInstance);

            call_user_func_array(array(&$this->baseInstance->plugins, 'before' . $plugin_method), $plugin_args);

            if (method_exists($this, $method)) {
                $return = call_user_func_array(array(&$this, $method), $args);
            }

            call_user_func_array(array(&$this->baseInstance->plugins, 'on' . $plugin_method), $plugin_args);

            call_user_func_array(array(&$this->baseInstance->plugins, 'after' . $plugin_method), $plugin_args);

            return $return;
        }
    }
}

And then the core for “Plugabble” objects:

abstract class Pluggable {
    public $plugins = array();

    public function __construct($plugins) {
        $this->plugins = $plugins;
    }

    public function __call($method, $args) {
        if (preg_match('/^do(\\w+)$/', $method, $matches)) {
            $method = strtolower($matches[1]{0}) . substr($matches[1], 1);
            $return = NULL;

            $plugin_method = ucfirst($method);
            $plugin_args = $args;
            array_unshift($plugin_args, $this);

            call_user_func_array(array(&$this->plugins, 'before' . $plugin_method), $plugin_args);

            if (method_exists($this, $method)) {
                $return = call_user_func_array(array(&$this, $method), $args);
            }

            call_user_func_array(array(&$this->plugins, 'on' . $plugin_method), $plugin_args);

            call_user_func_array(array(&$this->plugins, 'after' . $plugin_method), $plugin_args);

            return $return;
        }
    }
}

The way this works, is that if you call doMethodName() on a Pluggable object, it will set itself as the current base object for the plugin group the object is using, call beforeMethodName() across the plugin group, call & get result of methodName() in the object [if it exists], call onMethodName() across the plugin group, call afterMethodName() across the plugin group, and finally return the result. And then basically the same is true of Plugins as well, so in addition to being responsive to Pluggable objects, Plugins can also be responsive to each other as well. And the Pluggable’s method doesn’t have to exist, so you can make virtual event/action calls.

And then here is a quite simple & silly example I did of how one could implement the above API.

First a “plugin group”. I’m only going to have one group in this example, but naturally one could have as many as they wished. I also don’t have anything in this group such as specific methods for the group on whole, but you naturally can and a more complex example probably would:

class SpeakerPlugins extends Plugins { }

Then a simple ClassRoom that is Plugabble:

class ClassRoom extends Pluggable {

    public $noise = false;

}

Then a Pluggable speaker:

class Speaker extends Pluggable {

    public $speaker;
    public $ended = false;

    public function __construct($speaker, $plugins) {
        parent::__construct($plugins);
        $this->speaker = $speaker;
    }

    protected function speak($word) {
        echo "{$this->speaker}: $word<br />";
    }

    public function speakNormally($word) {
        $this->speak($word);
    }

    public function speakSoftly($word) {
        $this->speak('<small>'.strtolower($word).'</small>');
    }

    public function speakLoudly($word) {
        $this->speak(strtoupper($word));
    }

    public function endSpeech($text) {
        if ($this->ended === true) {
            return;
        }
        if ($text !== false) {
            $this->doSpeakNormally($text);
        }
        $this->ended = true;
    }

}

Then a Speech, which I didn’t make Pluggable:

class Speech {

    public $speaker;

    public function __construct(Pluggable $speaker) {
        $this->speaker = $speaker;
    }

    public function giveSpeech($texts, $type = 'normal') {
        $this->speaker->doStartSpeech();

        if (!is_array($texts)) {
            $texts = array($texts);
        }

        foreach ($texts as $text) {
            switch ($type) {
                case 'soft':
                    $this->speaker->doSpeakSoftly($text);
                    break;
                case 'loud':
                    $this->speaker->doSpeakLoudly($text);
                    break;
                case 'normal':
                default:
                    $this->speaker->doSpeakNormally($text);
                    break;
            }
        }

        $this->speaker->doEndSpeech('Thank You.');
    }

}

Then an abstract SpeakerPlugin for the SpeakerPlugins group:

abstract class SpeakerPlugin extends Plugin {
    public $speaker;

    protected function speak($text) {
        echo "{$this->speaker}: $text<br />";
    }
}

Then a Bell SpeakerPlugin:

class Bell extends SpeakerPlugin {

    public $speaker = 'Bell';

    public function onBeginClass() {
        $this->speak('<<<bell rings>>>');
        $this->baseInstance->noise = false;
    }

    public function onEndClass() {
        $this->speak('<<<bell rings>>>');
    }

    protected function speak($text) {
        echo "<strong>$text</strong><br />";
    }

}

Then a Teacher SpeakerPlugin:

class Teacher extends SpeakerPlugin {

    public $speaker = 'Teacher';

    public function afterBeginClass() {
        if ($this->baseInstance->noise === true) {
            $this->speak("Hush children! The bell has rung.");
            $this->baseInstance->noise = false;
        }
    }

    public function afterEndClass() {
        if ($this->baseInstance->noise === true) {
            $this->speak("May I have your attention for a moment please!");
            $this->baseInstance->noise = false;
        }
        $this->speak("Your reports are due 1 week from today. Have a nice weekend");
    }

    public function onStartSpeeches() {
        if ($this->baseInstance->noise === true) {
            $this->speak("Shhh!");
            $this->baseInstance->noise = false;
        }
    }

    public function beforeStartSpeech() {
        $this->speak("{$this->baseInstance->speaker} is now going to give a speech.");
    }

    public function afterSpeakSoftly($word) {
        $this->doReprimandSpeaker("Speak Up {$this->baseInstance->speaker}!");
        $this->baseInstance->doSpeakNormally($word);
    }

    public function afterSpeakLoudly($word) {
        $this->doReprimandSpeaker("Thank you {$this->baseInstance->speaker}, but next time don't yell.");
        $this->baseInstance->doEndSpeech(false);
    }

    protected function reprimandSpeaker($text) {
        $this->speak($text);
    }

}

And finally Students SpeakerPlugin:

class Students extends SpeakerPlugin {

    public $speaker = 'ClassRoom';

    protected $canApplaud = true;

    public function beforeBeginClass() {
        $this->speak("<<<giggling & loud talking>>>");
        $this->baseInstance->noise = true;
    }

    public function afterBeginClass() {
        $this->speak("<<<giggling & loud talking>>>");
        $this->baseInstance->noise = true;
    }

    public function afterEndClass() {
        $this->speak("<<<giggling & loud talking>>>");
        $this->baseInstance->noise = true;
    }

    public function beforeStartSpeeches() {
        $this->speak("<<<giggling>>>");
        $this->baseInstance->noise = true;
    }

    public function onReprimandSpeaker() {
        $this->speak("<<<giggles>>>");
    }

    public function beforeEndSpeech($text) {
        if ($this->baseInstance->ended === true) {
            $this->canApplaud = false;
        } else {
            $this->canApplaud = true;
        }
    }

    public function afterEndSpeech($text) {
        if (($text !== false) && $this->canApplaud) {
            $this->speak("<<<applause>>>");
        }
    }

}

Whew.

And now putting it all together something like:

$load_plugins = array(
    'Students',
    'Teacher',
    'Bell',
);

$plugins = new SpeakerPlugins($load_plugins);


$class = new ClassRoom($plugins);

$class->doBeginClass();

echo '<hr />';

$class->doStartSpeeches();

echo '<hr />';

$speech = new Speech(new Speaker('Bob', $plugins));
$speech->giveSpeech('Foo Bar Foo Foo.', 'soft');

echo '<hr />';

$speech = new Speech(new Speaker('Linda', $plugins));
$speech->giveSpeech(array(
    'Foo Bar Foo Foo.',
    'Foo Bar Foo Foo.',
    'Foo Bar Foo Foo.',
    'Foo Bar Foo Foo.',
));


echo '<hr />';

$speech = new Speech(new Speaker('John', $plugins));
$speech->giveSpeech('Foo Bar Foo Foo.', 'loud');

echo '<hr />';

$class->doEndSpeeches();

echo '<hr />';

$class->doEndClass();

Which outputs something like:

ClassRoom: <<<giggling & loud talking>>>
<<<bell rings>>>
ClassRoom: <<<giggling & loud talking>>>
Teacher: Hush children! The bell has rung.


ClassRoom: <<<giggling>>>
Teacher: Shhh!

Teacher: Bob is now going to give a speech.
Bob: foo bar foo foo.
Teacher: Speak Up Bob!
ClassRoom: <<<giggles>>>
Bob: Foo Bar Foo Foo.
Bob: Thank You.
ClassRoom: <<<applause>>>

Teacher: Linda is now going to give a speech.
Linda: Foo Bar Foo Foo.
Linda: Foo Bar Foo Foo.
Linda: Foo Bar Foo Foo.
Linda: Foo Bar Foo Foo.
Linda: Thank You.
ClassRoom: <<<applause>>>

Teacher: John is now going to give a speech.
John: FOO BAR FOO FOO.
Teacher: Thank you John, but next time don’t yell.
ClassRoom: <<<giggles>>>


<<<bell rings>>>
ClassRoom: <<<giggling & loud talking>>>
Teacher: May I have your attention for a moment please!
Teacher: Your reports are due 1 week from today. Have a nice weekend

If you play around with the example a bit, you’ll notice that in addition to being responsive to the Pluggable objects, particularly the Teacher & Students Plugins are quite responsive to each other too. For example, if the students aren’t being noisy, the Teacher doesn’t have to keep telling them to be quite.

I think this may be one of my favorite implementations to date, though I haven’t had a lot of time to think it over or test it in a real scenario, so I reserve the right to take that comment back :wink:

~dreamscape thanks for the appreciation of how it was implemented (I’ve used my first version in Swift and it seems to be working nicely for the plugins I’ve been mucking about with :slight_smile:

:eek: Long post! Started reading the code but I’m pretty exhausted from decorating all day so I’m gonna have to look over it in more detail after a soak and a coffee :wink: Love the example you used! It’s actually quite a nice way to describe exactly how the everything communicates with each other. And yes, it’s brilliant that everything only runs as and when it needs to - and in fact it doesn’t have to run at all if it doesn’t want to… I think it’s really flexible and I’ll no doubt keep using something like this, alveit a little more refined as I suss out any pitfalls etc.

I’ll probably post again after a second look at your code :smiley:

Yes already that I’ve had a little more time to think about the code, a few areas that jump out to me that could be refined. For the first revision, and not spending much time on it, it’s not bad, but definitely has room for improvement. I’m actually really glad I ran across this thread, because I’ve written a number of plugin API systems, but haven’t yet been able to come up with a “universal” plugin API that will adapt well to all the situations I’ve come across a need for it in. But I think with some refinement, that perhaps we could be on to something here that might come pretty darn close to just that, for me anyways.

One thing would be reversing the plugin order or giving a reversal option on the “after” hook.

Another would be giving the “on” hook the ability to use and/or change the return value for the main method call. This would be both useful and help to differentiate it from the “after” hook since right now they are only different in name (and really 2 after calls). There are several ways to allow this, but my thought right now is to append the return value to the end of the arguments passed to the “on” hook. This way, then each “on” hook implementation would have the option of using the return value, changing it by using a reference in the method definition, or just simply ignoring it.

I also think that just calling the method name on the plugins could become too limited, especially if you have many Pluggables, so I’ve been thinking that instead of method names in the Plugins such as beforeMethodName() that perhaps beforeClassNameMethodName() may be better in order to avoid collisions.

Finally, another idea I’ve had cross my mind would be to make a Pluggable that you could pass an object to and plug into the object you pass to it, so that all the classes you want to be “pluggable” don’t have to explicitly be Pluggable, which would work far better for broader scope plugins, such as generic service plugins which could encompass an entire application or an entire system in the application, instead of just a few objects like the example I did. The idea would be a call similar to: $pluggable->doMethodName($object, $param-1, … $param-n); which would effectively allow any object to become “pluggable” dynamically at any point in time.

PS. Sorry that the post was quite long. The actual plugin API is quite short, but the example is 2 Pluggables and 3 Plugins, which is longer than I anticipated making, but I wanted to give an example that showed plugins hooking into multiple objects and also them hooking into each other but didn’t leave you wondering what the usefulness was (although maybe it does leave you wondering that).

So the Pluggable - which is the main feat of this thing - is a sort of automatic decorator. The only difference is that rather than manually writing a decorator for the purpose, you use __call to dispatch on the decorated object ?
What’s the reason for having both the class Pluggable and Plugin ? Couldn’t the Pluggable have an array of listeners, and you could then register with that ? (And gt rid of Plugin)
I realize that the actual “wiring” would be more verbose, but that could then be hidden away somewhere else (or somehow be automagically resolved).

Ah let’s see… hmm… Well I don’t pretend to be all that familiar with the decorator pattern, but I guess you can kind of look at it like that. Though to me I think of it more as an automatic point cutter, making cut points before entering the method and after leaving it, and now that I’ve thought some, ideally one that enables using or manipulating the return value.

Uh… ok… the automatic wiring gets a little hairy, so let’s see if I can map this out here. On a Pluggable, __call dispatches a before point cut (I’m just going to use the term “point cut” here, even though this really isn’t AOP, I think we can still call them point cuts). Then it dispatches to the called method of the object, the Pluggable. Then it dispatches to the after point cuts.

These point cut dispatches are made to the Plugins which the Pluggable has loaded. On a Plugins collection, __call checks if an object was passed in as the 1st parameter. If so, it sets this object as the current “base object” for each Plugin and removes it from the parameter array. Then it loops through each plugin, and if the requested point cut exists in the Plugin, it sets the current base object for that Plugin, and then dispatches the call to the Plugin.

Let’s see if I can explain this in any meaningful way :wink: … I’ll do all 3 class types, to help me along. First we have Pluggable, which is an object that you want to be able to plug into. Next we have Plugins, which is a collection of Plugins, or group if you prefer. For a small application you may just have one type of Plugins, as I did in the example, but let’s say something like eCommerce, you may have PaymentPlugins, ShippingPlugins, ProductPlugins, ServicePlugins, etc… And finally a Plugin is and individual plugin. Then, the reason for the “Plugin” base class that plugins must extend is that they have some common behavior, such as loading the base object they are currently plugging into (which changes on the fly as the program executes), as well as some __call() magic that basically allows plugins to also be pluggable, to allow not just Plugin to Pluggable interaction, but also Plugin to Plugin interaction. I’ve worked on a few Plugin APIs before as I said, and letting Plugins be able to plug into each other as well as to the application is really a cool thing, and at times quite necessary. And just generally makes the plugins more responsive since they can be aware of actions of other plugins (maybe this plugin over here has already done something, so that plugin over there doesn’t need to).

As for getting rid of Plugin, my thought is actually to minimize Pluggable. Pluggables are objects you want the ability to hook into, so these are likely the main objects of your application, or at least some, and naturally you don’t want to be forced to have them all extend Pluggable. So I’m thinking now, to have a DynamicPluggable which would allow any object to become pluggable itself, by passing the object to it.

So from the example instead of:

$plugins = new SpeakerPlugins($load_plugins);

$class = new ClassRoom($plugins);

$class->doBeginClass();

I’m thinking something like:

$school = new DynamicPluggable(new SpeakerPlugins($load_plugins));

$class = new ClassRoom();

$school->doBeginClass($class);

Here, ClassRoom is no longer a Pluggable. The pluggable calls are a little more awkward, being the form $pluggable->doSomeMethod($object-to-plug-into, $params…); rather than $object-to-plug-into->doSomeMethod($params…); but ultimately I think that the former is more flexible.

If done right, that could also actually get rid of the need for Plugin too, and really any object could then become either a Pluggable or a Plugin or both at any point.