[wp-hackers] Plugin dependency checking

Mike Schinkel mikeschinkel at newclarity.net
Wed Jun 17 04:11:00 GMT 2009


"scribu" <scribu at gmail.com>
> Now I finally get what Mike was trying to say about 
> why to use interfaces. (I think we should stick to 
> this term)

Cool!

"Stephen Rider" <wp-hackers at striderweb.com> wrote:

> As I see it, everything needed to do this already exists 
> within WordPress.  The sub-plugin can do a custom add_action, 
> and the master plugins can do a do_action on the same hook.

Most of it already exists.  add_action()/do_action() allow for defacto definition of interfaces vs. named definition.

I've spend several hours working on a proof of concept this afternoon and come up with some approaches and some challenges. Before I dive in I want to explicitly state the principles I was following:

1.) As low impact on the owners of WordPress blog as possible - The should not need to know anything about said interfaces and they shouldn't have to do anything additional with respect to copying files besides what they currently have to do to install a plugin.

2.) Low cognitive load on plugin developers - Whatever is required should be fully optional except to gain features that currently are not available and when the optional features are used they should be as intuitively obvious for the average plugin developer as many (most?) plugin developers do not have computer science degrees and proficiency in many programming languages.

3.) Support Ad-Hoc Development - Make it as easy for ad-hoc plugin development as development of plugins that are hosted on WordPress.org extend.

Basically I've identified three (3) levels of interface support:

1.) Fully implicit (we already have this)
2.) Explicitly named and implicitly specified
3.) Explicitly named and explicitly specified

#2 is actually rather easy to tackle, and I propose that we tackle it via the plugin header because the plugin header is only parsed during activation and parsing doesn't require the plugin to actually be run through the PHP parser. Ignoring the exact terminology we would use for the moment I have taken the Akismet plugin as if it were a Parent/Master using the terms we've discussed in this thread and tried to apply the concepts (I've removed the "Description:" for clarity):

/*
Plugin Name: Akismet
Plugin URI: http://akismet.com/
Description: ...
Version: 2.2.4
Author: Matt Mullenweg
Author URI: http://ma.tt/
Uses: Akismet Interface 1.0
*/

I've named the interface that the Akismet plugin can use/consume "Akismet Interface" (which would be the default.) I'm thinking this name would imply the name "Akismet Dependent Plugin Interface." If it had two interfaces I might call them something like "Akismet Spam Interface 1.0" and  "Akismet Tab Interface 1.0." 

Back to the example; one or more plugins could "Implement" (or "Expose" or "Offer" or ...?) the "Akismet 1.0" Interface while Akisment and/or other plugins would be able to "Use"/"Consume" it. Note I versioned the interface; it think that will be important over time. Also note that it's not the same version # as the plugin itself; hopefully it is obvious why.

Akisment has a hook called 'akismet_spam_caught' that it runs after it find a comment that it considers spam. Here's an adhoc plugin I composed adhoc here in email (i.e. it's not tested.) It uses the header to state that it "Implements" "Akismet Interface 1.0":

/*
Plugin Name: My Akismet Spam Watcher
Description: Watchers Spam IP Addresses by Akismet
Version: 1.0
Author: Mike Schinkel
Author URI: http://mikeschinkel.com/
Implements: Akismet Interface 1.0
*/
add_action('akismet_spam_caught','my_akismet_spam_watcher');
function my_akismet_spam_watcher() {
  global $commentdata;
  $ip = $commentdata['comment_author_IP'];
  $options = get_option('my_akismet_spam_watcher');
  $options['ips'][$ip]= (isset($options['ips'][$ip])? $options['ips'][$ip]+1 : 1);
  if ($options['ips'][$ip]==3)
    @wp_mail($options['notify_email'], "3 Spam Strikes for IP $ip!");
  update_option('my_akismet_spam_watcher',$options);
}

With just this bit we could determine plugin load order after each activation and deactivation. Of course the definition of "Akismet Interface 1.0" is still implicit and thus murky, but its better than we had before.

Another consideration is that plugins should be able to both use and implement more than one interface, hence we should need multiple, I propose comma-separated:

/*
Implements Interface: Akismet 1.0, Foo 1.3, Bar 2.0
*/

The next step is to go to #3: Explicitly Named AND Explicitly Specified. I see great value in being able to do this but I'm sure it won't be immediately obvious to many why. I'll try to give examples here but if I were to be exhaustive it would take me all night and you'd probably never read it anyway. But first what is it that define an interface in WordPress? Well, it includes at least the following...

-- do_action()s
-- apply_filter()s
-- get_option()/update_option()
-- global variables defined
-- PHP Interfaces of pluggable functions
-- MySQL Tables added 
-- Fields added to MySQL Tables
-- Several other things I've yet to realize.

Note I did NOT include "add_action()" or "add_filter()" here. While you could view each of those individuallt as an Interface (and that too may make sense) I am viewing an Interface as a named collection of one or more hooks plus other stuff.  And while it might also make good sense to document the hooks a plugin consumes, the database tables and fields it touches, the global variables it accesses and modifies and so on I'm not going to address that in this email 

BTW, much of this information can actually be auto-generated by scanning source code. We can easily have a parser discover that 'foo_bar' is an action when "do_action('foo_bar')" is in the code but not when "do_action($hook)" is used instead.

What can having the information I described (optionally) provide us?

-- Allow us to do code coverage analysis for testing
-- Allow us to implement cleanup returns when plugins are deleted
-- Allow us to validate plugins on activation
-- Provide MUCH BETTER documentation for how to interface a plugin (and also WordPress core)
-- Allow us to move some of our specific code in our plugins to more generic code in the core (i.e. table creation, adding fields, etc.) 
-- Many other things I've forgotten at the moment, or yet to realize.

The problem with #3 is that the proposed WP_Interface is an independent entity from a plugin (just like a PHP interface is independent from the classes that implement it and the classes that use it) and as such needs to be defined independently from any given plugin lest we find ourselves in the paradox of the man with many watches (i.e. he never knows what time it is.) This leads me to think that the most appropriate solution would be a central registry online at wordpress.org (maybe http://interfaces.wordpress.org or similar.) BUT WAIT! DON'T BURN ME AT THE STAKE YET! :-)  Hear me out...
 
However, since most interfaces will only be consumed by one plugin that plugin should be able to define it in the plugin, something like this:

add_filter('plugin_interface_akismet_1.0','akismet_define_interface');
function akismet_define_interface($wp_interfaces) {
  $wp_interfaces['Akismet']['1.0'] = array(
    'actions'   => array(...),
    'filters'   => array(...),
    'options'   => array(...),
    'globals'   => array(...),
    'functions' => array(...),
    'tables'    => array(...),
    'fields'    => array(...),
    'other'     => array(...),
  );
  return $wp_interfaces;
}

For adhoc-defined interfaces, we could use a convention like interfaces that start with 'x-' are reserved for individual use. If they get hosted on Extend they need a registered interface.

But the problem again is what happens when two different plugins define the same interface differently? That's where the registry comes in. The function 'akismet_define_interface' would NEVER be called if it is defined in the registry, so plugin devs would be forced to ensure their definition would match the one in the registry (which is a good thing.) OTOH if a developer is developing locally with no Internet connection, or the registry web service was down, WP could fall back to the local definition.

Okay, that's enough for tonight. I'm exhausted. I need to see if any of this makes sense to the rest of you before I put any more thought into it.

-Mike Schinkel
Custom Wordpress Plugins
http://mikeschinkel.com/custom-wordpress-plugins

P.S. Another thought that occurs to me as I finish the email is what I now think Stephen was trying to say, i.e. that for a plugin with only one do_action() or one apply_filter() we can assume it defines the interface in a defacto manner and I think that would be a good idea. I think we also really do need to recognize that an interface is a collection of touch points, and rarely just one. 


More information about the wp-hackers mailing list