1use warnings; 2use strict; 3 4package Jifty::Plugin::ClassLoader; 5 6=head1 NAME 7 8Jifty::Plugin::ClassLoader - Automatically generates application classes 9 10=head1 DESCRIPTION 11 12C<Jifty::Plugin::ClassLoader> loads additional model and action classes on 13behalf of the application out of the configured plugin classes. Unlike, 14C<Jifty::ClassLoader>, this class will only autogenerate classes if the 15plugin provides them. The plugin classes are checked before the base Jifty 16classes, so that a plugin can override the Jifty class, just as any 17existing application classes will be loaded first. 18 19=head2 new 20 21Returns a new ClassLoader object. Doing this installs a hook into 22C<@INC> that allows L<Jifty::Plugin::ClassLoader> to dynamically create 23needed classes if they do not exist already. This works because if 24use/require encounters a blessed reference in C<@INC>, it will 25invoke the INC method with the name of the module it is searching 26for on the reference. 27 28Takes two mandatory arguments, C<base>, which should be the 29application's base path; and C<plugin> which is the plugin classname. 30 31=cut 32 33sub new { 34 my $class = shift; 35 my %args = (@_); 36 my @exist = grep {ref $_ eq $class and $_->{base} eq $args{base}} @INC; 37 return $exist[0] if @exist; 38 39 40 my $self = bless {%args}, $class; 41 42 push @INC, $self; 43 return $self; 44} 45 46=head2 INC 47 48The hook that is called when a module has been C<require>'d that 49cannot be found on disk. The following stub classes are 50auto-generated: 51 52=over 53 54=item I<Application> 55 56An empty application base class is created that doesn't provide any 57methods or inherit from anything. 58 59=item I<Application>::Record 60 61An empty class that descends from L<Jifty::Record> is created. 62 63=item I<Application>::Collection 64 65An empty class that descends from L<Jifty::Collection> is created. 66 67=item I<Application>::Notification 68 69An empty class that descends from L<Jifty::Notification>. 70 71=item I<Application>::Dispatcher 72 73An empty class that descends from L<Jifty::Dispatcher>. 74 75=item I<Application>::Bootstrap 76 77An empty class that descends from L<Jifty::Bootstrap>. 78 79=item I<Application>::Upgrade 80 81An empty class that descends from L<Jifty::Upgrade>. 82 83=item I<Application>::CurrentUser 84 85An empty class that descends from L<Jifty::CurrentUser>. 86 87=item I<Application>::Model::I<Anything>Collection 88 89If C<I<Application>::Model::I<Something>> is a valid model class, then 90it creates a subclass of L<Jifty::Collection> whose C<record_class> is 91C<I<Application>::Model::I<Something>>. 92 93=item I<Application>::Action::(Create or Update or Delete)I<Anything> 94 95If C<I<Application>::Model::I<Something>> is a valid model class, then 96it creates a subclass of L<Jifty::Action::Record::Create>, 97L<Jifty::Action::Record::Update>, or L<Jifty::Action::Record::Delete> 98whose I<record_class> is C<I<Application>::Model::I<Something>>. 99 100=back 101 102=cut 103 104# This subroutine's name is fully qualified, as perl will ignore a 'sub INC' 105sub Jifty::Plugin::ClassLoader::INC { 106 my ( $self, $module ) = @_; 107 108 my $base = $self->{base}; 109 my $plugin = $self->{plugin}; 110 return undef unless ( $module and $base and $plugin); 111 112 113 114 # Canonicalize $module to :: style rather than / and .pm style; 115 $module =~ s/.pm$//; 116 $module =~ s{/}{::}g; 117 118 # The quick check 119 return undef unless $module =~ m!^$base!; 120 121 # Note that at this point, all of the plugins classes will already be 122 # loaded, so we can just check their presence when deciding whether 123 # this is a class the plugin intends to autocreate 124 if ( $module =~ m{^(?:$base)::CurrentUser$} ) { 125 my $method = "${plugin}::CurrentUser"; 126 if ( Jifty::Util->already_required($method) ) { 127 Jifty->log->debug("Implementing $module using $method"); 128 $Jifty::ClassLoader::AUTOGENERATED{$module} = 1; 129 return Jifty::ClassLoader->return_class( 130 "use warnings; use strict; package $module;\n" 131 . "use base qw/$method/;\n" 132 . "1;" ) 133 } 134 else { 135 Jifty->log->debug("Couldn't implement $module using $method"); 136 } 137 } elsif ( $module =~ m!^(?:$base)::Action::(Create|Update|Delete|Search)([^\.]+)$! ) { 138 my $model = "::Model::" . $2; 139 my $method = $plugin . "::Action::" . $1 . $2; 140 141 # Check to see if this is an action for a model that this plugin 142 # doesn't provide 143 return undef unless Jifty::Util->already_required("$plugin$model"); 144 145 if ( Jifty::Util->already_required($method) ) { 146 Jifty->log->debug("Implementing $module using $method"); 147 $Jifty::ClassLoader::AUTOGENERATED{$module} = 1; 148 return Jifty::ClassLoader->return_class( 149 "use warnings; use strict; package $module;\n" 150 . "use base qw/$method/;\n" 151 . "sub record_class { '$base$model' };\n" 152 . "1;" ) 153 } 154 else { 155 Jifty->log->debug("Couldn't implement $module using $method"); 156 } 157 } elsif ( $module =~ m{^(?:$base)::(Action|Notification)([^\.]+)$} ) { 158 my $method = $plugin . "::" . $1 . $2; 159 if ( Jifty::Util->already_required($method) ) { 160 Jifty->log->debug("Implementing $module using $method"); 161 $Jifty::ClassLoader::AUTOGENERATED{$module} = 1; 162 return Jifty::ClassLoader->return_class( 163 "use warnings; use strict; package $module;\n" 164 . "use base qw/$method/;\n" 165 . "1;" ) 166 } 167 else { 168 Jifty->log->debug("Couldn't implement $module using $method"); 169 } 170 171 } 172 173 return undef; 174} 175 176=head2 require 177 178Loads all of the application's Actions and Models. It additionally 179C<require>'s all Collections and Create/Update actions for each Model 180base class -- which will auto-create them using the above code if they 181do not exist on disk. 182 183=cut 184 185sub require { 186 my $self = shift; 187 188 my $base = $self->{plugin}; 189 190 191 # if we don't even have an application class, this trick will not work 192 return unless ($base); 193 Jifty::Util->require($base); 194 Jifty::Util->require($base."::CurrentUser"); 195 196 Jifty::Module::Pluggable->import( 197 search_path => 198 [ map { $base . "::" . $_ } 'Model', 'Action', 'Notification' ], 199 require => 1, 200 except => qr/\.#/, 201 inner => 0 202 ); 203 $self->{models}{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection$/} $self->plugins; 204 for my $full (keys %{$self->{models}}) { 205 my($short) = $full =~ /::Model::(.*)/; 206 Jifty::Util->require($full . "Collection"); 207 Jifty::Util->require($base . "::Action::" . $_ . $short) 208 for qw/Create Update Delete/; 209 } 210} 211 212=head2 DESTROY 213 214When the ClassLoader gets garbage-collected, its entry in @INC needs 215to be removed. 216 217=cut 218 219# The entries in @INC end up having SvTYPE == SVt_RV, but SvRV(sv) == 220# 0x0 and !SvROK(sv) (!?) This may be something that perl should cope 221# with more cleanly. 222# 223# We call this explictly in an END block in Jifty.pm, because 224# otherwise the DESTROY block gets called *after* there's already a 225# bogus entry in @INC 226 227# This bug manifests itself as warnings that look like this: 228 229# Use of uninitialized value in require at /tmp/7730 line 9 during global destruction. 230 231 232sub DESTROY { 233 my $self = shift; 234 @INC = grep {!$self} @INC; 235} 236 2371; 238