On 08.04.2010 11:46, Dan Horne wrote:
> I have a question regarding the best method for implementing application
> plugins - i.e. if there is an idiomatic way to do this in Moose. Here's a
> high level description of what I'm trying to achieve:



> 
>    1. When the application starts, it finds all modules in a "plugins"
>    directory.

Create a package called MyRegistry with a load_registry() class method
that loads all the classes enumerated by Module::Pluggable.

# Create function that list type handler classes
use Module::Pluggable
    sub_name    => 'type_handlers',
    search_path => [ 'MyApp::Plugins' ],
    only        => qr!^MyApp::Plugins::\w+$!,
    except      => 'MyApp::Plugins::Plugin',  # Abstract base class for
plugins, if needed (or the interface role module)
;

# Load information from all handlers
sub load_registry {
    use Class::MOP ();
    Class::MOP::load_class($_) for type_handlers();
}

>    2. We loop through each module in turn:
>       1. We call a register method, which registers the plugin (this method
>       returns the plugin name). This becomes the key in the "plugins" hash

use Sub::Exporter -setup => {
    exports => [qw( register_type )],
    groups  => {
        default => [qw( register_type )],
    },
};

sub register_type {
    my ($str) = @_;
    confess("Not a valid value") unless defined $str and length $str > 0;
    __PACKAGE__->type_registry->{$str} = scalar caller();
}

use MooseX::ClassAttribute;
class_has 'type_registry' => (
    is      => 'ro',
    isa     => 'HashRef[Str]',
    default => sub { +{} },
);

>       2. We then call a the plugin "functionality" method (I don't have a
>       good name off the top of my head), which returns an anonymous
> sub containing
>       the plugin functionality, (or perhaps a callback method name)

This is your factory method...

sub new_from_type {
    my ($class, $type, @params) = @_;
    confess("new_from_object() is a class method") unless
defined($class) and not ref($class);

    # Load registry unless already loaded.
    my $registry = 'MyApp::PluginRegistry';
    Class::MOP::load_class($registry);
    $registry->load_registry();  # Runtime init registry

    # Resolve handler class
    my $handler_class = $registry->type_registry->{ $type };
    unless ( $handler_class ) {
        confess(
            "Could not resolve handler class for type '"
          . $type
          . "'"
        );
    }

    # Instantiate and return handler
    return $handler_class->new( @params );
}

>    3. Now, whenever the plugin is called by name, the supplied
>    "functionality"method is invoked

I guess you can extend the register_type() method to also apply a role
to the caller() class that possibly includes some requires() calls or
explicitly check if the caller() does the required role (or extends the
base class, depending on your point of view).

-- Robin

Reply via email to