Add myself to contributors
[gitmo/MooseX-Object-Pluggable.git] / lib / MooseX / Object / Pluggable.pm
CommitLineData
421e9f8d 1package MooseX::Object::Pluggable;
2
3use Carp;
421e9f8d 4use Moose::Role;
208f7b27 5use Class::MOP;
6use Module::Pluggable::Object;
421e9f8d 7
2279bc7c 8our $VERSION = '0.0008';
421e9f8d 9
10=head1 NAME
11
12 MooseX::Object::Pluggable - Make your classes pluggable
13
14=head1 SYNOPSIS
15
16 package MyApp;
17 use Moose;
9843fa64 18
421e9f8d 19 with 'MooseX::Object::Pluggable';
20
21 ...
22
23 package MyApp::Plugin::Pretty;
24 use Moose::Role;
25
26 sub pretty{ print "I am pretty" }
27
28 1;
29
30 #
31 use MyApp;
32 my $app = MyApp->new;
33 $app->load_plugin('Pretty');
34 $app->pretty;
35
36=head1 DESCRIPTION
37
38This module is meant to be loaded as a role from Moose-based classes
9bef9c02 39it will add five methods and four attributes to assist you with the loading
421e9f8d 40and handling of plugins and extensions for plugins. I understand that this may
41pollute your namespace, however I took great care in using the least ambiguous
42names possible.
43
44=head1 How plugins Work
45
46Plugins and extensions are just Roles by a fancy name. They are loaded at runtime
47on demand and are instance, not class based. This means that if you have more than
48one instance of a class they can all have different plugins loaded. This is a feature.
49
50Plugin methods are allowed to C<around>, C<before>, C<after>
51their consuming classes, so it is important to watch for load order as plugins can
52and will overload each other. You may also add attributes through has.
53
06c9b714 54Please note that when you load at runtime you lose the ability to wrap C<BUILD>
cb251ecc 55and roles using C<has> will not go through compile time checks like C<required>
9bef9c02 56and <default>.
57
9843fa64 58Even though C<override> will work , I STRONGLY discourage it's use
421e9f8d 59and a warning will be thrown if you try to use it.
cb251ecc 60This is closely linked to the way multiple roles being applied is handled and is not
421e9f8d 61likely to change. C<override> bevavior is closely linked to inheritance and thus will
62likely not work as you expect it in multiple inheritance situations. Point being,
63save yourself the headache.
64
65=head1 How plugins are loaded
66
9bef9c02 67When roles are applied at runtime an anonymous class will wrap your class and
421e9f8d 68C<$self-E<gt>blessed> and C<ref $self> will no longer return the name of your object,
9bef9c02 69they will instead return the name of the anonymous class created at runtime.
70See C<_original_class_name>.
421e9f8d 71
9bef9c02 72=head1 Usage
421e9f8d 73
9bef9c02 74For a simple example see the tests included in this distribution.
421e9f8d 75
76=head1 Attributes
77
78=head2 _plugin_ns
79
80String. The prefix to use for plugin names provided. MyApp::Plugin is sensible.
81
208f7b27 82=head2 _plugin_app_ns
421e9f8d 83
208f7b27 84ArrayRef, Accessor automatically dereferences into array on a read call.
85By default will be filled with the class name and it's prescedents, it is used
86to determine which directories to look for plugins as well as which plugins
87take presedence upon namespace collitions. This allows you to subclass a pluggable
88class and still use it's plugins while using yours first if they are available.
421e9f8d 89
421e9f8d 90=cut
91
208f7b27 92=head2 _plugin_locator
93
94An automatically built instance of L<Module::Pluggable::Object> used to locate
95available plugins.
96
97=cut
421e9f8d 98
208f7b27 99#--------#---------#---------#---------#---------#---------#---------#---------#
421e9f8d 100
7579722e 101has _plugin_ns =>
102 (
103 is => 'rw',
104 required => 1,
105 isa => 'Str',
106 default => sub{ 'Plugin' },
107 );
108
109has _plugin_loaded =>
110 (
111 is => 'rw',
112 required => 1,
113 isa => 'HashRef',
114 default => sub{ {} }
115 );
116
117has _plugin_app_ns =>
118 (
119 is => 'rw',
120 required => 1,
121 isa => 'ArrayRef',
122 lazy => 1,
123 auto_deref => 1,
124 builder => '_build_plugin_app_ns',
125 trigger => sub{ $_[0]->_clear_plugin_locator if $_[0]->_has_plugin_locator; },
126 );
127
128has _plugin_locator =>
129 (
130 is => 'rw',
131 required => 1,
132 lazy => 1,
133 isa => 'Module::Pluggable::Object',
134 clearer => '_clear_plugin_locator',
135 predicate => '_has_plugin_locator',
136 builder => '_build_plugin_locator'
137 );
421e9f8d 138
421e9f8d 139#--------#---------#---------#---------#---------#---------#---------#---------#
140
141=head1 Public Methods
142
485dad3c 143=head2 load_plugins @plugins
144
421e9f8d 145=head2 load_plugin $plugin
146
7579722e 147Load the apropriate role for C<$plugin>.
421e9f8d 148
149=cut
150
485dad3c 151sub load_plugins {
152 my ($self, @plugins) = @_;
153 die("You must provide a plugin name") unless @plugins;
421e9f8d 154
155 my $loaded = $self->_plugin_loaded;
485dad3c 156 my @load = grep { not exists $loaded->{$_} } @plugins;
485dad3c 157 my @roles = map { $self->_role_from_plugin($_) } @load;
421e9f8d 158
cf35ae5d 159 return if @roles == 0;
160
485dad3c 161 if ( $self->_load_and_apply_role(@roles) ) {
162 @{ $loaded }{@load} = @roles;
485dad3c 163 return 1;
164 } else {
165 return;
166 }
167}
9843fa64 168
169
485dad3c 170sub load_plugin {
9843fa64 171 my $self = shift;
485dad3c 172 $self->load_plugins(@_);
9843fa64 173}
174
9bef9c02 175=head2 _original_class_name
421e9f8d 176
9bef9c02 177Because of the way roles apply C<$self-E<gt>blessed> and C<ref $self> will
178no longer return what you expect. Instead use this class to get your original
179class name.
421e9f8d 180
181=cut
182
9bef9c02 183sub _original_class_name{
421e9f8d 184 my $self = shift;
9bef9c02 185 return (grep {$_ !~ /^Moose::/} $self->meta->class_precedence_list)[0];
421e9f8d 186}
187
9bef9c02 188
189=head1 Private Methods
190
9843fa64 191There's nothing stopping you from using these, but if you are using them
192for anything thats not really complicated you are probably doing
7579722e 193something wrong.
9bef9c02 194
421e9f8d 195=head2 _role_from_plugin $plugin
196
9843fa64 197Creates a role name from a plugin name. If the plugin name is prepended
421e9f8d 198with a C<+> it will be treated as a full name returned as is. Otherwise
9843fa64 199a string consisting of C<$plugin> prepended with the C<_plugin_ns>
208f7b27 200and the first valid value from C<_plugin_app_ns> will be returned. Example
9843fa64 201
202 #assuming appname MyApp and C<_plugin_ns> 'Plugin'
421e9f8d 203 $self->_role_from_plugin("MyPlugin"); # MyApp::Plugin::MyPlugin
204
205=cut
206
207sub _role_from_plugin{
208 my ($self, $plugin) = @_;
209
208f7b27 210 return $1 if( $plugin =~ /^\+(.*)/ );
211
212 my $o = join '::', $self->_plugin_ns, $plugin;
9843fa64 213 #Father, please forgive me for I have sinned.
208f7b27 214 my @roles = grep{ /${o}$/ } $self->_plugin_locator->plugins;
9843fa64 215
e091956e 216 croak("Unable to locate plugin '$plugin'") unless @roles;
208f7b27 217 return $roles[0] if @roles == 1;
9843fa64 218
208f7b27 219 my $i = 0;
0a369903 220 my %presedence_list = map{ $i++; "${_}::${o}", $i } $self->_plugin_app_ns;
9843fa64 221
208f7b27 222 @roles = sort{ $presedence_list{$a} <=> $presedence_list{$b}} @roles;
421e9f8d 223
9843fa64 224 return shift @roles;
421e9f8d 225}
226
485dad3c 227=head2 _load_and_apply_role @roles
421e9f8d 228
9bef9c02 229Require C<$role> if it is not already loaded and apply it. This is
230the meat of this module.
421e9f8d 231
232=cut
233
234sub _load_and_apply_role{
485dad3c 235 my ($self, @roles) = @_;
236 die("You must provide a role name") unless @roles;
421e9f8d 237
485dad3c 238 foreach my $role ( @roles ) {
239 eval { Class::MOP::load_class($role) };
240 confess("Failed to load role: ${role} $@") if $@;
421e9f8d 241
485dad3c 242 carp("Using 'override' is strongly discouraged and may not behave ".
243 "as you expect it to. Please use 'around'")
9843fa64 244 if scalar keys %{ $role->meta->get_override_method_modifiers_map };
485dad3c 245 }
246
247 Moose::Util::apply_all_roles( $self, @roles );
421e9f8d 248
249 return 1;
250}
251
208f7b27 252=head2 _build_plugin_app_ns
253
254Automatically builds the _plugin_app_ns attribute with the classes in the
255class presedence list that are not part of Moose.
256
257=cut
258
259sub _build_plugin_app_ns{
260 my $self = shift;
261 my @names = (grep {$_ !~ /^Moose::/} $self->meta->class_precedence_list);
262 return \@names;
263}
264
265=head2 _build_plugin_locator
266
267Automatically creates a L<Module::Pluggable::Object> instance with the correct
268search_path.
269
270=cut
271
272sub _build_plugin_locator{
273 my $self = shift;
274
275 my $locator = Module::Pluggable::Object->new
9843fa64 276 ( search_path =>
277 [ map { join '::', ($_, $self->_plugin_ns) } $self->_plugin_app_ns ]
278 );
279 return $locator;
208f7b27 280}
281
12d4facb 282=head2 meta
283
284Keep tests happy. See L<Moose>
285
286=cut
287
421e9f8d 2881;
289
290__END__;
291
292=head1 SEE ALSO
293
9bef9c02 294L<Moose>, L<Moose::Role>, L<Class::Inspector>
421e9f8d 295
296=head1 AUTHOR
297
298Guillermo Roditi, <groditi@cpan.org>
299
300=head1 BUGS
301
302Holler?
303
304Please report any bugs or feature requests to
305C<bug-moosex-object-pluggable at rt.cpan.org>, or through the web interface at
306L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooseX-Object-Pluggable>.
307I will be notified, and then you'll automatically be notified of progress on
308your bug as I make changes.
309
310=head1 SUPPORT
311
312You can find documentation for this module with the perldoc command.
313
314 perldoc MooseX-Object-Pluggable
315
316You can also look for information at:
317
318=over 4
319
320=item * AnnoCPAN: Annotated CPAN documentation
321
322L<http://annocpan.org/dist/MooseX-Object-Pluggable>
323
324=item * CPAN Ratings
325
326L<http://cpanratings.perl.org/d/MooseX-Object-Pluggable>
327
328=item * RT: CPAN's request tracker
329
330L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooseX-Object-Pluggable>
331
332=item * Search CPAN
333
334L<http://search.cpan.org/dist/MooseX-Object-Pluggable>
335
336=back
337
338=head1 ACKNOWLEDGEMENTS
339
340=over 4
341
342=item #Moose - Huge number of questions
343
344=item Matt S Trout <mst@shadowcatsystems.co.uk> - ideas / planning.
345
346=item Stevan Little - EVERYTHING. Without him this would have never happened.
347
c25d8f1c 348=item Shawn M Moore - bugfixes
349
421e9f8d 350=back
351
352=head1 COPYRIGHT
353
354Copyright 2007 Guillermo Roditi. All Rights Reserved. This is
355free software; you may redistribute it and/or modify it under the same
356terms as Perl itself.
357
358=cut