One option would be to have a base Plugin class, which plugins would
extend. Each plugin's constructor would be responsible for registering
the plugin action against the Habari hooks that trigger those actions.
I _think_ our base Plugin class should be a singleton, with only a few
public methods. I think it ought to be like our database class, so that
the Plugin class can be called statically or dynamically.
I envision the primary method of Plugin to be do(), with an invocation
something like this:
Plugin::do('hook_name');
Optional variables could be passed after the hook name, as needed.
Plugins ought to be free to register actions against arbitrary hooks
(ie: hooks that are not defined or called anywhere within the Habari
core). This would permit plugins to be called on-demand from within
template files.
The Plugin class should maintain a list of registered actions, and the
hooks to which they belong. When Plugin::do() is invoked, it should
check the hook name passed to it and make sure that at least one action
is defined. If no action is defined for the named hook, Plugin::do()
should simply return.
I do not know if individual plugin classes would need to be
instantiated, or if they should all be static.
I am open to discussion on all of the above. I'd like to get some
movement on this, so that we can resume Habari development.
--
ski...@skippy.net | http://skippy.net/
gpg --keyserver pgp.mit.edu --recv-keys 9CFA4B35
506C F8BB 17AE 8A05 0B49 3544 476A 7DEC 9CFA 4B35
Ok...
> I _think_ our base Plugin class should be a singleton, with only a few
> public methods. I think it ought to be like our database class, so that
> the Plugin class can be called statically or dynamically.
Ok, but... Can we make Plugins (with "s") the singleton class and
provide Plugin as base class or as an interface? I think it may be
difficult to derive new plugins from a singleton anyway, as this was
an issue with the database object.
We're also going to have to figure out how plugins are loaded, how our
interface presents them. I'm not really keen on the way WP does it -
by loading some concocted format from within the plugin files
themselves. I would much rather see one of these options:
1) Instead of registering the plugin in the constructor, the
constructor would simply initialize the plugin object. A method in
the class interface could be called to return plugin information.
2) Plugin information is stored in an xml format, or some other format
that could be standardized and included in the plugin distribution.
It would be worthwhile to look for prior art in other products.
Each has drawbacks. Alternatively, we could use something like what
WP has, but define it a little better and provide a bit more dynamics
to it, which is one of my two problems with it.
Primarily, I don't like that it the system isn't extensible without
modifying the core. In other words, you can't add new fields to the
plugin headers that are accessible from other plugins. Adding new
meta information to plugins is non-trivial as a result.
Secondarily, having text in the plugin that isn't a code comment and
isn't code itself makes me feel oogy. I don't like my data
intermingled like that. But whatever, that's not really too
important.
> The Plugin class should maintain a list of registered actions, and the
> hooks to which they belong. When Plugin::do() is invoked, it should
> check the hook name passed to it and make sure that at least one action
> is defined. If no action is defined for the named hook, Plugin::do()
> should simply return.
No options for filtering or formatting?
You know what would be cool...
Plugins is instanciated as an object that implements the __call()
function. All plugin hooks would be called like this:
$plugins->hook_name(parameters);
The Plugins class would then dispatch the hook to the appropriate
derivates of Plugin.
It's a shame that __call() doesn't work on static classes. Of course,
you could always make Plugins a singleton and call this instead:
Plugins::o()->hook_name(parameters);
Thinking aloud:
class Plugins {
...
function __call($fname, $params) {
foreach($this->pluginobj as $obj) {
if(method_exists($obj, $fname)) {
call_user_method_array($fname, $obj, $params);
}
}
}
}
And just say up front that all returned values are returned via
parameters by reference -- the plugin functions never return values.
I suppose that you could do this statically, too:
class Plugins {
...
function do() {
$params= func_get_args();
$fname = array_shift($params);
//...etc...
}
}
Concerning priority of executed actions, the plugin objects could
somehow delegate that themselves...
class Plugins {
...
function __call($fname, $params) {
$active = array();
foreach($this->pluginobj as $obj) {
if(method_exists($obj, $fname)) {
$active[] = $obj;
}
}
$sorter = new PluginSorter($fname);
usort($active, array(&$sorter, 'evalsort');
foreach($active as $obj) {
call_user_method_array($fname, $obj, $params);
}
}
}
class PluginSorter
{
function __construct($fname)
{
$this->fname = $fname;
}
function evalsort($a , $b)
{
$x=$a->priority($this->fname);
$y=$b->priority($this->fname);
if($x == $y) return 0;
return ($x < $y) ? -1 : 1;
}
}
The Plugin base class would provide a default (and overridable)
priority() function that would return a default value, so you wouldn't
need to handle priorities if you didn't care.
"priority" is a bad word for this. "order" isn't much better.
Something that will help me to remember whether higher numbers execute
first or last would be nice.
Owen
Sure.
> We're also going to have to figure out how plugins are loaded, how our
> interface presents them. I'm not really keen on the way WP does it -
> by loading some concocted format from within the plugin files
> themselves. I would much rather see one of these options:
>
> 1) Instead of registering the plugin in the constructor, the
> constructor would simply initialize the plugin object. A method in
> the class interface could be called to return plugin information.
>
> 2) Plugin information is stored in an xml format, or some other format
> that could be standardized and included in the plugin distribution.
> It would be worthwhile to look for prior art in other products.
Drupal has an interesting mechanism: each plugin (or "module" in Drupal
parlance) lives in a directory named for that plugin and the plugin file
itself is named [module].module. So the plugin "foobar" lives at
/foobar/foobar.module
The modules provide a function called [module]_help(), which return
varying types of explanatory text. For example, here is the
adminblock.module adminblock_help() (grabbed at random from drupal for
example purposes):
/**
* Implementation of hook_help().
*/
function adminblock_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Block that display the comments approval queue, the node
moderation queue and the trackback queue.');
break;
}
}
> Primarily, I don't like that it the system isn't extensible without
> modifying the core. In other words, you can't add new fields to the
> plugin headers that are accessible from other plugins. Adding new
> meta information to plugins is non-trivial as a result.
Can you give me an example of why you'd want to do this? It's not a
restriction I've ever encountered.
>> The Plugin class should maintain a list of registered actions, and the
>> hooks to which they belong. When Plugin::do() is invoked, it should
>> check the hook name passed to it and make sure that at least one action
>> is defined. If no action is defined for the named hook, Plugin::do()
>> should simply return.
>
> No options for filtering or formatting?
It was simple oversight, rather than deliberate omission. Although,
strictly speaking, the do() method could be used to invoke filtering and
formatting plugins. In my mind, do() is simply the executor function,
and doesn't do anything else. So, for example,
`Plugins::do('filter_comment', $this_comment);` would execute all
plugins registered against the filter_comment hook.
> Plugins is instanciated as an object that implements the __call()
> function. All plugin hooks would be called like this:
>
> $plugins->hook_name(parameters);
>
> The Plugins class would then dispatch the hook to the appropriate
> derivates of Plugin.
How, exactly, is that cool? What does it buy us that Plugins::do() does
not provide? Note that I'm not arguing against the use of a __call()
handler -- I'm trying to make sure I keep up with you.
> class Plugins {
> ...
> function __call($fname, $params) {
> foreach($this->pluginobj as $obj) {
> if(method_exists($obj, $fname)) {
> call_user_method_array($fname, $obj, $params);
> }
> }
> }
> }
>
> And just say up front that all returned values are returned via
> parameters by reference -- the plugin functions never return values.
I'm okay with that. It'll need to be clearly documented, and a useful
example or two needs to be provided with said documentation.
> The Plugin base class would provide a default (and overridable)
> priority() function that would return a default value, so you wouldn't
> need to handle priorities if you didn't care.
>
> "priority" is a bad word for this. "order" isn't much better.
> Something that will help me to remember whether higher numbers execute
> first or last would be nice.
Order is as good as anything, especially if we're storing these in an
array. The order in which they exist in the array is the order in which
they'll be executed. "sequence" is an alternative.
Should we agree now that the lower the order, the sooner the plugin will
execute? That keeps the logical execution in line with the logical
storage inside the array.
Is the [module].module file PHP source? That seems odd.
This seems reasonable, although I can think of instances where I would
want to have several plugins that register as separate plugins all
reside in the same directory, specifically, those that interact with
each other. My stats plugin for WP is like that. Can you put more
module files in the directory than just the one? Could we?
> > Primarily, I don't like that it the system isn't extensible without
> > modifying the core. In other words, you can't add new fields to the
> > plugin headers that are accessible from other plugins. Adding new
> > meta information to plugins is non-trivial as a result.
>
> Can you give me an example of why you'd want to do this? It's not a
> restriction I've ever encountered.
When Matt Read and I were writing the plugin versioning system, we
added a minimum and maximum requried core version to the plugin
headers. His plugin checked these values in the plugin headers, but
he had to rewrite that whole section of the WP code because the WP
code only looked for very specific values.
While it might seem feasible to include only these particular fields,
it would be nice if this plugin metadata was extensible from the
beginning.
> > No options for filtering or formatting?
>
> It was simple oversight, rather than deliberate omission. Although,
> strictly speaking, the do() method could be used to invoke filtering and
> formatting plugins. In my mind, do() is simply the executor function,
> and doesn't do anything else. So, for example,
> `Plugins::do('filter_comment', $this_comment);` would execute all
> plugins registered against the filter_comment hook.
This is one thing I would like to improve on WP: Two separate
functions, one for filtering that works only with filters, and one for
actions that works only with actions. For clarity.
There could be an actual difference between them: One would return and
the other would not. By calling the correct one, you would anticipate
the behavior.
> > Plugins is instanciated as an object that implements the __call()
> > function. All plugin hooks would be called like this:
> >
> > $plugins->hook_name(parameters);
> >
> > The Plugins class would then dispatch the hook to the appropriate
> > derivates of Plugin.
>
> How, exactly, is that cool? What does it buy us that Plugins::do() does
> not provide? Note that I'm not arguing against the use of a __call()
> handler -- I'm trying to make sure I keep up with you.
__call() is just cool. No good reason to use it, really. Everything
I suggested in code can be done the other way, too.
> > The Plugin base class would provide a default (and overridable)
> > priority() function that would return a default value, so you wouldn't
> > need to handle priorities if you didn't care.
> >
> > "priority" is a bad word for this. "order" isn't much better.
> > Something that will help me to remember whether higher numbers execute
> > first or last would be nice.
>
> Order is as good as anything, especially if we're storing these in an
> array. The order in which they exist in the array is the order in which
> they'll be executed. "sequence" is an alternative.
>
> Should we agree now that the lower the order, the sooner the plugin will
> execute? That keeps the logical execution in line with the logical
> storage inside the array.
Well, that's the point I was trying to make. "order" doesn't imply
whether higher or lower values are executed first. Choose a word that
implies which comes first by the value it returns and we'll have both
issues solved.
Personally, I would like to see a better way for plugins to negotiate
which executes first than using some meaningless number, but I have no
suggestions for how to do that. I recall that a design pattern might
have worked for that purpose, but I can't remember what it was nor
find it after some recent searching, so maybe I just dreamed it.
Owen
Yes, it is PHP source. I don't know the history of that decision.
> This seems reasonable, although I can think of instances where I would
> want to have several plugins that register as separate plugins all
> reside in the same directory, specifically, those that interact with
> each other. My stats plugin for WP is like that. Can you put more
> module files in the directory than just the one? Could we?
I don't know if multiple module files, or even multiple modules, can
reside inside a module sub-directory. I haven't looked at enough Drupal
modules to know.
If we use sub-directories for plugins, then the path to files will be
something like this:
/home/skippy/public_html/user/plugins/foo/foo.php
Right?
> When Matt Read and I were writing the plugin versioning system, we
> added a minimum and maximum requried core version to the plugin
> headers. His plugin checked these values in the plugin headers, but
> he had to rewrite that whole section of the WP code because the WP
> code only looked for very specific values.
>
> While it might seem feasible to include only these particular fields,
> it would be nice if this plugin metadata was extensible from the
> beginning.
Okay. Since plugins should all be objects, they can have as much
internal metadata as they want, and that can be exposed (and therefore
accessed) in any number of ways, right?
Habari could enforce a specific minimum set of metadata necessary, and
plugins could add whatever else they want. If it's merely a set of
class variables, or even key/value pairs inside a class associative
array, that should work?
We still need to determine whether plugins should only ever be invoked
by static methods or if each individual plugin should be instantiated.
> This is one thing I would like to improve on WP: Two separate
> functions, one for filtering that works only with filters, and one for
> actions that works only with actions. For clarity.
In WordPress, filters and actions are the same thing, with only a
lexical difference. It's never been entirely clear to me why both names
are maintained when one simply calls the other.
> There could be an actual difference between them: One would return and
> the other would not. By calling the correct one, you would anticipate
> the behavior.
So the "filter" plugins would return a value to the place from which
they were invoked? You suggested in your previous email that all
plugins operate on data passed by reference, so that no return values
are necessary. I liked that idea.
>> How, exactly, is that cool? What does it buy us that Plugins::do() does
>> not provide? Note that I'm not arguing against the use of a __call()
>> handler -- I'm trying to make sure I keep up with you.
>
> __call() is just cool. No good reason to use it, really. Everything
> I suggested in code can be done the other way, too.
Okay. I'm comfortable using __call(), if for no other reason than to
say "we're taking advantage of what PHP5 has to offer." :)
>> Should we agree now that the lower the order, the sooner the plugin will
>> execute? That keeps the logical execution in line with the logical
>> storage inside the array.
>
> Well, that's the point I was trying to make. "order" doesn't imply
> whether higher or lower values are executed first. Choose a word that
> implies which comes first by the value it returns and we'll have both
> issues solved.
I know of no single word that means "Ascending order".
Cumulative? Progressive? Ordinal?
We could use the English alphabet, a-z, to signify execution order.
> Personally, I would like to see a better way for plugins to negotiate
> which executes first than using some meaningless number, but I have no
> suggestions for how to do that. I recall that a design pattern might
> have worked for that purpose, but I can't remember what it was nor
> find it after some recent searching, so maybe I just dreamed it.
We could say that plugin functions get called in alphabetical order, but
that will quickly be defeated when everyone starts prepending
underscores to their method names. ;)
I understand that you don't have any specific suggestions, but can you
provide an abstracted example of what you mean -- not in code terms but
in functional terms? I don't understand how else plugins would need to
negotiate execution order beyond a simple numerical index. What beyond
"This plugin runs before these other ones" is necessary?