Page 1 of 1
Writing dynamic plugins for PHP Classes/OOP
Posted: Thu May 11, 2006 4:11 am
by Chris Corbyn
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
I've just yesterday done something like this:
Code: Select all
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
Posted: Thu May 11, 2006 11:50 am
by Chris Corbyn
I've just had an interesting read of something to do with
AspectPHP.
http://www.sebastian-bergmann.de/Aspect ... tcuts.html
It looks as though some of the ideas I used above are vaguely like I was trying to create pointcuts in the code even though I didn't know that's what I was doing
I was thinking about how you'd go about intersecting points in the logic and method calls etc but PHP is not really suited for that sort of coding I guess (hence the lack of response to this thread?).
Posted: Thu May 11, 2006 2:31 pm
by Christopher
I have a couple of thoughts. I think I mentioned awhile back about not liking the loadBaseObject() fuctionality. It is usual for the plugin to be passed any data it needs when the callback occurs -- often the calling object itself (i.e. $this). I think that simplfies things.
Regarding the interface it seems like there are three ways to go:
1. a single plugin that is passed which point/state from which it is being called
2. a single plugin with a method per plugin point
3. an individual plugin for each plugin point
I think usage determines which one to use.
Posted: Thu May 11, 2006 4:26 pm
by Chris Corbyn
arborint wrote:I have a couple of thoughts. I think I mentioned awhile back about not liking the loadBaseObject() fuctionality. It is usual for the plugin to be passed any data it needs when the callback occurs -- often the calling object itself (i.e. $this). I think that simplfies things.
Regarding the interface it seems like there are three ways to go:
1. a single plugin that is passed which point/state from which it is being called
2. a single plugin with a method per plugin point
3. an individual plugin for each plugin point
I think usage determines which one to use.
Ah you're quite right about loadBaseObject() being a bit off-ish. In fact I was just think the same earlier but I wrote a little interface to make things more obvious all the same, it's still a bit whacky... passing the object to be used locally would avoid that "recursive object strcuture" and make the logic a little more readable perhaps.
Little did I know that I'm basically trying to go beyond OOP and work with AOP in PHP (which of course isn't an AOP language).
The thing with me is I've never written anything that's designed to be pluggable before so this is all new to me and I'm sort of exploring if you like
Your points:
1. It's clean but it doesn't really allow you to do very much with a single plugin - then again, that's probably a good thing - a plugin should probably do one specific task and that's it. How might you code this if it needs to run multiple times without user-interaction though?
2. This would (perhaps) solve an issue I was thinking regarding "clashes" with multiple plugins... it still feels a little restrictive though.
3. Same sort of thing as above but better factored.
I think I've gotten into the wrong frame of mind from the outset. JavaScript was on my mind, hence my consideration for event handlers.
OK, now sit down:
"Forgive me for I have sinned dear lord...."

I've posted on SitePoint
One guy posted this however which also realtes with my way of of thinking about Javascript events (it's hacky like my stuff too though - just a lot more lengthy).
Ezku on SitePoint wrote:Here's something experimental I threw together a year ago. Oh, how this makes me wish PHP had closures...
Code: Select all
/**
* 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);
}
}
Code: Select all
/**
* "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 [url]http://wact.sourceforge.net/index.php/ResolveHandle[/url]
*/
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; }
}
Code: Select all
/**
* 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;
}
}
Code: Select all
/**
* 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):
Code: Select all
$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.

Posted: Thu May 11, 2006 6:03 pm
by Christopher
d11wtq wrote:Little did I know that I'm basically trying to go beyond OOP and work with AOP in PHP (which of course isn't an AOP language).
The thing with me is I've never written anything that's designed to be pluggable before so this is all new to me and I'm sort of exploring if you like

I think you have gone a little astray with AOP -- you just need plugins.
d11wtq wrote:Your points:
1. It's clean but it doesn't really allow you to do very much with a single plugin - then again, that's probably a good thing - a plugin should probably do one specific task and that's it. How might you code this if it needs to run multiple times without user-interaction though?
2. This would (perhaps) solve an issue I was thinking regarding "clashes" with multiple plugins... it still feels a little restrictive though.
3. Same sort of thing as above but better factored.
None of the points I posted have any less capabilities, they only have different interfaces. You can implement anything in anyone of them. They are just more or less convenient depending on the useage. The main decision points are: one or many plugins, and if one then state param or call method. I think supporting chains of plugins is a little overboard given that it will probably never be used. But a simple array based chain would be enough.
I think you have jumped the shark a little with your last release though.
PS - Ezku is a good guy, but he is right -- that is old code from all over the place (WACT, Lastcraft, Skeleton, etc.).
Posted: Sun May 14, 2006 1:43 pm
by santosj
Well, what do you know, I was thinking of implementing a similar idea with a little different method. Using SPL Observer classes, but I suppose I could just rip your code instead. However I feel that if you are using PHP 5, then you should use the Reflection Classes.
Posted: Sun May 14, 2006 6:31 pm
by Chris Corbyn
santosj wrote:Well, what do you know, I was thinking of implementing a similar idea with a little different method. Using SPL Observer classes, but I suppose I could just rip your code instead. However I feel that if you are using PHP 5, then you should use the Reflection Classes.
I'm not really familiar with the reflection classes, although I've seen a little example code with them... I'll have a look thanks

If the code changed enough to depend on these classes it would mean that my PHP4 conversion, which at present is syntactical only, would go out of the window.
Posted: Mon May 15, 2006 1:37 pm
by santosj
d11wtq wrote:I'm not really familiar with the reflection classes, although I've seen a little example code with them... I'll have a look thanks

If the code changed enough to depend on these classes it would mean that my PHP4 conversion, which at present is syntactical only, would go out of the window.
This is true, but PHP 5 is the wave of the future, so forget about the old man PHP 4.
I've found that the Reflection classes are pretty straight forward and easy to work with. It wouldn't be hard to create your own code without going to tutorial sites.
Posted: Mon May 15, 2006 2:48 pm
by Christopher
I am not sure that Reflection is the way to go with this. Plugins needs a explicit interface that plugin builders can follow -- and predictable behavior. Using reflection would add some intellegence where none is really needed, and hurt performance in the process.
Posted: Mon May 15, 2006 3:14 pm
by santosj
arborint wrote:I am not sure that Reflection is the way to go with this. Plugins needs a explicit interface that plugin builders can follow -- and predictable behavior. Using reflection would add some intellegence where none is really needed, and hurt performance in the process.
D11wtq example could be completely replaced by the SPL Observer Classes without the need for Reflection. Since SPL is compiled into PHP, it would be faster than an user defined class. The example also implement some of the Reflection Class functionality. By using both SPL Observer and Reflection, the classes would be smaller and faster.
How is this:
Code: Select all
$callback = array(&$this->subordinate, $this->method);
return call_user_func_array($callback, $args);
A performance hit over
Code: Select all
$method = new ReflectionMethod($this->method);
return $method->invokeArgs($this->subordinate, $args);
Not only is it easier, but the cause of error is reduced. I have not done any benchmarks... as I haven't been able to get call_user_func_array to work correctly and decided using Reflection was less stressful.
Disclaimer: I meant no disrespect to D11wtq, only trying to prove me point and I'm sorry in advance.
Posted: Mon May 15, 2006 3:34 pm
by Chris Corbyn
santosj wrote:Disclaimer: I meant no disrespect to D11wtq, only trying to prove me point and I'm sorry in advance.
Believe me, I don't take offense at criticism

If something I've done can be done better or just doesn't fell right I'd like to know

(I might not choose to accept it but either way I wonuld never be offended).
Re PHP5 being the way of the future... As much as I prefer PHP5 over 4 I don't feel it is. PHP6 is already in development, PHP5 never really got off the ground as much as it should have done due to shared hosts failing to bother upgrading and PHP4 is still the most highly used. I do believe that by the time PHP6 is out more people will use it than PHP4 because the changes they've added ake it more worthwhile upgrading - that's another topic all together though.
/heads off for more background reading
Posted: Mon May 15, 2006 3:45 pm
by santosj
Do not worry about copying because in PHP 5, classes are passed by reference.
First Example:
Code: Select all
class Word implements SplSubject
{
private $observers = array();
private $word = '';
function attach(SplObserver $plugin)
{
$class = get_class($plugin);
$this->observers[$class] = $plugin;
}
function detach(SplObserver $plugin)
{
$class = get_class($plugin);
unset($this->observers[$class]);
}
function notify()
{
foreach($this->observers as $plugin)
{
$plugin->update($this);
}
}
function speak($word)
{
$this->word = $word;
}
public function get()
{
return $this->word;
}
function __construct()
{
}
function __toString()
{
return $this->word ."<br />\n";
}
}
class Plugin implements SplObserver
{
function update(SplSubject $subject)
{
$word = $subject->get() . '... touched by a plugin';
$subject->speak($word);
}
}
$word = new Word;
$plugin = new Plugin;
$word->speak('testing');
$word->attach($plugin);
This gives each plugin the builtin interface for which to implement allowing the plugin to only worry about updating the subject. With this, you could perhaps build a paragraph based entirely on which plugins were attached and in which order they were attached.
Posted: Mon May 15, 2006 3:55 pm
by Chris Corbyn
Haha sweet cheers!

Posted: Mon May 15, 2006 3:59 pm
by santosj
Yeah, thank
http://wiki.cc/php/SplObserver and
http://wiki.cc/php/SPL
I still haven't converted the other example over, but I'll do that later. There are flaws in the design of my example that would need to be worked out. I was going to give the plugin an public variable that would the key for deletion, but I never really got around to it.