Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Failed to load latest commit information.
bench Building plugin list with grep seems faster than next-if



MooX::Role::Pluggable - Add a plugin pipeline to your cows


# A simple pluggable dispatcher:
package MyDispatcher;
use MooX::Role::Pluggable::Constants;
use Moo;
with 'MooX::Role::Pluggable';

sub BUILD {
  my ($self) = @_;

  # (optionally) Configure our plugin pipeline
    reg_prefix => 'Plug_',
    ev_prefix  => 'Event_',
    types      => {
      NOTIFY  => 'N',
      PROCESS => 'P',

around '_pluggable_event' => sub {
  # This override redirects internal events (errors, etc) to ->process()
  my ($orig, $self) = splice @_, 0, 2;
  $self->process( @_ )

sub process {
  my ($self, $event, @args) = @_;

  # Dispatch to 'P_' prefixed "PROCESS" type handlers.
  # _pluggable_process will automatically strip a leading 'ev_prefix'
  # (see the call to _pluggable_init above); that lets us easily
  # dispatch errors to our P_plugin_error handler below without worrying
  # about our ev_prefix ourselves:
  my $retval = $self->_pluggable_process( PROCESS =>

  unless ($retval == EAT_ALL) {
    # The pipeline allowed the event to continue.
    # A dispatcher might re-dispatch elsewhere, etc.

sub shutdown {
  my ($self) = @_;
  # Unregister all of our plugins.

sub P_plugin_error {
  # Since we re-dispatched errors in our _pluggable_event handler,
  # we could handle exceptions here and then eat them, perhaps:
  my ($self, undef) = splice @_, 0, 2;

  # Arguments are references:
  my $plug_err  = ${ $_[0] };
  my $plug_obj  = ${ $_[1] };
  my $error_src = ${ $_[2] };

  # ...


# A Plugin object.
package MyPlugin;

use MooX::Role::Pluggable::Constants;

sub new { bless {}, shift }

sub Plug_register {
  my ($self, $core) = @_;

  # Subscribe to events:
  $core->subscribe( $self, 'PROCESS',

  # Log that we're here, do some initialization, etc ...

  return EAT_NONE

sub Plug_unregister {
  my ($self, $core) = @_;
  # Called when this plugin is unregistered
  # ... do some cleanup, etc ...
  return EAT_NONE

sub P_my_event {
  # Handle a dispatched "PROCESS"-type event:
  my ($self, $core) = splice @_, 0, 2;

  # Arguments are references and can be modified:
  my $arg = ${ $_[0] };

  # ... do some work ...

  # Return an EAT constant to control event lifetime
  # EAT_NONE allows this event to continue through the pipeline
  return EAT_NONE

# An external package that interacts with our dispatcher;
# this is just a quick and dirty example to show external
# plugin manipulation:

package MyController;
use Moo;

has dispatcher => (
  is      => 'rw',
  default => sub {  MyDispatcher->new()  },

sub BUILD {
  my ($self) = @_;
  $self->dispatcher->plugin_add( 'MyPlugin',

sub do_stuff {
  my $self = shift;
  $self->dispatcher->process( 'my_event', @_ )


A Moo::Role for turning instances of your class into pluggable objects. Consumers of this role gain a plugin pipeline and methods to manipulate it, as well as a flexible dispatch system (see "_pluggable_process").

The logic and behavior is based almost entirely on Object::Pluggable (see "AUTHOR"). Some methods are the same; implementation & some interface differ. Dispatch is significantly faster -- see "Performance".

If you're using POE, also see MooX::Role::POE::Emitter, which consumes this role.



  # Prefix for registration events.
  # Defaults to 'plugin_' ('plugin_register' / 'plugin_unregister')
  reg_prefix   => 'plugin_',

  # Prefix for dispatched internal events
  #  (add, del, error, register, unregister ...)
  # Defaults to 'plugin_ev_'
  event_prefix => 'plugin_ev_',

  # Map type names to prefixes;
  #  Event types can be named arbitrarily. Their respective prefix is
  #  prepended when dispatching events of that type.
  #  Here are the defaults:
  types => {
    NOTIFY  => 'N',
    PROCESS => 'P',

A consumer can call _pluggable_init to set up pipeline-related options appropriately; this should be done prior to loading plugins or dispatching to "_pluggable_process". If it is not called, the defaults (as shown above) are used.

types => can be either an ARRAY of event types (which will be used as prefixes):

types => [ qw/ IncomingEvent OutgoingEvent / ],

... or a HASH mapping an event type to a prefix:

types => {
  Incoming => 'I',
  Outgoing => 'O',

A trailing _ is automatically appended to event type prefixes when events are dispatched via "_pluggable_process"; thus, an event destined for our 'Incoming' type shown above will be dispatched to appropriate I_ handlers:

# Dispatched to 'I_foo' method in plugins registered for Incoming 'foo':
$self->_pluggable_process( Incoming => 'foo', 'bar', 'baz' );

reg_prefix/event_prefix are not automatically munged in any way.

An empty string is a valid value for reg_prefix/event_prefix.



Shuts down the plugin pipeline, unregistering/unloading all known plugins.


# In our consumer:
sub _pluggable_event {
  my ($self, $event, @args) = @_;
  # Dispatch out, perhaps.

_pluggable_event is called for internal notifications, such as plugin load/unload and error reporting (see "Internal Events") -- it should be overriden in your consuming class to do something useful with the dispatched event and any arguments passed.

The $event given will be prefixed with the configured event_prefix.

Also see "Internal Events".


A plugin is any blessed object that is registered with your Pluggable object via "plugin_add"; during registration, plugins usually subscribe to some events via "subscribe".

See "plugin_add" regarding loading plugins.


Subscribe a plugin to some pluggable events.

$self->subscribe( $plugin_obj, $type, @events );

Registers a plugin object to receive @events of type $type.

This is typically called from within the plugin's registration handler (see "plugin_register"):

# In a plugin:
sub plugin_register {
  my ($self, $core) = @_;

  $core->subscribe( $self, PROCESS =>

  $core->subscribe( $self, NOTIFY => 'all' );


Subscribe to all to receive all events -- but note that subscribing many plugins to 'all' events is less performant during calls to "_pluggable_process" than many subscriptions to specific events.


Unsubscribe a plugin from subscribed events.

Carries the same arguments as "subscribe".

The plugin is still loaded and registered until "plugin_del" is called, even if there are no current event subscriptions.


Defined in your plugin(s) and called at load time.

(Note that 'plugin_' is just a default register method prefix; it can be changed prior to loading plugins. See "_pluggable_init" for details.)

The plugin_register method is called on a loaded plugin when it is added to the pipeline; it is passed the plugin object ($self), the Pluggable object, and any arguments given to "plugin_add" (or similar registration methods).

Normally one might call a "subscribe" from here to start receiving events after load-time:

# In a plugin:
sub plugin_register {
  my ($self, $core, @args) = @_;
  $core->subscribe( $self, 'NOTIFY', @events );


Defined in your plugin(s) and called at load time.

(Note that 'plugin_' is just a default register method prefix; it can be changed prior to loading plugins. See "_pluggable_init" for details.)

The unregister counterpart to "plugin_register", called when the plugin object is removed from the pipeline (via "plugin_del" or "_pluggable_destroy").

# In a plugin:
sub plugin_unregister {
  my ($self, $core) = @_;

Carries the same arguments as "plugin_register".



# In your consumer's dispatch method:
my $eat = $self->_pluggable_process( $type, $event, \@args );
return 1 if $eat == EAT_ALL;

The _pluggable_process method handles dispatching.

If $event is prefixed with our event prefix (see "_pluggable_init"), the prefix is stripped prior to dispatch (to be replaced with a type prefix matching the specified $type).

Arguments should be passed as a reference to an array. During dispatch, references to the provided arguments are passed to relevant plugin subroutines following the automatically-prepended plugin and Pluggable consumer objects (respectively); this allows for argument modification as an event is passed along the plugin pipeline:

my @args = qw/baz bar/;
$self->_pluggable_process( NOTIFY => foo => \@args );

# In a plugin:
sub N_foo {
  # Remove automatically-provided plugin and consumer objects from @_
  my ($self, $core) = splice @_, 0, 2;

  # Dereference expected scalars
  my $bar = ${ $_[0] };
  my $num = ${ $_[1] };

  # Increment actual second argument before pipeline dispatch continues
  ++${ $_[1] };


Dispatch Process

Your Pluggable consuming class typically provides syntax sugar to dispatch different types or "classes" of events:

sub process {
  # Dispatch to 'PROCESS'-type events
  my ($self, $event, @args) = @_;
  my $eat = $self->_pluggable_process( PROCESS => $event, \@args );
  # ... possibly take further action based on $eat return value, see below

sub notify {
  # Dispatch to 'NOTIFY'-type events
  my ($self, $event, @args) = @_;
  my $eat = $self->_pluggable_process( NOTIFY => $event, \@args );
  # ...

Event types and matching prefixes can be arbitrarily named to provide event dispatch flexibility. For example, the dispatch process for $event 'foo' of $type 'NOTIFY' performs the following actions:

$self->_pluggable_process( NOTIFY => foo => \@args );

# - Prepend the known prefix for the specified type and '_'
#   'foo' -> 'N_foo'
# - Attempt to dispatch to $self->N_foo()
# - If no such method, attempt to dispatch to $self->_default()
#   (When using _default, the method we were attempting to call is prepended
#   to arguments.)
# - If the event was not eaten by the Pluggable consumer (see below), call
#   $plugin->N_foo() for subscribed plugins sequentially until event is eaten
#   or no relevant plugins remain.

"Eaten" means a handler returned an EAT_* constant from MooX::Role::Pluggable::Constants indicating that the event's lifetime should terminate.

If our consuming class provides a method or '_default' that returns:

EAT_ALL:    skip plugin pipeline, return EAT_ALL
EAT_CLIENT: continue to plugin pipeline
            return EAT_ALL if plugin returns EAT_PLUGIN later
EAT_PLUGIN: skip plugin pipeline entirely
            return EAT_NONE unless EAT_CLIENT was seen previously
EAT_NONE:   continue to plugin pipeline

If one of our plugins in the pipeline returns:

EAT_ALL:    skip further plugins, return EAT_ALL
EAT_CLIENT: continue to next plugin, set pending EAT_ALL
            (EAT_ALL will be returned when plugin processing finishes)
EAT_PLUGIN: return EAT_ALL if previous sub returned EAT_CLIENT
            else return EAT_NONE
EAT_NONE:   continue to next plugin

This functionality (derived from Object::Pluggable) provides fine-grained control over event lifetime.

Higher-level layers (see MooX::Role::POE::Emitter for an example) can check for an EAT_ALL return value from "_pluggable_process" to determine whether to continue operating on a particular event (re-dispatch elsewhere, for example).

Plugins can use EAT_CLIENT to indicate that an event should be eaten after plugin processing is complete, EAT_PLUGIN to stop plugin processing, and EAT_ALL to indicate that the event should not be dispatched further.

Plugin Management Methods

These plugin pipeline management methods will set $@, warn via Carp, and return an empty list on error (unless otherwise noted). See "plugin_error" regarding errors raised during plugin registration and dispatch.


$self->plugin_add( $alias, $plugin_obj, @args );

Add a plugin object to the pipeline.

Returns the same values as "plugin_pipe_push".


$self->plugin_del( $alias_or_plugin_obj, @args );

Remove a plugin from the pipeline.

Takes either a plugin alias or object. Returns the removed plugin object.


my $plug_obj = $self->plugin_get( $alias );
      my ($plug_obj, $plug_alias) = $self->plugin_get( $alias_or_plugin_obj );

In scalar context, returns the plugin object belonging to the specified alias.

In list context, returns the object and alias, respectively.


my @loaded = $self->plugin_alias_list;

Returns the list of loaded plugin aliases.

As of version 1.002, the list is ordered to match actual plugin dispatch order. In prior versions, the list is unordered.


  old    => $alias_or_plugin_obj,
  alias  => $new_alias,
  plugin => $new_plugin_obj,
  # Optional:
  register_args   => [ ],
  unregister_args => [ ],

Replace an existing plugin object with a new one.

Returns the old (removed) plugin object.

Pipeline Methods


$self->plugin_pipe_push( $alias, $plugin_obj, @args );

Add a plugin to the end of the pipeline; typically one would call "plugin_add" rather than using this method directly.


my $plug = $self->plugin_pipe_pop( @unregister_args );

Pop the last plugin off the pipeline, passing any specified arguments to "plugin_unregister".

In scalar context, returns the plugin object that was removed.

In list context, returns the plugin object and alias, respectively.


$self->plugin_pipe_unshift( $alias, $plugin_obj, @args );

Add a plugin to the beginning of the pipeline.

Returns the total number of loaded plugins (or an empty list on failure).


$self->plugin_pipe_shift( @unregister_args );

Shift the first plugin off the pipeline, passing any specified args to "plugin_unregister".

In scalar context, returns the plugin object that was removed.

In list context, returns the plugin object and alias, respectively.


my $idx = $self->plugin_pipe_get_index( $alias_or_plugin_obj );
if ($idx < 0) {
  # Plugin doesn't exist

Returns the position of the specified plugin in the pipeline.

Returns -1 if the plugin does not exist.


  after  => $alias_or_plugin_obj,
  alias  => $new_alias,
  plugin => $new_plugin_obj,
  # Optional:
  register_args => [ ],

Add a plugin to the pipeline after the specified previously-existing alias or plugin object. Returns boolean true on success.


  before => $alias_or_plugin_obj,
  alias  => $new_alias,
  plugin => $new_plugin_obj,
  # Optional:
  register_args => [ ],

Similar to "plugin_pipe_insert_after", but insert before the specified previously-existing plugin, not after.


$self->plugin_pipe_bump_up( $alias_or_plugin_obj, $count );

Move the specified plugin 'up' $count positions in the pipeline.

Returns -1 if the plugin cannot be bumped up any farther.


$self->plugin_pipe_bump_down( $alias_or_plugin_obj, $count );

Move the specified plugin 'down' $count positions in the pipeline.

Returns -1 if the plugin cannot be bumped down any farther.

Internal Events

These events are dispatched to "_pluggable_event" prefixed with our pluggable event prefix; see "_pluggable_init".


Issued via "_pluggable_event" when an error occurs.

The arguments are, respectively: the error string, the offending object, and a string describing the offending object ('self' or 'plugin' with name appended).


Issued via "_pluggable_event" when a new plugin is registered.

Arguments are the new plugin alias and object, respectively.


Issued via "_pluggable_event" when a plugin is unregistered.

Arguments are the old plugin alias and object, respectively.


My motivation for writing this role was two-fold; I wanted Object::Pluggable behavior but without screwing up my class inheritance, and I needed a little bit more juice out of the pipeline dispatch process for a fast-paced daemon.

Dispatcher performance has been profiled and micro-optimized, but I'm most certainly open to further ideas ;-)

Some Benchmark runs. 30000 "_pluggable_process" calls with 20 loaded plugins dispatching one argument to one handler that does nothing except return EAT_NONE:

                    Rate    object-pluggable moox-role-pluggable
object-pluggable    6173/s                  --                -38%
moox-role-pluggable 9967/s                 61%

                     Rate    object-pluggable moox-role-pluggable
object-pluggable     6224/s                  --                -38%
moox-role-pluggable 10000/s                 61%                  --

                    Rate    object-pluggable moox-role-pluggable
object-pluggable    6383/s                  --                -35%
moox-role-pluggable 9868/s                 55%

(Benchmark script is available in the bench/ directory of the upstream repository; see


Jon Portnoy

Written from the ground up, but conceptually derived entirely from Object::Pluggable (c) Chris Williams, Apocalypse, Hinrik Orn Sigurosson and Jeff Pinyan.

Licensed under the same terms as Perl 5; please see the license that came with your Perl distribution for details.

Something went wrong with that request. Please try again.