Preserve $_ (RT#66661)
[p5sagit/Class-C3-Componentised.git] / lib / Class / C3 / Componentised.pm
CommitLineData
d288ce53 1package Class::C3::Componentised;
2
20169807 3=head1 NAME
4
5Class::C3::Componentised
6
7=head1 DESCRIPTION
8
9Load mix-ins or components to your C3-based class.
10
11=head1 SYNOPSIS
12
13 package MyModule;
14
15 use strict;
16 use warnings;
17
18 use base 'Class::C3::Componentised';
19
0c205e9c 20 sub component_base_class { "MyModule::Component" }
20169807 21
22 package main;
23
0c205e9c 24 MyModule->load_components( qw/Foo Bar/ );
b34a4025 25 # Will load MyModule::Component::Foo and MyModule::Component::Bar
0c205e9c 26
27=head1 DESCRIPTION
28
29This will inject base classes to your module using the L<Class::C3> method
30resolution order.
31
32Please note: these are not plugins that can take precedence over methods
33declared in MyModule. If you want something like that, consider
34L<MooseX::Object::Pluggable>.
20169807 35
36=head1 METHODS
37
38=cut
39
d288ce53 40use strict;
41use warnings;
42
3a4635fb 43# This will prime the Class::C3 namespace (either by loading it proper on 5.8
44# or by installing compat shims on 5.10+). A user might have a reasonable
45# expectation that using Class::C3::<something> will give him access to
46# Class::C3 itself, and this module has been providing this historically.
47# Therefore leaving it in indefinitely.
0b8e135a 48use MRO::Compat;
3a4635fb 49
f8b4872f 50use Carp ();
d288ce53 51
011bd3f1 52our $VERSION = 1.0008;
d288ce53 53
eac9b176 54my $invalid_class = qr/(?: \b:\b | \:{3,} | \:\:$ )/x;
55
20169807 56=head2 load_components( @comps )
d288ce53 57
20169807 58Loads the given components into the current module. If a module begins with a
59C<+> character, it is taken to be a fully qualified class name, otherwise
60C<< $class->component_base_class >> is prepended to it.
d288ce53 61
20169807 62Calling this will call C<Class::C3::reinitialize>.
63
64=cut
d288ce53 65
66sub load_components {
67 my $class = shift;
3a4635fb 68 $class->_load_components( map {
69 /^\+(.*)$/
70 ? $1
71 : join ('::', $class->component_base_class, $_)
72 } grep { $_ !~ /^#/ } @_
73 );
d288ce53 74}
75
20169807 76=head2 load_own_components( @comps )
77
5e54b45d 78Similar to L<load_components>, but assumes every class is C<"$class::$comp">.
20169807 79
80=cut
81
d288ce53 82sub load_own_components {
83 my $class = shift;
3a4635fb 84 $class->_load_components( map { "${class}::$_" } grep { $_ !~ /^#/ } @_ );
d288ce53 85}
86
87sub _load_components {
3a4635fb 88 my $class = shift;
89 return unless @_;
90
91 $class->ensure_class_loaded($_) for @_;
92 $class->inject_base($class => @_);
20169807 93 Class::C3::reinitialize();
d288ce53 94}
95
20169807 96=head2 load_optional_components
d288ce53 97
20169807 98As L<load_components>, but will silently ignore any components that cannot be
99found.
d288ce53 100
20169807 101=cut
d288ce53 102
20169807 103sub load_optional_components {
104 my $class = shift;
3a4635fb 105 $class->_load_components( grep
106 { $class->load_optional_class( $_ ) }
107 ( map
108 { /^\+(.*)$/
109 ? $1
110 : join ('::', $class->component_base_class, $_)
111 }
112 grep { $_ !~ /^#/ } @_
113 )
114 );
20169807 115}
d288ce53 116
20169807 117=head2 ensure_class_loaded
118
119Given a class name, tests to see if it is already loaded or otherwise
120defined. If it is not yet loaded, the package is require'd, and an exception
121is thrown if the class is still not loaded.
122
123 BUG: For some reason, packages with syntax errors are added to %INC on
124 require
125=cut
126
20169807 127sub ensure_class_loaded {
128 my ($class, $f_class) = @_;
129
3a4635fb 130 no strict 'refs';
131
132 # ripped from Class::Inspector for speed
133 # note that the order is important (faster items are first)
134 return if ${"${f_class}::VERSION"};
135
136 return if @{"${f_class}::ISA"};
137
138 my $file = (join ('/', split ('::', $f_class) ) ) . '.pm';
139 return if $INC{$file};
140
141 for ( keys %{"${f_class}::"} ) {
142 return if ( *{"${f_class}::$_"}{CODE} );
143 }
144
3a4635fb 145 # require always returns true on success
15b7d164 146 # ill-behaved modules might very well obliterate $_
147 eval { local $_; require($file) } or do {
3a4635fb 148
eac9b176 149 $@ = "Invalid class name '$f_class'" if $f_class =~ $invalid_class;
3a4635fb 150
20169807 151 if ($class->can('throw_exception')) {
152 $class->throw_exception($@);
153 } else {
f8b4872f 154 Carp::croak $@;
20169807 155 }
3a4635fb 156 };
157
158 return;
20169807 159}
d288ce53 160
20169807 161=head2 ensure_class_found
d288ce53 162
20169807 163Returns true if the specified class is installed or already loaded, false
b34a4025 164otherwise.
165
166Note that the underlying mechanism (Class::Inspector->installed()) used by this
167sub will not, at the time of writing, correctly function when @INC includes
168coderefs. Since PAR relies upon coderefs in @INC, this function should be
169avoided in modules that are likely to be included within a PAR.
d288ce53 170
20169807 171=cut
d288ce53 172
20169807 173sub ensure_class_found {
3a4635fb 174 #my ($class, $f_class) = @_;
175 require Class::Inspector;
176 return Class::Inspector->loaded($_[1]) ||
177 Class::Inspector->installed($_[1]);
20169807 178}
d288ce53 179
d288ce53 180
181=head2 inject_base
182
20169807 183Does the actual magic of adjusting @ISA on the target module.
184
185=cut
d288ce53 186
20169807 187sub inject_base {
3a4635fb 188 my $class = shift;
189 my $target = shift;
190
3a4635fb 191 for (reverse @_) {
20169807 192 no strict 'refs';
e1950a66 193 unshift ( @{"${target}::ISA"}, $_ )
194 unless ($target eq $_ || $target->isa($_));
20169807 195 }
196
d91a39a9 197 mro::set_mro($target, 'c3');
20169807 198}
d288ce53 199
078742b1 200=head2 load_optional_class
201
202Returns a true value if the specified class is installed and loaded
203successfully, throws an exception if the class is found but not loaded
204successfully, and false if the class is not installed
205
206=cut
207
208sub load_optional_class {
209 my ($class, $f_class) = @_;
3a4635fb 210
211 # ensure_class_loaded either returns a () (*not* true) or throws
212 eval {
213 $class->ensure_class_loaded($f_class);
214 1;
215 } && return 1;
216
dfb3a821 217 my $err = $@; # so we don't lose it
3a4635fb 218
eac9b176 219 if ($f_class =~ $invalid_class) {
220 $err = "Invalid class name '$f_class'";
221 }
222 else {
223 my $fn = quotemeta( (join ('/', split ('::', $f_class) ) ) . '.pm' );
224 return 0 if ($err =~ /Can't locate ${fn} in \@INC/ );
3a4635fb 225 }
eac9b176 226
227 if ($class->can('throw_exception')) {
3a4635fb 228 $class->throw_exception($err);
078742b1 229 }
dfb3a821 230 else {
3a4635fb 231 die $err;
dfb3a821 232 }
078742b1 233}
234
d288ce53 235=head1 AUTHOR
236
20169807 237Matt S. Trout and the DBIx::Class team
238
239Pulled out into seperate module by Ash Berlin C<< <ash@cpan.org> >>
d288ce53 240
241=head1 LICENSE
242
243You may distribute this code under the same terms as Perl itself.
20169807 244
245=cut
246
2471;