1 package MooseX::Object::Pluggable;
9 our $VERSION = '0.0003';
13 MooseX::Object::Pluggable - Make your classes pluggable
20 with 'MooseX::Object::Pluggable';
24 package MyApp::Plugin::Pretty;
27 sub pretty{ print "I am pretty" }
34 $app->load_plugin('Pretty');
39 This module is meant to be loaded as a role from Moose-based classes
40 it will add five methods and four attributes to assist you with the loading
41 and handling of plugins and extensions for plugins. I understand that this may
42 pollute your namespace, however I took great care in using the least ambiguous
45 =head1 How plugins Work
47 Plugins and extensions are just Roles by a fancy name. They are loaded at runtime
48 on demand and are instance, not class based. This means that if you have more than
49 one instance of a class they can all have different plugins loaded. This is a feature.
51 Plugin methods are allowed to C<around>, C<before>, C<after>
52 their consuming classes, so it is important to watch for load order as plugins can
53 and will overload each other. You may also add attributes through has.
55 Please note that when you laod at runtime you lose the ability to wrap C<BUILD>
56 and roles using C<has> will not go through comile time checks like C<required>
59 Even thouch C<override> will work , I STRONGLY discourage it's use
60 and a warning will be thrown if you try to use it.
61 This is closely linked to the way multiple roles being applies is handles and is not
62 likely to change. C<override> bevavior is closely linked to inheritance and thus will
63 likely not work as you expect it in multiple inheritance situations. Point being,
64 save yourself the headache.
66 =head1 How plugins are loaded
68 When roles are applied at runtime an anonymous class will wrap your class and
69 C<$self-E<gt>blessed> and C<ref $self> will no longer return the name of your object,
70 they will instead return the name of the anonymous class created at runtime.
71 See C<_original_class_name>.
75 For a simple example see the tests included in this distribution.
81 String. The prefix to use for plugin names provided. MyApp::Plugin is sensible.
85 Boolean. Indicates whether we should attempt to load plugin extensions.
90 String. The namespace plugin extensions have. Defaults to 'ExtensionFor'.
92 This means that is _plugin_ns is "MyApp::Plugin" and _plugin_ext_ns is
93 "ExtensionFor" loading plugin "Bar" would search for extensions in
94 "MyApp::Plugin::Bar::ExtensionFor::*".
98 HashRef. Keeps an inventory of what plugins are loaded and what the actual
99 module name is to avoid multiple loading.
103 #--------#---------#---------#---------#---------#---------#---------#---------#
105 has _plugin_ns => (is => 'rw', required => 1, isa => 'Str',
106 default => 'Plugin');
108 has _plugin_ext => (is => 'rw', required => 1, isa => 'Bool',
110 has _plugin_ext_ns => (is => 'rw', required => 1, isa => 'Str',
111 default => 'ExtensionFor');
112 has _plugin_loaded => (is => 'rw', required => 1, isa => 'HashRef',
113 default => sub{ {} });
115 #--------#---------#---------#---------#---------#---------#---------#---------#
117 =head1 Public Methods
119 =head2 load_plugin $plugin
121 This is the only method you should be using. Load the apropriate role for
122 C<$plugin> as well as any extensions it provides if extensions are enabled.
127 my ($self, $plugin) = @_;
128 die("You must provide a plugin name") unless $plugin;
130 my $loaded = $self->_plugin_loaded;
131 return 1 if exists $loaded->{$plugin};
133 my $role = $self->_role_from_plugin($plugin);
135 $loaded->{$plugin} = $role if $self->_load_and_apply_role($role);
136 $self->load_plugin_ext($plugin) if $self->_plugin_ext;
138 return exists $loaded->{$plugin};
142 =head2 _load_plugin_ext
144 Will load any extensions for a particular plugin. This should be called
145 automatically by C<load_plugin> so you don't need to worry about it.
146 It basically attempts to load any extension that exists for a plugin
147 that is already loaded. The only reason for using this is if you want to
148 keep _plugin_ext as false and only load extensions manually, which I don't
154 my ($self, $plugin) = @_;
155 die("You must provide a plugin name") unless $plugin;
156 my $role = $self->_role_from_plugin($plugin);
158 # $p for plugin, $r for role
159 while( my($p,$r) = each %{ $self->_plugin_loaded }){
160 my $ext = join "::", $role, $self->_plugin_ext_ns, $p;
162 $self->_load_and_apply_role( $ext )
163 if Class::Inspector->installed($ext);
165 #go back to prev loaded modules and load extensions for current module?
166 #my $ext2 = join "::", $r, $self->_plugin_ext_ns, $plugin;
167 #$self->_load_and_apply_role( $ext2 )
168 # if Class::Inspector->installed($ext2);
172 =head2 _original_class_name
174 Because of the way roles apply C<$self-E<gt>blessed> and C<ref $self> will
175 no longer return what you expect. Instead use this class to get your original
180 sub _original_class_name{
182 return (grep {$_ !~ /^Moose::/} $self->meta->class_precedence_list)[0];
186 =head1 Private Methods
188 There's nothing stopping you from using these, but if you are using them
189 for anything thats not really complicated you are probably doing
190 something wrong. Some of these may be inlined in the future if performance
191 becomes an issue (which I doubt).
193 =head2 _role_from_plugin $plugin
195 Creates a role name from a plugin name. If the plugin name is prepended
196 with a C<+> it will be treated as a full name returned as is. Otherwise
197 a string consisting of C<$plugin> prepended with the application name
198 and C<_plugin_ns> will be returned. Example
200 #assuming appname MyApp and C<_plugin_ns> 'Plugin'
201 $self->_role_from_plugin("MyPlugin"); # MyApp::Plugin::MyPlugin
205 sub _role_from_plugin{
206 my ($self, $plugin) = @_;
208 return $1 if $plugin =~ /^\+(.*)/;
210 return join '::', ( $self->_original_class_name,
211 $self->_plugin_ns, $plugin );
214 =head2 _load_and_apply_role $role
216 Require C<$role> if it is not already loaded and apply it. This is
217 the meat of this module.
221 sub _load_and_apply_role{
222 my ($self, $role) = @_;
223 die("You must provide a role name") unless $role;
225 #Throw exception if plugin is not installed
226 die("$role is not available on this system")
227 unless Class::Inspector->installed($role);
230 unless( Class::Inspector->loaded($role) ){
231 eval "require $role" || die("Failed to load role: $role");
235 carp("Using 'override' is strongly discouraged and may not behave ".
236 "as you expect it to. Please use 'around'")
237 if scalar keys %{ $role->meta->get_override_method_modifiers_map };
239 #apply the plugin to the anon subclass
240 die("Failed to apply plugin: $role")
241 unless $role->meta->apply( $self );
252 L<Moose>, L<Moose::Role>, L<Class::Inspector>
256 Guillermo Roditi, <groditi@cpan.org>
262 Please report any bugs or feature requests to
263 C<bug-moosex-object-pluggable at rt.cpan.org>, or through the web interface at
264 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooseX-Object-Pluggable>.
265 I will be notified, and then you'll automatically be notified of progress on
266 your bug as I make changes.
270 You can find documentation for this module with the perldoc command.
272 perldoc MooseX-Object-Pluggable
274 You can also look for information at:
278 =item * AnnoCPAN: Annotated CPAN documentation
280 L<http://annocpan.org/dist/MooseX-Object-Pluggable>
284 L<http://cpanratings.perl.org/d/MooseX-Object-Pluggable>
286 =item * RT: CPAN's request tracker
288 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooseX-Object-Pluggable>
292 L<http://search.cpan.org/dist/MooseX-Object-Pluggable>
296 =head1 ACKNOWLEDGEMENTS
300 =item #Moose - Huge number of questions
302 =item Matt S Trout <mst@shadowcatsystems.co.uk> - ideas / planning.
304 =item Stevan Little - EVERYTHING. Without him this would have never happened.
310 Copyright 2007 Guillermo Roditi. All Rights Reserved. This is
311 free software; you may redistribute it and/or modify it under the same
312 terms as Perl itself.