Specify minimal perl version
[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
20169807 50use Carp;
d288ce53 51
ade50a21 52our $VERSION = 1.0006;
d288ce53 53
20169807 54=head2 load_components( @comps )
d288ce53 55
20169807 56Loads the given components into the current module. If a module begins with a
57C<+> character, it is taken to be a fully qualified class name, otherwise
58C<< $class->component_base_class >> is prepended to it.
d288ce53 59
20169807 60Calling this will call C<Class::C3::reinitialize>.
61
62=cut
d288ce53 63
64sub load_components {
65 my $class = shift;
3a4635fb 66 $class->_load_components( map {
67 /^\+(.*)$/
68 ? $1
69 : join ('::', $class->component_base_class, $_)
70 } grep { $_ !~ /^#/ } @_
71 );
d288ce53 72}
73
20169807 74=head2 load_own_components( @comps )
75
5e54b45d 76Similar to L<load_components>, but assumes every class is C<"$class::$comp">.
20169807 77
78=cut
79
d288ce53 80sub load_own_components {
81 my $class = shift;
3a4635fb 82 $class->_load_components( map { "${class}::$_" } grep { $_ !~ /^#/ } @_ );
d288ce53 83}
84
85sub _load_components {
3a4635fb 86 my $class = shift;
87 return unless @_;
88
89 $class->ensure_class_loaded($_) for @_;
90 $class->inject_base($class => @_);
20169807 91 Class::C3::reinitialize();
d288ce53 92}
93
20169807 94=head2 load_optional_components
d288ce53 95
20169807 96As L<load_components>, but will silently ignore any components that cannot be
97found.
d288ce53 98
20169807 99=cut
d288ce53 100
20169807 101sub load_optional_components {
102 my $class = shift;
3a4635fb 103 $class->_load_components( grep
104 { $class->load_optional_class( $_ ) }
105 ( map
106 { /^\+(.*)$/
107 ? $1
108 : join ('::', $class->component_base_class, $_)
109 }
110 grep { $_ !~ /^#/ } @_
111 )
112 );
20169807 113}
d288ce53 114
20169807 115=head2 ensure_class_loaded
116
117Given a class name, tests to see if it is already loaded or otherwise
118defined. If it is not yet loaded, the package is require'd, and an exception
119is thrown if the class is still not loaded.
120
121 BUG: For some reason, packages with syntax errors are added to %INC on
122 require
123=cut
124
20169807 125sub ensure_class_loaded {
126 my ($class, $f_class) = @_;
127
3a4635fb 128 no strict 'refs';
129
130 # ripped from Class::Inspector for speed
131 # note that the order is important (faster items are first)
132 return if ${"${f_class}::VERSION"};
133
134 return if @{"${f_class}::ISA"};
135
136 my $file = (join ('/', split ('::', $f_class) ) ) . '.pm';
137 return if $INC{$file};
138
139 for ( keys %{"${f_class}::"} ) {
140 return if ( *{"${f_class}::$_"}{CODE} );
141 }
142
143
144 # require always returns true on success
145 eval { require($file) } or do {
146
147 $@ = "Invalid class name $f_class"
20169807 148 if ($f_class=~m/(?:\b:\b|\:{3,})/);
3a4635fb 149
20169807 150 if ($class->can('throw_exception')) {
151 $class->throw_exception($@);
152 } else {
153 croak $@;
154 }
3a4635fb 155 };
156
157 return;
20169807 158}
d288ce53 159
20169807 160=head2 ensure_class_found
d288ce53 161
20169807 162Returns true if the specified class is installed or already loaded, false
b34a4025 163otherwise.
164
165Note that the underlying mechanism (Class::Inspector->installed()) used by this
166sub will not, at the time of writing, correctly function when @INC includes
167coderefs. Since PAR relies upon coderefs in @INC, this function should be
168avoided in modules that are likely to be included within a PAR.
d288ce53 169
20169807 170=cut
d288ce53 171
20169807 172sub ensure_class_found {
3a4635fb 173 #my ($class, $f_class) = @_;
174 require Class::Inspector;
175 return Class::Inspector->loaded($_[1]) ||
176 Class::Inspector->installed($_[1]);
20169807 177}
d288ce53 178
d288ce53 179
180=head2 inject_base
181
20169807 182Does the actual magic of adjusting @ISA on the target module.
183
184=cut
d288ce53 185
20169807 186sub inject_base {
3a4635fb 187 my $class = shift;
188 my $target = shift;
189
190 my %isa = map { $_ => 1 } ($target, @{mro::get_linear_isa($target)} );
191
192 for (reverse @_) {
20169807 193 no strict 'refs';
3a4635fb 194 unless ($isa{$_}++) {
195 unshift ( @{"${target}::ISA"}, $_ );
20169807 196 }
197 }
198
d91a39a9 199 mro::set_mro($target, 'c3');
20169807 200}
d288ce53 201
078742b1 202=head2 load_optional_class
203
204Returns a true value if the specified class is installed and loaded
205successfully, throws an exception if the class is found but not loaded
206successfully, and false if the class is not installed
207
208=cut
209
210sub load_optional_class {
211 my ($class, $f_class) = @_;
3a4635fb 212
213 # ensure_class_loaded either returns a () (*not* true) or throws
214 eval {
215 $class->ensure_class_loaded($f_class);
216 1;
217 } && return 1;
218
dfb3a821 219 my $err = $@; # so we don't lose it
3a4635fb 220
221 my $fn = quotemeta( (join ('/', split ('::', $f_class) ) ) . '.pm' );
222 if ($err =~ /Can't locate ${fn} in \@INC/ ) {
223 return 0;
224 }
225 elsif ($class->can('throw_exception')) {
226 $class->throw_exception($err);
078742b1 227 }
dfb3a821 228 else {
3a4635fb 229 die $err;
dfb3a821 230 }
078742b1 231}
232
d288ce53 233=head1 AUTHOR
234
20169807 235Matt S. Trout and the DBIx::Class team
236
237Pulled out into seperate module by Ash Berlin C<< <ash@cpan.org> >>
d288ce53 238
239=head1 LICENSE
240
241You may distribute this code under the same terms as Perl itself.
20169807 242
243=cut
244
2451;