Make tests more resilient
[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/ );
25 # Will load MyModule::Component::Foo an MyModule::Component::Bar
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
d288ce53 43use Class::C3;
20169807 44use Class::Inspector;
45use Carp;
d288ce53 46
bdce0c01 47our $VERSION = 1.0001;
d288ce53 48
20169807 49=head2 load_components( @comps )
d288ce53 50
20169807 51Loads the given components into the current module. If a module begins with a
52C<+> character, it is taken to be a fully qualified class name, otherwise
53C<< $class->component_base_class >> is prepended to it.
d288ce53 54
20169807 55Calling this will call C<Class::C3::reinitialize>.
56
57=cut
d288ce53 58
59sub load_components {
60 my $class = shift;
61 my $base = $class->component_base_class;
62 my @comp = map { /^\+(.*)$/ ? $1 : "${base}::$_" } grep { $_ !~ /^#/ } @_;
63 $class->_load_components(@comp);
d288ce53 64}
65
20169807 66=head2 load_own_components( @comps )
67
68Simialr to L<load_components>, but assumes every class is C<"$class::$comp">.
69
70=cut
71
d288ce53 72sub load_own_components {
73 my $class = shift;
74 my @comp = map { "${class}::$_" } grep { $_ !~ /^#/ } @_;
75 $class->_load_components(@comp);
76}
77
78sub _load_components {
79 my ($class, @comp) = @_;
80 foreach my $comp (@comp) {
20169807 81 $class->ensure_class_loaded($comp);
d288ce53 82 }
83 $class->inject_base($class => @comp);
20169807 84 Class::C3::reinitialize();
d288ce53 85}
86
20169807 87=head2 load_optional_components
d288ce53 88
20169807 89As L<load_components>, but will silently ignore any components that cannot be
90found.
d288ce53 91
20169807 92=cut
d288ce53 93
20169807 94sub load_optional_components {
95 my $class = shift;
96 my $base = $class->component_base_class;
97 my @comp = grep { $class->load_optional_class( $_ ) }
98 map { /^\+(.*)$/ ? $1 : "${base}::$_" }
99 grep { $_ !~ /^#/ } @_;
d288ce53 100
20169807 101 $class->_load_components( @comp ) if scalar @comp;
102}
d288ce53 103
20169807 104=head2 ensure_class_loaded
105
106Given a class name, tests to see if it is already loaded or otherwise
107defined. If it is not yet loaded, the package is require'd, and an exception
108is thrown if the class is still not loaded.
109
110 BUG: For some reason, packages with syntax errors are added to %INC on
111 require
112=cut
113
114#
115# TODO: handle ->has_many('rel', 'Class'...) instead of
116# ->has_many('rel', 'Some::Schema::Class'...)
117#
118sub ensure_class_loaded {
119 my ($class, $f_class) = @_;
120
121 croak "Invalid class name $f_class"
122 if ($f_class=~m/(?:\b:\b|\:{3,})/);
123 return if Class::Inspector->loaded($f_class);
c2bfa58a 124 my $file = $f_class . '.pm';
125 $file =~ s{::}{/}g;
126 eval { CORE::require($file) }; # require needs a bareword or filename
20169807 127 if ($@) {
128 if ($class->can('throw_exception')) {
129 $class->throw_exception($@);
130 } else {
131 croak $@;
132 }
133 }
134}
d288ce53 135
20169807 136=head2 ensure_class_found
d288ce53 137
20169807 138Returns true if the specified class is installed or already loaded, false
139otherwise
d288ce53 140
20169807 141=cut
d288ce53 142
20169807 143sub ensure_class_found {
144 my ($class, $f_class) = @_;
145 return Class::Inspector->loaded($f_class) ||
146 Class::Inspector->installed($f_class);
147}
d288ce53 148
d288ce53 149
150=head2 inject_base
151
20169807 152Does the actual magic of adjusting @ISA on the target module.
153
154=cut
d288ce53 155
20169807 156sub inject_base {
157 my ($class, $target, @to_inject) = @_;
158 {
159 no strict 'refs';
160 foreach my $to (reverse @to_inject) {
0c205e9c 161 unshift ( @{"${target}::ISA"}, $to )
20169807 162 unless ($target eq $to || $target->isa($to));
163 }
164 }
165
166 # Yes, this is hack. But it *does* work. Please don't submit tickets about
167 # it on the basis of the comments in Class::C3, the author was on #dbix-class
168 # while I was implementing this.
169
170 eval "package $target; import Class::C3;" unless exists $Class::C3::MRO{$target};
171}
d288ce53 172
173=head1 AUTHOR
174
20169807 175Matt S. Trout and the DBIx::Class team
176
177Pulled out into seperate module by Ash Berlin C<< <ash@cpan.org> >>
d288ce53 178
179=head1 LICENSE
180
181You may distribute this code under the same terms as Perl itself.
20169807 182
183=cut
184
1851;