=pod

=head1 INTRODUCTION

A lot of great software has plugins. I believe that some software became famous mostly because it invited others to contribute. A more recent example is perhaps iTunes App store. Apps are, of course, not plugins, but they do allow programmers to extend existing software, much like plugins do. Perl projects which are extendable with plugins and successfully attracted an active community are Dist::Zilla and Perl Dancer, to name just two. The examples suggest that "pluggability" could make your code more modular, more extendable, more collaborative and, if things go well, more cool. 

Of course, these days there are many ways of extending existing perl code. Plugins are not always the way to go. Perhaps more important is still inheritance and roles. Inheritance and roles offer excellent ways to extend modules, but when it comes to executable apps or scripts then inheritance and roles are not always what you want. This leads us to the question what plugins are.

In this tutorial, I understand plugin system as a mechanism which allows you to load plugins (code) from your app's configuration (i.e. without changing your app's code). Assuming that your plugins come in packages (typically one package per plugin) that means you will want to load plugins at runtime before you do something with them. (I guess, theoretically you could also load them slightly earlier using constants and BEGIN blocks etc., but I don't see how that would be of any practical use.)

CPAN

By the way, I was surprised that there was little on plugins in CPAN when I started to think about the topic a few days back. Basically, I found MooseX::Plaggerize which I didn't try, but which seems to rely on an older version of Moose and comes along as a Moose eXtension, i.e not a general plugin system. What I was looking for was a module that makes shows me how to write apps/scripts with plugins, a plugin system as described by the plugin wikipedia article. So instead, I looked at the code of projects which I new implemented a plugin system, Dancer and Dist::Zilla. 

Roll Your Own?
Why are there plugin systems for all kind of languages, but not for perl? Perhaps I am looking at the wrong  places or perhaps it's just too simple to make your own custom plugin system. After all, you  only need one sub with three lines of perl to implement it:

package YourCore;  
use Class::Load 'load_class';
my $p1ugin=load_plugin ($plugin_class,%args); 
$p1ugin->doSomething(@opts);  

sub load_plugin {
      my $plugin=shift;
      load_class ($plugin);
      return $plugin->new(@_);
  }

You get $plugin_class (and %args if you need them) from the configuration and repeat the process as many times as configuration supplies information on plugins. There are dozens of modules on CPAN  which allow you to conveniently require modules at runtime. My favorite is Class::Load. I believe it's a spin-off from Moose. It's recent, stable and has good documentation. It one word: it does excellent work as long you don't need to import keywords.

Want a Little More?
 
I like to encapsulate common things with good method names, so I wrote my own little plugin module a couple days back: Plugin::Tiny. It turns out that about the simplest implementation I could think of does everything I need. So I will use that in the rest of this tutorial, but much of it should still be useful if you roll your own or use a similar plugin mechanism.

Why anything more than the above 3-liner? The 3-line method I show above doesn't make it particularly easy to load an arbitrary number of plugins and use them at different places in your app without knowing what your users come up with in the future. But a 'realworldly' plugin system should be able to do exactly that.

Phases
So, let's associate each plugin with a unique keyword. Your program knows the keywords, but not which plugins your users will load for each keyword. Also, the keywords allow you to call specific plugins at different times during the life cycle of your app without having to know which plugins your users will load exactly. Because the keywords allow you to call plugins at specific times, I call them 'phases'. 

Plugin::Tiny
A limitation of Plugin::Tiny is that each phase is associated with exactly one plugin. You can still have multiple plugins in one phase if you define a plugin bundle (see below) and you can load an arbitrary number of plugins with Plugin::Tiny, but then things get a little more complicated, because your program doesn't know the phases at compile time.

First we make a new Plugin::Tiny object. That's about as straight-forward as can be. By the way, I use 'core' to refer to the app you're writing (in opposition to the plugins):

use Plugin::Tiny;           #in your core
my $ps=Plugin::Tiny->new(); #plugin system
Then we register a plugin:
my $plugin=$ps->register(
    plugin=>'Your::App::Plugin::Foo', #required
    phase=>$phase,          
    arg1=>$arg1,         
);
Register (loads, requires and imports plugin), makes a new object and returns it. So it's a bit like. 
use Your::App::Plugin::Foo;
my $plugin=Your::App::Plugin::Foo->new(arg1=>$arg1);
The main difference is, of course, that 'use' takes place quite a bit earlier than Plugin::Tiny's register. 

Let's look some more at the arguments for register. All we really need is the package name of the plugin. If you want to specify an explicit certain phase you can do so. If you don't, Plugin::Tiny will use the built-in default phase which in this case takes the last element of plugin package name: 'Foo'. Generally you should make sure that the phase is unique. If the phase is not unique, i.e. you load more than one plugin in the same phase Plugin::Tiny will confess. (You can disable that error message, see perldoc Plugin::Tiny).

If you want to pass down arguments to the object constructor you just add them ('arg1' in the example above). You can also specify a role that your plugin needs to comply with using the argument role.

We're not quite done yet. You may be able to write your plugins in a way that they can do their work when they are registered (i.e. when new is called). But it's also possible and in fact recommend that you have separate methods called at a later stage. You can call methods on your object after you registered the plugin (and made the corresponding object) or you get the object later using get_plugin:

my $plugin=$ps->register(
    plugin=>'Your::App::Plugin::Foo', 
    arg1=>$arg1,         
);
$plugin->do_something ($arg2,$arg3);
#some later stage when you don't have $plugin anymore:
$plugin_system->get_plugin($phase); #phase is 'Foo'
A shortcut: If Plugin package names get too long
If your plugin package names get too long you can specify a prefix which will be added to all package names during register:
use Plugin::Tiny;           
my $ps=Plugin::Tiny->new(prefix=>'Your::App::Plugin::'); 
my $plugin=$ps->register(
    plugin=>'Foo', #default phase is 'Foo'
    arg1=>$arg1,         
);
Access
Plugin::Tiny lets you decide how much of your application is directly available to the plugin. At least in theory. As you can see in the above examples you are free to hand down whatever you like as arguments during the plugin construction and later anyways (do_something).

Of course, this is perl and perl objects don't really have private parts. So crafty plugin authors will find a way to access your main app, if they really want to. The potential problem here is that they can alter all the internal values of your main app. So it's their responsibility not to kill kittens or do other bad things. 

If that is so why don't you pass down the whole core app to the plugin anyways. Or at least some parts that you need the everywhere, say the logger and the plugin system:

my $plugin=$ps->register(
    plugin=>'Foo', #default phase is 'Foo'
    core=>$self, #your whole app         
);
#or alternatively only the important parts 
use Moose; 
has 'plugin_system'=>(is=>'ro', isa=>'Plugin::Tiny');
my $plugin=$ps->register(
    plugin=>'Foo', #default phase is 'Foo'
    plugin_system=>$self->plugin_system
);
The Plugin
I should not forget to show you a simple plugin. This being a tiny implementation, Plugin::Tiny doesn't require much from plugins. Almost anything can be an object. Perhaps you can even load existing and non-related CPAN modules as plugins without any modifications. All these modules need is a new subroutine. And currently it's not possible to import symbols:

package My::Plugin; #a complete plugin that doesn't do very much
sub new {
        return bless {},$_[0]; #avoiding Moose here just to show that it works
}    
sub do_something {
        print "Hello World\n";
}
    
1;

Bundles
If your plugin has access to the Plugin::Tiny object it can continue to register plugins. Yes, that's right: Plugins registering plugins. I suggest to think of plugin bundles as plugins which register multiple other plugins. If that is the case, a plugin bundle could look like this:

package PluginBundle;
use Moose;
has 'plugins'=>(is=>'ro', isa=>'Plugin::Tiny', required=>1); 
#phase defaults to 'One' and 'Two':
sub BUILD { 
  $self->plugins->register_bundle({Plugin::One=>{},Plugin::Two=>{}});
  
  #more or less the same as:    
  #$self->plugins->register (plugin=>'Plugin::One');  
  #$self->plugins->register (plugin=>'Plugin::Two'); 
} 
sub start {  
  my $one=$self->plugins->get('One');
  $one->do_something(@args);  
}
 
This example also introduces the register_bundle method which expects a hashref. The hashref has almost the same information as a series of 'normal' register methods, only that the key 'plugin' is left out and the plugin package name functions as a key. However, it's probably better style to write this bundle differently (example below), so that you or the next plugin developer can inherit the bundle hashref, alter it (add or remove plugins etc.) before they call:

package PluginBundle;
use Moose;
has 'plugins'=>(is=>'ro', isa=>'Plugin::Tiny', required=>1); 
 
sub bundle {
     return {Plugin::One=>{},Plugin::Two=>{}};
}
 
sub BUILD { 
    $self->plugins->register_bundle(bundle());
}
sub start {  
  my $one=$self->plugins->get('One');
  $one->do_something(@args);  
}