1 package MooseX::Object::Pluggable;
10 our $VERSION = '0.0002';
14 MooseX::Object::Pluggable - Make your classes pluggable
21 with 'MooseX::Object::Pluggable';
25 package MyApp::Plugin::Pretty;
28 sub pretty{ print "I am pretty" }
35 $app->load_plugin('Pretty');
40 This module is meant to be loaded as a role from Moose-based classes
41 it will add five methods and five attributes to assist you with the loading
42 and handling of plugins and extensions for plugins. I understand that this may
43 pollute your namespace, however I took great care in using the least ambiguous
46 =head1 How plugins Work
48 Plugins and extensions are just Roles by a fancy name. They are loaded at runtime
49 on demand and are instance, not class based. This means that if you have more than
50 one instance of a class they can all have different plugins loaded. This is a feature.
52 Plugin methods are allowed to C<around>, C<before>, C<after>
53 their consuming classes, so it is important to watch for load order as plugins can
54 and will overload each other. You may also add attributes through has.
56 Even thouch C<override> will work in basic cases, I STRONGLY discourage it's use
57 and a warning will be thrown if you try to use it.
58 This is closely linked to the way multiple roles being applies is handles and is not
59 likely to change. C<override> bevavior is closely linked to inheritance and thus will
60 likely not work as you expect it in multiple inheritance situations. Point being,
61 save yourself the headache.
63 =head1 How plugins are loaded
65 You don't really need to understand anything except for the first paragraph.
67 The first time you load a plugin a new anonymous L<Moose::Meta::Class> will be
68 created. This class will inherit from your pluggable object and then your object
69 will be reblessed to an instance of this anonymous class. This means that
70 C<$self-E<gt>blessed> and C<ref $self> will no longer return the name of your object,
71 they will instead return the name of the anonymous class created at runtime. Your
72 original class name can be located at C<($self-E<gt>meta-E<gt>superclasses)[0]>
74 Once the anonymous subclass exists all plugin roles will be C<apply>ed to this class
75 directly. This "subclass" though is in fact now C<$self> and it C<isa($yourclassname)>.
76 If this is confusing.. it should be, thats why you let me handle it. Just know that it
77 has to be done this way in order for plugins to override core functionality.
81 For a simple example see the tests for this distribution.
87 String. The prefix to use for plugin names provided. MyApp::Plugin is sensible.
91 Boolean. Indicates whether we should attempt to load plugin extensions.
96 String. The namespace plugin extensions have. Defaults to 'ExtensionFor'.
98 This means that is _plugin_ns is "MyApp::Plugin" and _plugin_ext_ns is
99 "ExtensionFor" loading plugin "Bar" would search for extensions in
100 "MyApp::Plugin::Bar::ExtensionFor::*".
102 =head2 _plugin_loaded
104 HashRef. Keeps an inventory of what plugins are loaded and what the actual
105 module name is to avoid multiple loading.
107 =head2 __plugin_subclass
109 Object. This holds the subclass of our pluggable object in the form of an
110 anonymous L<Moose::Meta::Class> instance. All roles are actually applied to
111 this instance instead of the original class instance in order to not lose
112 the original object name as roles are applied. The anonymous class will be
113 automatically generated upon first use.
117 #--------#---------#---------#---------#---------#---------#---------#---------#
119 has _plugin_ns => (is => 'rw', required => 1, isa => 'Str',
120 default => 'Plugin');
122 has _plugin_ext => (is => 'rw', required => 1, isa => 'Bool',
124 has _plugin_ext_ns => (is => 'rw', required => 1, isa => 'Str',
125 default => 'ExtensionFor');
126 has _plugin_loaded => (is => 'rw', required => 1, isa => 'HashRef',
127 default => sub{ {} });
129 has __plugin_subclass => ( is => 'rw', required => 0, isa => 'Object', );
131 #--------#---------#---------#---------#---------#---------#---------#---------#
133 =head1 Public Methods
135 =head2 load_plugin $plugin
137 This is the only method you should be using.
138 Load the apropriate role for C<$plugin> as well as any
139 extensions it provides if extensions are enabled.
144 my ($self, $plugin) = @_;
145 die("You must provide a plugin name") unless $plugin;
147 my $loaded = $self->_plugin_loaded;
148 return 1 if exists $loaded->{$plugin};
150 my $role = $self->_role_from_plugin($plugin);
152 $loaded->{$plugin} = $role if $self->_load_and_apply_role($role);
153 $self->load_plugin_ext($plugin) if $self->_plugin_ext;
155 return exists $loaded->{$plugin};
159 =head2 _load_plugin_ext
161 Will load any extensions for a particular plugin. This should be called
162 automatically by C<load_plugin> so you don't need to worry about it.
163 It basically attempts to load any extension that exists for a plugin
164 that is already loaded. The only reason for using this is if you want to
165 keep _plugin_ext as false and only load extensions manually, which I don't
171 my ($self, $plugin) = @_;
172 die("You must provide a plugin name") unless $plugin;
173 my $role = $self->_role_from_plugin($plugin);
175 # $p for plugin, $r for role
176 while( my($p,$r) = each %{ $self->_plugin_loaded }){
177 my $ext = join "::", $role, $self->_plugin_ext_ns, $p;
179 $self->_load_and_apply_role( $ext )
180 if Class::Inspector->installed($ext);
182 #go back to prev loaded modules and load extensions for current module?
183 #my $ext2 = join "::", $r, $self->_plugin_ext_ns, $plugin;
184 #$self->_load_and_apply_role( $ext2 )
185 # if Class::Inspector->installed($ext2);
189 =head1 Private Methods
191 There's nothing stopping you from using these, but if you are using them
192 you are probably doing something wrong.
194 =head2 _plugin_subclass
196 Creates, if needed and returns the anonymous instance of the consuming objects
197 subclass to which roles will be applied to.
201 sub _plugin_subclass{
203 my $anon_class = $self->__plugin_subclass;
205 #initialize if we havnt been initialized already.
206 unless(ref $anon_class && $self->meta->is_anon_class){
208 #create an anon class that inherits from $self that plugins can be
209 #applied to safely and store it within the $self instance.
210 $anon_class = Moose::Meta::Class->
211 create_anon_class(superclasses => [$self->meta->name]);
212 $self->__plugin_subclass( $anon_class );
214 #rebless $self as the anon class which now inherits from ourselves
215 #this allows the anon class to override methods in the consuming
216 #class while keeping a stable name and set of superclasses
217 bless $self => $anon_class->name
218 unless $self->meta->name eq $anon_class->name;
224 =head2 _role_from_plugin $plugin
226 Creates a role name from a plugin name. If the plugin name is prepended
227 with a C<+> it will be treated as a full name returned as is. Otherwise
228 a string consisting of C<$plugin> prepended with the application name
229 and C<_plugin_ns> will be returned. Example
231 #assuming appname MyApp and C<_plugin_ns> 'Plugin'
232 $self->_role_from_plugin("MyPlugin"); # MyApp::Plugin::MyPlugin
236 sub _role_from_plugin{
237 my ($self, $plugin) = @_;
239 my $name = $self->meta->is_anon_class ?
240 ($self->meta->superclasses)[0] : $self->blessed;
242 $plugin =~ /^\+(.*)/ ? $1 : join '::', $name, $self->_plugin_ns, $plugin;
245 =head2 _load_and_apply_role $role
247 Require C<$role> if it is not already loaded and apply it to
248 C<_plugin_subclass>. This is the meat of this module.
252 sub _load_and_apply_role{
253 my ($self, $role) = @_;
254 die("You must provide a role name") unless $role;
256 #Throw exception if plugin is not installed
257 die("$role is not available on this system")
258 unless Class::Inspector->installed($role);
261 unless( Class::Inspector->loaded($role) ){
262 eval "require $role" || die("Failed to load role: $role");
265 carp("Using 'override' is strongly discouraged and may not behave ".
266 "as you expect it to. Please use 'around'")
267 if scalar keys %{ $role->meta->get_override_method_modifiers_map };
269 #apply the plugin to the anon subclass
270 die("Failed to apply plugin: $role")
271 unless $role->meta->apply( $self->_plugin_subclass );
283 L<Moose>, L<Moose::Role>
287 Guillermo Roditi, <groditi@cpan.org>
293 Please report any bugs or feature requests to
294 C<bug-moosex-object-pluggable at rt.cpan.org>, or through the web interface at
295 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooseX-Object-Pluggable>.
296 I will be notified, and then you'll automatically be notified of progress on
297 your bug as I make changes.
301 You can find documentation for this module with the perldoc command.
303 perldoc MooseX-Object-Pluggable
305 You can also look for information at:
309 =item * AnnoCPAN: Annotated CPAN documentation
311 L<http://annocpan.org/dist/MooseX-Object-Pluggable>
315 L<http://cpanratings.perl.org/d/MooseX-Object-Pluggable>
317 =item * RT: CPAN's request tracker
319 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooseX-Object-Pluggable>
323 L<http://search.cpan.org/dist/MooseX-Object-Pluggable>
327 =head1 ACKNOWLEDGEMENTS
331 =item #Moose - Huge number of questions
333 =item Matt S Trout <mst@shadowcatsystems.co.uk> - ideas / planning.
335 =item Stevan Little - EVERYTHING. Without him this would have never happened.
341 Copyright 2007 Guillermo Roditi. All Rights Reserved. This is
342 free software; you may redistribute it and/or modify it under the same
343 terms as Perl itself.