How to create an elegant and powerful plugin-based system with PERL

When you want to have a PERL application “modularized”, the “traditional” approach is to have the main script referring (and including) all the modules required for the application to run.

The problem with this approach is that – for every module you will be adding or removing – you would need to modify the application with all the possible mistakes that can arise by doing so.

The approach I propose is to have a main script that uses a “plugin” approach where you will be adding/removing “plugins” without the need to ever touch any other portion of the application.

Other benefits are that you can compartmentalize your development between developers and assign to each developer a “module” without he needs to know what the rest of the application does besides how he will need to interface with the core application.

Using proper coding guidelines, using this approach makes possible to develop big sized applications in PERL in an easy and efficient way. Let’s see how it works and how you can make full use of it.

We have the main controlling file. This is the main application file. Call it any name you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/perl
use CGI qw(:all);
print header;
################################################################################
#### VARIABLES
################################################################################
$module_error = "";
################################################################################
### ANALYZE QUERY STRING
### All the values coming from the browser are passed to the array $form
################################################################################
$query=$ENV{'QUERY_STRING'};
if ($query){
 @pairs=split(/&/,$query);
 } else {
 read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
 @pairs = split(/&/, $buffer);
 }
foreach $pair (@pairs) {
 $something_in=1; ($name, $value) = split(/=/, $pair);
 $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
 $name =~ tr/+/ /; $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
 if ($form{$name}) { $form{$name} = $form{$name}.",".$value; } else { $form{$name} = $value; }
 $form{$name} =~ s/'/''/g;
 }
################################################################################
#### LOADS AND INITIALIAZES ALL THE PLUGINS IN THE DIRECTORY
#### all plugins to have extension ".pm"
################################################################################
if ( $ENV{'HTTPS'} eq "on" ){
 $var_http = "https";
 } else {
 $var_http = "http";
 }
$plugin_directory = './plugins';
$var_script = "$var_http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
################################################################################
use lib ('plugins');
opendir (DIR, $plugin_directory) or die $!;
while (my $file = readdir(DIR)) {
 next unless ($file =~ m/(.*?)\.pm$/);
 push(@modules,$1);
 }
closedir(DIR);
foreach $module (@modules){
 eval "require $module";
 &{"init_$module"};
 if ($module_error ne "") { print "$module_error"; }
 }
################################################################################
### Events Management
################################################################################
if ( $hooks{"$form{'action'}"}) { eval $hooks{"$form{'action'}"} } else { &main; }
exit;
################################################################################
#### TRAPS ANY NON EXISTING OR WRONG SUB RETURNING THE NAME IN module_error
################################################################################
sub AUTOLOAD {
 use vars qw($AUTOLOAD);
 my $error = $AUTOLOAD;
 $error =~ s/.*:://;
 $module_error = "$error";
}

This application does the following:

  • it scans a directory (in our case we called it “plugins”) which is going to be a sub-directory of the folder where you will be placing this script. This directory will contain plugin files which will have the extension “.pm” (Perl Module);
  • once the plugins are loaded, it’s executed the init subroutine located in each plugin. This is used to load the “hooks” that will be then used to execute the various functions.
  • upon completion of the initialization procedure, the application tests the existence in memory of the hook to execute “something”

One plugin that always have to be in the system is the “main.pm” which has to contain the function “&main” that is used as the default execution point OR used in case a hook is not found in the system.

Please note that like in any perl module, the last line of the plugin must contain “return 1;” or it will go in error.

1
2
3
4
5
6
7
8
9
10
#####################
# MODULE : main.pm
#####################
sub init_main{
	}
 
sub main {
        print qq~Click here to <a href="$var_script?action=test">Do Something</a>~;
        }
return 1;
1
2
3
4
5
6
7
8
9
10
11
#####################
# MODULE : test.pm
#####################
sub init_test{
	$hooks{'test'} = '&amp;test_dosomething;';
	}
 
sub test_dosomething{
        print "I am doing something right here";
        }
return 1;

As you notice, in the plugins, the first function is always named

1
init_{plugin name}

and the name has to be exactly like the file name (minus the extension).

All the functions of the plugin should begin with the name of the plugin.

Leave a Reply

Your email address will not be published. Required fields are marked *