changes
[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
472334a0 8our $VERSION = '0.0009';
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
485dad3c 159 if ( $self->_load_and_apply_role(@roles) ) {
160 @{ $loaded }{@load} = @roles;
485dad3c 161 return 1;
162 } else {
163 return;
164 }
165}
9843fa64 166
167
485dad3c 168sub load_plugin {
9843fa64 169 my $self = shift;
485dad3c 170 $self->load_plugins(@_);
9843fa64 171}
172
9bef9c02 173=head2 _original_class_name
421e9f8d 174
9bef9c02 175Because of the way roles apply C<$self-E<gt>blessed> and C<ref $self> will
176no longer return what you expect. Instead use this class to get your original
177class name.
421e9f8d 178
179=cut
180
9bef9c02 181sub _original_class_name{
421e9f8d 182 my $self = shift;
9bef9c02 183 return (grep {$_ !~ /^Moose::/} $self->meta->class_precedence_list)[0];
421e9f8d 184}
185
9bef9c02 186
187=head1 Private Methods
188
9843fa64 189There's nothing stopping you from using these, but if you are using them
190for anything thats not really complicated you are probably doing
7579722e 191something wrong.
9bef9c02 192
421e9f8d 193=head2 _role_from_plugin $plugin
194
9843fa64 195Creates a role name from a plugin name. If the plugin name is prepended
421e9f8d 196with a C<+> it will be treated as a full name returned as is. Otherwise
9843fa64 197a string consisting of C<$plugin> prepended with the C<_plugin_ns>
208f7b27 198and the first valid value from C<_plugin_app_ns> will be returned. Example
9843fa64 199
200 #assuming appname MyApp and C<_plugin_ns> 'Plugin'
421e9f8d 201 $self->_role_from_plugin("MyPlugin"); # MyApp::Plugin::MyPlugin
202
203=cut
204
205sub _role_from_plugin{
206 my ($self, $plugin) = @_;
207
208f7b27 208 return $1 if( $plugin =~ /^\+(.*)/ );
209
210 my $o = join '::', $self->_plugin_ns, $plugin;
9843fa64 211 #Father, please forgive me for I have sinned.
208f7b27 212 my @roles = grep{ /${o}$/ } $self->_plugin_locator->plugins;
9843fa64 213
e091956e 214 croak("Unable to locate plugin '$plugin'") unless @roles;
208f7b27 215 return $roles[0] if @roles == 1;
9843fa64 216
208f7b27 217 my $i = 0;
0a369903 218 my %presedence_list = map{ $i++; "${_}::${o}", $i } $self->_plugin_app_ns;
9843fa64 219
208f7b27 220 @roles = sort{ $presedence_list{$a} <=> $presedence_list{$b}} @roles;
421e9f8d 221
9843fa64 222 return shift @roles;
421e9f8d 223}
224
485dad3c 225=head2 _load_and_apply_role @roles
421e9f8d 226
9bef9c02 227Require C<$role> if it is not already loaded and apply it. This is
228the meat of this module.
421e9f8d 229
230=cut
231
232sub _load_and_apply_role{
485dad3c 233 my ($self, @roles) = @_;
234 die("You must provide a role name") unless @roles;
421e9f8d 235
485dad3c 236 foreach my $role ( @roles ) {
237 eval { Class::MOP::load_class($role) };
238 confess("Failed to load role: ${role} $@") if $@;
421e9f8d 239
485dad3c 240 carp("Using 'override' is strongly discouraged and may not behave ".
241 "as you expect it to. Please use 'around'")
9843fa64 242 if scalar keys %{ $role->meta->get_override_method_modifiers_map };
485dad3c 243 }
244
245 Moose::Util::apply_all_roles( $self, @roles );
421e9f8d 246
247 return 1;
248}
249
208f7b27 250=head2 _build_plugin_app_ns
251
252Automatically builds the _plugin_app_ns attribute with the classes in the
253class presedence list that are not part of Moose.
254
255=cut
256
257sub _build_plugin_app_ns{
258 my $self = shift;
259 my @names = (grep {$_ !~ /^Moose::/} $self->meta->class_precedence_list);
260 return \@names;
261}
262
263=head2 _build_plugin_locator
264
265Automatically creates a L<Module::Pluggable::Object> instance with the correct
266search_path.
267
268=cut
269
270sub _build_plugin_locator{
271 my $self = shift;
272
273 my $locator = Module::Pluggable::Object->new
9843fa64 274 ( search_path =>
275 [ map { join '::', ($_, $self->_plugin_ns) } $self->_plugin_app_ns ]
276 );
277 return $locator;
208f7b27 278}
279
12d4facb 280=head2 meta
281
282Keep tests happy. See L<Moose>
283
284=cut
285
421e9f8d 2861;
287
288__END__;
289
290=head1 SEE ALSO
291
9bef9c02 292L<Moose>, L<Moose::Role>, L<Class::Inspector>
421e9f8d 293
294=head1 AUTHOR
295
296Guillermo Roditi, <groditi@cpan.org>
297
298=head1 BUGS
299
300Holler?
301
302Please report any bugs or feature requests to
303C<bug-moosex-object-pluggable at rt.cpan.org>, or through the web interface at
304L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooseX-Object-Pluggable>.
305I will be notified, and then you'll automatically be notified of progress on
306your bug as I make changes.
307
308=head1 SUPPORT
309
310You can find documentation for this module with the perldoc command.
311
312 perldoc MooseX-Object-Pluggable
313
314You can also look for information at:
315
316=over 4
317
318=item * AnnoCPAN: Annotated CPAN documentation
319
320L<http://annocpan.org/dist/MooseX-Object-Pluggable>
321
322=item * CPAN Ratings
323
324L<http://cpanratings.perl.org/d/MooseX-Object-Pluggable>
325
326=item * RT: CPAN's request tracker
327
328L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooseX-Object-Pluggable>
329
330=item * Search CPAN
331
332L<http://search.cpan.org/dist/MooseX-Object-Pluggable>
333
334=back
335
336=head1 ACKNOWLEDGEMENTS
337
338=over 4
339
340=item #Moose - Huge number of questions
341
342=item Matt S Trout <mst@shadowcatsystems.co.uk> - ideas / planning.
343
344=item Stevan Little - EVERYTHING. Without him this would have never happened.
345
346=back
347
348=head1 COPYRIGHT
349
350Copyright 2007 Guillermo Roditi. All Rights Reserved. This is
351free software; you may redistribute it and/or modify it under the same
352terms as Perl itself.
353
354=cut