Take me over?
NAME
Object::Meta::Plugin::Host - Hosts plugins that work like Object::Meta::Plugin (Or rather Object::Meta::Plugin::Useful, because the prior doesn't work per se). Can be a plugin if subclassed, or contains a plugin which can help it to plug.
SYNOPSIS
# if you want working examples, read basic.t in the distribution
# i don't know what kind of a synopsis would be useful for this.
my $host = new Object::Meta::Plugin::Host;
eval { $host->method() }; # should die
$host->plug($plugin); # $plugin defines method
$host->plug($another); # $another defines method and another
# $another supplied the following, since it was plugged in later
$host->method();
$host->another($argument);
$host->unplug($another);
$host->method(); # now $plugin's method is used
DESCRIPTION
Object::Meta::Plugin::Host is an implementation of a plugin host, as illustrated in Object::Meta::Plugin.
The host is not just simply a merged namespace. It is designed to allow various plugins to provide similar capabilities - methods with conflicting namespace. Conflicting namespaces can coexist, and take precedence over, as well as access one another. An examplifying scenario could be an image processor, whose various filter plugins all define the method "process". The plugins are all installed, ordered as the effect should be taken out, and finally atop them all a plugin which wraps them into a pipeline is set. It's process method will look like
sub process {
my $self = shift;
my $image = shift;
foreach my $plugin (reverse @{ $self->super->stack('process') }){
next if $plugin == $self->self;
$image = $self->super->specific($plugin)->process($image);
}
# for (my $i = 1; $i <= $#{ $self->super->stack('process') }){
# $image = $self->offset($i)->process($image);
# }
return $image;
}
When a plugin's method is entered it receives, instead of the host object, a context object, particular to itself. The context object allows it access to the host host, the plugin's siblings, and so forth explicitly, while implicitly providing one or two modifications. The first is that all calls against $_[0], which is the context, have an altered method priority - calls will be mapped to the current plugin's method before the host defaults methods. The second, default but optional implicit feature is that all modifications on the reference received in $_[0] are mapped via a tie interface to the original plugin's data structures.
Such a model enables a dumb plugin to work quite happily with others, even those which may take it's role.
A more complex plugin, aware that it may not be peerless, could explicitly ask for the default (host defined) methods it calls, instead of it's own. It can request to call a method on the plugin which succeeds it or precedes it in a certain method's stack.
The interface aims to be simple enough to be flexible, trying for the minimum it needs to define in order to be useful, and creating workarounds for the limitations this minimum imposes.
METHODS
Host
- methods
-
Returns a hash ref, to a hash of method names => array refs. The array refs are the stacks, and they can be accessed individually via the
stack
method. - plug PLUGIN [ LIST ]
-
Takes a plugin, and calls it's
init
with the supplied arguments. The return value is then fed toregister
. - plugins
-
Returns a hash ref, to a refhash. The keys are references to the plugins, and the values are export lists.
- register EXPORTLIST
-
Takes an export list and integrates it's context into the method tree. The plugin the export list represents will be the topmost.
- specific PLUGIN
-
Returns a context object for a specific plugin. Like
Context
'snext
,prev
, andoffset
, only with a plugin instead of an index. - stack METHOD
-
Returns an array ref to a stack of plugins, for the method. The last element is considered the topmost plugin, which is counter intuitive, considering
offset
works with higher indices being lower perceedence. - unplug PLUGIN [ PLUGIN ... ]
-
Takes a reference to a plugin, and sweeps the method tree clean of any of it's occurrences.
- unregister EXPORTLIST [ EXPORTLIST ... ]
-
Takes an export list, and unmerges it from the currently active one. If it's empty, calls
unplug
. If something remains, it cleans out the stacks manually.This behavior may change, as a plugin which has no active methods might still need be available.
Context
- self
- plugin
-
Grants access to the actual plugin object which was passed via the export list. Use for internal storage space. See
CONTEXT STYLES (ACCESS TO PLUGIN INTERNALS)
. - super
- host
-
Grants access to the host object. Use
$self-
super->method> if you want to override the precedence of the current plugin. - next
- prev
- offset INTEGER
-
Generates a new context, having to do with a plugin n steps away from this, to a certain direction.
next
andprev
calloffset
with 1 and -1 respectively. The offset object they return, has an autoloader which will search to see where the current plugin's instance is in the stack of a certain method, and then move a specified offset from that, and use the plugin in that slot.
CONTEXT STYLES (ACCESS TO PLUGIN INTERNALS)
The context shim styles are set by the object returned by the info
method of the export list. Object::Meta::Plugin::ExportList will create an info object which will have the style
method return tied by default.
Implicit access via tie
This way the shim object will be a tied reference, of the type the original plugin's data structure (if it is a hash, array or scalar). The tie will interface to the contents of the original plugin object.
In this way the plugin object can gain access to it's internals normally, but the methods it calls will be called on the context shim. The context data will be stored in the object behind the tie, and be access via a called to tie
.
This way implicit and complete namespace (context shim & plugin) separation can be made, without the plugin needing to do any tricks. The downsides is that the plugin can't be a blessed glob or code ref, or if the plugin does not behave well regarding blessing and stuff.
If the plugin is funny tied structure, you have to set the style to 'force-tied', in order for plug
not to die. Do this by sending an export list info object as the first argument to a Useful init
. But make sure you're not breaking anything.
Explicit access via $self->self
This method is theoretically much more efficient.
In this style, the plugin will get the actual structure of the context shim. If tied access is in applicable, that's the way to go.
In order to get access to the plugin structure the plugin must call $self-
self> or $self-
plugin>.
C'est tout.
DIAGNOSIS
All errors are errors (e.g not warnings), and are thus fatal, and are produced with Carp's croak
. They can be trapped with eval
if necessary.
- The offset is outside the bounds of the method stack for "%s"
-
The offset requested (via the methods
next
,prev
oroffset
) is outside of the the stack of plugins for that method. That is, no plugin could be found that offset away from the current plugin.Generated at call time.
- Can't locate object method "%s" via any plugin in %s
-
The method requested could not be found in any of the plugged in plugins. Instead of a classname, however, this error will report the host object's value.
Generated at call time.
- Method "%s" is reserved for use by the context object
-
The host
AUTOLOAD
er was queried for a method defined in the context class. This is not a good thing, because it can cause unexpected behavior.Generated at
plug
or call time. - %s doesn't look like a plugin
-
The provided object's method
can
did not return a true value forinit
. This is what we define as a plugin for clarity.Generated at
plug
time. - %s doesn't look like a valid export list
-
The export list handed to the
register
method did not define all the necessary methods, as documented in Object::Meta::Plugin::ExportList.Generated at
register
time. - Can't locate object method "%s" via plugin %s
-
The method, requested for export by the export list, cannot be found via
can
within the plugin.Generated at
register
time. - %s is not plugged into %s
-
When requesting a specific plugin to be used, and the plugin doesn't exist this happens.
Generated at
specific
time.
CAVEATS
The implementation is by no means optimized. I doubt it's fast, but I don't really care. It's supposed to create a nice framework for a complex application. It's more efficient programming, not execution. This may be worked on a bit.
The
can
method (e.g.UNIVERSAL::can
) is depended on. Without it everything will break. If you try to plug something nonstandard into a host, and export somethingUNIVERSAL::can
won't say is there, implementcan
yourself.
BUGS
Just you wait. See TODO
for what I have in stock!
TODO
Decide if
unregister
should or shouldn't callunplug
if nothing of the plugin remains by the 0.02 release. Currently leaning towards "no".Offset contexting AUTOLOADER needs to diet.
Since 5.8 is a prerequisite, use Scalar::Util to cleanup references which shouldn't be strong.
COPYRIGHT & LICENSE
Copyright 2003 Yuval Kogman. All rights reserved.
This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.
AUTHOR
Yuval Kogman <nothingmuch@woobling.org>
SEE ALSO
Class::Classless, Class::Prototyped, Class::SelfMethods, Class::Object, and possibly Pipeline & Class::Dynamic.