Page 1 of 1
Best Way To Do Plugin Architecture?
Posted: Sat May 30, 2009 2:31 pm
by volomike
I don't know if this should be under Theory and Design?
I just want to ask, if you have an extremely obvious MVC architecture of say controllers, models, and views; a bootstrap file; a front controller file (+ .htaccess); a system folder containing framework handlers for controllers, models, and views; a conf file; a set of other handy typical toolbox classes in the system folder for PDO, PDF stuff, dates, curl, file upload, image management, captcha -- stuff like that -- if you had all this,
how would you implement the plugin architecture?
Take for instance I take this MVC architecture and I build a blended Basecamp/ActiveCollab kind of project manager with it. Now I want to provide a way that one can install plugins and activate and deactivate them -- what would be the best approach?
I noticed in the PunBB forum software, they implement plugins by putting this throughout the critical files:
Code: Select all
($hook = get_hook('vf_start')) ? eval($hook) : null;
But I'm sure you can think of a better way.
Re: Best Way To Do Plugin Architecture?
Posted: Sat May 30, 2009 2:36 pm
by volomike
Or perhaps you may have an opinion about any of these implementations:
http://stackoverflow.com/questions/42/b ... pplication
Re: Best Way To Do Plugin Architecture?
Posted: Sat May 30, 2009 6:36 pm
by alex.barylski
Mike: Intersting topics lately dude...your always good for that.
I don't have much time but I wanted to say:
First I would ask myself (or attempt to anticipate) what parts of the application/framework/library my users would want to 'extend' via a plugin. Do plugins enhance/override what already exists or do they replace outright.
For example simple inheritence could be consider a plugin architecture (pretty vague term me thinks) in that you typically extend an existing funcitonality, at the class level. Alternatively, you might use composition and dependency injection to completely replace the functionality of an object implementation.
That being said, what plugin architecture are you refering to exactly? Class level or something like Joomla and it's component architecture?
In the latter case, things get more complicated because now you have several points of interest to the client developer. If they extend the 'model' of an MVC component, they likely have to extend the view and possiblky controller.
Can these components even be extended or is it easier to replace outright?
Extending an MVC component could be done with something like a controller pre-post filter or plugin but that type of plugin will only take you so far (modifying the HTML result before sending to screen, etc.
Enough said for now...more coming later...
Cheers,
Alex
Re: Best Way To Do Plugin Architecture?
Posted: Sat May 30, 2009 6:44 pm
by volomike
Ah, you're thinking from an academic OOP class perspective I think. That's all well and good, and I can understand the refactoring idea. Try thinking from the end user's perspective on the example I gave. Imagine they want to download some kind of plugin file into a directory and then activate it or deactivate it. So, it would have to have its "fingers" weaved all throughout the code. Take for instance WordPress, I can install a plugin in that which can do all kinds of things like:
- add a new menu in the admin system
- provide a control panel item in the admin system
- add a new screen widget
- significantly change the way data is input from the comments
- significantly change the way data is output to the pages, posts, and/or comments
- cache files
That's a pretty powerful and amazing plugin system, and it's done with very few files and in one tidy little subdirectory under the plugins subdirectory. So that's the sort of thing I'd like to accomplish as a goal with my MVC framework, not only for the framework itself, but for what I use with the framework to implement something else, such as a Basecamp/ActiveCollab knockoff.
Re: Best Way To Do Plugin Architecture?
Posted: Sat May 30, 2009 9:20 pm
by alex.barylski
Word press (as you know) isn`t MVC they simply allow you to inject custom methods in various places throughout the execution of a page request, etc.
The same principle would be applied to an MVC architecture. Wherever you anticipate a user wanting to èxtend the system, you would basically invoke a callback method or instantiate an object and invoke one or more of it`s methods according to what was required.
The drop in approach is the best way and you seem content on following wordpress and it`s single `plugin` directory (personally I prefer Joomla and it`s organization of extensions.
So yea it`s really as simple as just implementing some callbackéhook mechanism whereever you want users to be able to override existingédefault functionality.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 12:36 am
by volomike
Here's something I came up with that works with PHP5 or higher because it uses "magic methods" (__get, __set, __call, etc.) and is_callable and call_user_func_array. Basically it lets you take any class and convert it into a plugin class that you can override class methods and properties with new values, or use it to log existing values (such as to a log or notification system), as well as block a class property or class method from executing with the given argument(s).
I list it for your comments. I like this better than the Observer Pattern (see the stackoverflow.com reference above) because it doesn't require that you extend your class, and it doesn't require you add any special things inside your class. The only thing that might be a pain is that call_user_func_array tends to run a little slow (as in another few microseconds) according to some discussion on the web about this, and then I make it another more microseconds slower by having it run my plugin code. However, if you want the plugin architecture, I guess you have to accept this -- where even the Observer Pattern introduces some more microseconds of time.
Code: Select all
<?php
////////////////////
// PART 1
////////////////////
class Plugin {
private $_RefObject;
private $_Class = '';
public function __construct(&$RefObject) {
$this->_Class = get_class(&$RefObject);
$this->_RefObject = $RefObject;
}
public function __set($sProperty,$mixed) {
$sPlugin = $this->_Class . '_' . $sProperty . '_setEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
$this->_RefObject->$sProperty = $mixed;
}
public function __get($sProperty) {
$asItems = (array) $this->_RefObject;
$mixed = $asItems[$sProperty];
$sPlugin = $this->_Class . '_' . $sProperty . '_getEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
return $mixed;
}
public function __call($sMethod,$mixed) {
$sPlugin = $this->_Class . '_' . $sMethod . '_beforeEvent';
if (is_callable($sPlugin)) {
$mixed = call_user_func_array($sPlugin, $mixed);
}
if ($mixed != 'BLOCK_EVENT') {
call_user_func_array(array(&$this->_RefObject, $sMethod), $mixed);
$sPlugin = $this->_Class . '_' . $sMethod . '_afterEvent';
if (is_callable($sPlugin)) {
call_user_func_array($sPlugin, $mixed);
}
}
}
} //end class Plugin
class Pluggable extends Plugin {
} //end class Pluggable
////////////////////
// PART 2
////////////////////
class Dog {
public $Name = '';
public function bark(&$sHow) {
echo "$sHow<br />\n";
}
public function sayName() {
echo "<br />\nMy Name is: " . $this->Name . "<br />\n";
}
} //end class Dog
$Dog = new Dog();
////////////////////
// PART 3
////////////////////
$PDog = new Pluggable($Dog);
function Dog_bark_beforeEvent(&$mixed) {
$mixed = 'Woof'; // Override saying 'meow' with 'Woof'
// $mixed = 'BLOCK_EVENT'; // if you want to block the event
return $mixed;
}
function Dog_bark_afterEvent(&$mixed) {
echo $mixed; // show the override
}
function Dog_Name_setEvent(&$mixed) {
$mixed = 'Coco'; // override 'Fido' with 'Coco'
return $mixed;
}
function Dog_Name_getEvent(&$mixed) {
$mixed = 'Different'; // override 'Coco' with 'Different'
return $mixed;
}
////////////////////
// PART 4
////////////////////
$PDog->Name = 'Fido';
$PDog->Bark('meow');
$PDog->SayName();
echo 'My New Name is: ' . $PDog->Name;
In Part 1, that's what you might include with a require_once() call at the top of your PHP script. It loads the classes to make something pluggable.
In Part 2, that's where we load a class. Note I didn't have to do anything special to the class, which is significantly different than the Observer pattern.
In Part 3, that's where we switch our class around into being "pluggable" (that is, supports plugins that let us override class methods and properties). So, for instance, if you have a web app, you might have a plugin registry, and you could activate plugins here. Notice also the "Dog_bark_beforeEvent" function. If I set $mixed = 'BLOCK_EVENT' before the return statement, it will block the dog from barking and would also block the Dog_bark_afterEvent because there wouldn't be any event.
In Part 4, that's the normal operation code, but notice that what you might think would run does not run like that at all. For instance, the dog does not announce it's name as 'Fido', but 'Coco'. The dog does not say 'meow', but 'Woof'. And when you want to look at the dog's name afterwards, you find it is 'Different' instead of 'Coco'. All those overrides were provided in Part 3.
So how does this work? Well, let's rule out eval() (which everyone says is "evil") and rule out that it's not an Observer pattern. So, the way it works is the sneaky empty class called Pluggable, which does not contain the methods and properties used by the Dog class. Thus, since that occurs, the magic methods will engage for us. That's why in parts 3 and 4 we mess with the object derived from the Pluggable class, not the Dog class itself. Instead, we let the Plugin class do the "touching" on the Dog object for us. (If that's some kind of design pattern I don't know about -- please let me know.)
So, anyway, let me know if you think something could be done better here.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 3:42 am
by Weirdan
(If that's some kind of design pattern I don't know about -- please let me know.)
It looks like a decorator. And overall what you're doing here resembles quite closely some AOP (Aspect Oriented Programming) implementations, with *before and *after pointcuts.
There's one problem though - your implementation relies on function names and therefore you can't have several plugins listening to events on one class. For example, if I wanted Amplifier and TimeBasedGag installed on the Dog at the same time (to make Dog bark louder, but not bark at night) - it would fail.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 4:14 am
by onion2k
When I implemented plugins for PHP Image I borrowed heavily from Swiftmailer. That's a great example of a plugin system. I'm not sure it'd work for a site rather than a library though. It'd be orthwhile having a look at it anyway though because you'll learn something about good software design.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 7:31 am
by volomike
Weirdan wrote:There's one problem though - your implementation relies on function names and therefore you can't have several plugins listening to events on one class. For example, if I wanted Amplifier and TimeBasedGag installed on the Dog at the same time (to make Dog bark louder, but not bark at night) - it would fail.
Ah, you are right! I didn't realize that. Hmmm.... Got a fix?
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 8:27 am
by Paul Arnold
I'm just about to start a project that requires something similar to this.
I'll keep an eye on this thread and post anything useful that I find.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 10:31 am
by volomike
onion2k wrote:When I implemented plugins for PHP Image I borrowed heavily from Swiftmailer. That's a great example of a plugin system.
Thanks on that. I think what I see in Swiftmailer's plugin system is that it uses the Observer pattern. Perhaps that guy in the helicopter could fly in to respond? (You'll know what I mean when you check his avatar -- I think he's the one that started Swiftmailer.)
The only problem on the Observer pattern, and which I tried to overcome with the Decorator hook pattern, is that I wanted a way where any set of classes could be overridden without modification. So I guess what I'm needing is likely a blend of the Decorator hook and Observer patterns. I mean, I think the Decorator hook is a good start, but instead of calling say Dog_bark_beforeEvent, I call a function like Plugin_beforeEvent, pass it the name of the class and method along with arguments as $mixed, and let it then implement a notification strategy. Then all some class has to do is use the Observer strategy to observe these events and be able to intercept or block those events.
But then it comes down to a speed hit -- I need something that is the most optimal design as possible.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 11:08 am
by volomike
Just briefly looking over the latest WordPress plugin system under wp-includes/plugin.php, it appears they build a global array of plugin names, and then run those plugins where applicable. So then I see they have filter hooks and action hooks. Filter hooks are I/O, such as before screen output, before database input. Action hooks are before class methods are called. I think in many functions inside WP, they are making a call to check hooks and running those with call_user_func_array?
So, if that's the case, then it's somewhat cumbersome because every class or function call has to be modified to allow this interception where appropriate, when it would be great if you could just use a decorator pattern, intercept all method calls and property set/get, and then introduce your callbacks there. Makes you almost want to just use the PunBB technique with eval right after a class method declaration (see top of this thread on that), on every class method throughout your app -- but I keep hearing, eval is evil.
Another thing I think is interesting is that WP uses an oddball priority system, where I think a developer can stipulate that their plugin has a priority over another one so that the code doesn't break. Yeah, that won't work. I can see all developers making their plugins as priority 1. Hahahaha.
But that's a guess on how this works -- don't know for certain.
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 11:44 am
by volomike
Still another approach is to consider MVC. You might have, say:
* data output coming out of the controller that could be intercepted and regexp on (controller output)
* data coming out of the database through the models that could be intercepted and regexp on (model output)
* data output going into the view to be rendered that could be intercepted and regexp on (view output)
And all these could be guided via the controller's given action (from the URL).
You could put some intercept code there that checks a global array of plugins that have been detected and activated, see if matches with a controller's action, see if a plugin function actually exists, and then use call_user_func_array either in the controller output condition, the model output condition, or the view output condition.
In my case, the controller condition in my MVC framework would be Controller::getVar() for the controller output. For the model output, I would put some code snippet in my page controllers right before doing anything with the views. For view output, I could add the plugin check code right inside View::renderView().
Re: Best Way To Do Plugin Architecture?
Posted: Mon Jun 01, 2009 2:11 pm
by pickle
Look at MyBB - they've got a nice plugin architecture.