restore ensure_class_loaded since Class::MOP::load_class doesn't have a force load...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Controller.pm
CommitLineData
5ee249f2 1package Catalyst::Controller;
2
a7caa492 3use base qw/Catalyst::Component Catalyst::AttrContainer/;
6e58f383 4use Moose;
e8b9f2a9 5
5fb67d52 6#Why does the following blow up?
7#extends qw/Catalyst::Component Catalyst::AttrContainer/;
8
9has path => (
10 is => 'ro',
11 isa => 'Str',
12 predicate => 'has_path',
13 );
14
15#this are frefixed like this because _namespace clases with something in
16#Catalyst.pm . to be fixed later.
17has _namespace => (
18 is => 'ro',
19 isa => 'Str',
20 init_arg => 'namespace',
21 predicate => '_has_namespace',
22 );
23
2361ed67 24__PACKAGE__->mk_accessors( qw/_application/ );
25
26has _application => (is => 'rw');
27sub _app{ shift->_application(@_) } # eww
28
5fb67d52 29use Scalar::Util qw/blessed/;
234763d4 30use Catalyst::Exception;
31use Catalyst::Utils;
32use Class::Inspector;
5ee249f2 33
34=head1 NAME
35
36Catalyst::Controller - Catalyst Controller base class
37
38=head1 SYNOPSIS
39
234763d4 40 package MyApp::Controller::Search
a269e0c2 41 use base qw/Catalyst::Controller/;
234763d4 42
a7caa492 43 sub foo : Local {
85d9fce6 44 my ($self,$c,@args) = @_;
a7caa492 45 ...
234763d4 46 } # Dispatches to /search/foo
5ee249f2 47
48=head1 DESCRIPTION
49
a269e0c2 50Controllers are where the actions in the Catalyst framework
51reside. Each action is represented by a function with an attribute to
52identify what kind of action it is. See the L<Catalyst::Dispatcher>
53for more info about how Catalyst dispatches to actions.
234763d4 54
55=cut
56
2361ed67 57#I think both of these could be attributes. doesn't really seem like they need
58#to ble class data. i think that attributes +default would work just fine
e8b9f2a9 59__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/;
234763d4 60
e8b9f2a9 61__PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] );
62__PACKAGE__->_action_class('Catalyst::Action');
63
234763d4 64
65sub _DISPATCH : Private {
66 my ( $self, $c ) = @_;
67
68 foreach my $disp ( @{ $self->_dispatch_steps } ) {
69 last unless $c->forward($disp);
70 }
71
72 $c->forward('_END');
73}
74
75sub _BEGIN : Private {
76 my ( $self, $c ) = @_;
77 my $begin = ( $c->get_actions( 'begin', $c->namespace ) )[-1];
78 return 1 unless $begin;
79 $begin->dispatch( $c );
80 return !@{ $c->error };
81}
82
83sub _AUTO : Private {
84 my ( $self, $c ) = @_;
85 my @auto = $c->get_actions( 'auto', $c->namespace );
86 foreach my $auto (@auto) {
87 $auto->dispatch( $c );
88 return 0 unless $c->state;
89 }
90 return 1;
91}
92
93sub _ACTION : Private {
94 my ( $self, $c ) = @_;
95 if ( ref $c->action
96 && $c->action->can('execute')
97 && $c->req->action )
98 {
99 $c->action->dispatch( $c );
100 }
101 return !@{ $c->error };
102}
103
104sub _END : Private {
105 my ( $self, $c ) = @_;
106 my $end = ( $c->get_actions( 'end', $c->namespace ) )[-1];
107 return 1 unless $end;
108 $end->dispatch( $c );
109 return !@{ $c->error };
110}
111
5fb67d52 112around new => sub {
113 my $orig = shift;
e8b9f2a9 114 my $self = shift;
115 my $app = $_[0];
5fb67d52 116 my $new = $self->$orig(@_);
e8b9f2a9 117 $new->_application( $app );
118 return $new;
5fb67d52 119};
e8b9f2a9 120
121
234763d4 122sub action_for {
123 my ( $self, $name ) = @_;
124 my $app = ($self->isa('Catalyst') ? $self : $self->_application);
125 return $app->dispatcher->get_action($name, $self->action_namespace);
126}
127
128sub action_namespace {
129 my ( $self, $c ) = @_;
130 unless ( $c ) {
131 $c = ($self->isa('Catalyst') ? $self : $self->_application);
132 }
5fb67d52 133 if( ref($self) ){
134 return $self->_namespace if $self->_has_namespace;
2361ed67 135 } # else { #i think this is hacky and it should work with the else enabled
5fb67d52 136 return $self->config->{namespace} if exists $self->config->{namespace};
137 #}
234763d4 138 return Catalyst::Utils::class2prefix( ref($self) || $self,
139 $c->config->{case_sensitive} )
140 || '';
141}
142
143sub path_prefix {
144 my ( $self, $c ) = @_;
145 unless ( $c ) {
146 $c = ($self->isa('Catalyst') ? $self : $self->_application);
147 }
5fb67d52 148 if( ref($self) ){
149 return $self->path if $self->has_path;
150 } # else {
151 return $self->config->{path} if exists $self->config->{path};
152 #}
234763d4 153 return shift->action_namespace(@_);
154}
155
156
157sub register_actions {
158 my ( $self, $c ) = @_;
159 my $class = ref $self || $self;
5fb67d52 160 #this is still not correct for some reason.
234763d4 161 my $namespace = $self->action_namespace($c);
162 my %methods;
5fb67d52 163 if( $self->can('meta') ){
164 my $meta = $self->meta;
165 %methods = map{ $_->{code}->body => $_->{name} }
2361ed67 166 grep {$_->{class} ne 'Moose::Object'} #ignore Moose::Object methods
5fb67d52 167 $meta->compute_all_applicable_methods;
168 } else { #until we are sure there's no moose stuff left...
169 $methods{ $self->can($_) } = $_
170 for @{ Class::Inspector->methods($class) || [] };
171 }
234763d4 172
173 # Advanced inheritance support for plugins and the like
5fb67d52 174 #to be modified to use meta->superclasses
234763d4 175 my @action_cache;
176 {
177 no strict 'refs';
178 for my $isa ( @{"$class\::ISA"}, $class ) {
179 push @action_cache, @{ $isa->_action_cache }
180 if $isa->can('_action_cache');
181 }
182 }
183
184 foreach my $cache (@action_cache) {
185 my $code = $cache->[0];
186 my $method = delete $methods{$code}; # avoid dupe registers
187 next unless $method;
188 my $attrs = $self->_parse_attrs( $c, $method, @{ $cache->[1] } );
189 if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
190 $c->log->debug( 'Bad action definition "'
191 . join( ' ', @{ $cache->[1] } )
192 . qq/" for "$class->$method"/ )
193 if $c->debug;
194 next;
195 }
5fb67d52 196 my $reverse = $namespace ? "${namespace}/${method}" : $method;
234763d4 197 my $action = $self->create_action(
198 name => $method,
199 code => $code,
200 reverse => $reverse,
201 namespace => $namespace,
202 class => $class,
203 attributes => $attrs,
204 );
205
206 $c->dispatcher->register( $c, $action );
207 }
208}
209
210sub create_action {
211 my $self = shift;
212 my %args = @_;
213
214 my $class = (exists $args{attributes}{ActionClass}
215 ? $args{attributes}{ActionClass}[0]
216 : $self->_action_class);
217
2361ed67 218 Class::MOP::load_class($class);
219 #unless ( Class::Inspector->loaded($class) ) {
220 # require Class::Inspector->filename($class);
221 #}
a7caa492 222
234763d4 223 return $class->new( \%args );
224}
225
226sub _parse_attrs {
227 my ( $self, $c, $name, @attrs ) = @_;
228
229 my %raw_attributes;
230
231 foreach my $attr (@attrs) {
232
233 # Parse out :Foo(bar) into Foo => bar etc (and arrayify)
234
235 if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
236 {
237
238 if ( defined $value ) {
239 ( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
240 }
241 push( @{ $raw_attributes{$key} }, $value );
242 }
243 }
244
5fb67d52 245 #this will not work under moose
246 #my $hash = (ref $self ? $self : $self->config); # hate app-is-class
2361ed67 247 #action / actions should be an attribute of Controller
5fb67d52 248 my $hash = $self->config;
234763d4 249
250 if (exists $hash->{actions} || exists $hash->{action}) {
251 my $a = $hash->{actions} || $hash->{action};
252 %raw_attributes = ((exists $a->{'*'} ? %{$a->{'*'}} : ()),
253 %raw_attributes,
254 (exists $a->{$name} ? %{$a->{$name}} : ()));
255 }
256
257 my %final_attributes;
258
259 foreach my $key (keys %raw_attributes) {
260
261 my $raw = $raw_attributes{$key};
262
263 foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
264
265 my $meth = "_parse_${key}_attr";
266 if ( $self->can($meth) ) {
267 ( $key, $value ) = $self->$meth( $c, $name, $value );
268 }
269 push( @{ $final_attributes{$key} }, $value );
270 }
271 }
272
273 return \%final_attributes;
274}
275
276sub _parse_Global_attr {
277 my ( $self, $c, $name, $value ) = @_;
278 return $self->_parse_Path_attr( $c, $name, "/$name" );
279}
280
281sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); }
282
283sub _parse_Local_attr {
284 my ( $self, $c, $name, $value ) = @_;
285 return $self->_parse_Path_attr( $c, $name, $name );
286}
287
288sub _parse_Relative_attr { shift->_parse_Local_attr(@_); }
289
290sub _parse_Path_attr {
291 my ( $self, $c, $name, $value ) = @_;
292 $value ||= '';
293 if ( $value =~ m!^/! ) {
294 return ( 'Path', $value );
295 }
296 elsif ( length $value ) {
297 return ( 'Path', join( '/', $self->path_prefix($c), $value ) );
298 }
299 else {
300 return ( 'Path', $self->path_prefix($c) );
301 }
302}
303
304sub _parse_Regex_attr {
305 my ( $self, $c, $name, $value ) = @_;
306 return ( 'Regex', $value );
307}
308
309sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); }
310
311sub _parse_LocalRegex_attr {
312 my ( $self, $c, $name, $value ) = @_;
313 unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; }
314 return ( 'Regex', '^' . $self->path_prefix($c) . "/${value}" );
315}
316
317sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); }
318
319sub _parse_ActionClass_attr {
320 my ( $self, $c, $name, $value ) = @_;
321 unless ( $value =~ s/^\+// ) {
322 $value = join('::', $self->_action_class, $value );
323 }
324 return ( 'ActionClass', $value );
325}
326
9287719b 327sub _parse_MyAction_attr {
328 my ( $self, $c, $name, $value ) = @_;
329
330 my $appclass = Catalyst::Utils::class2appclass($self);
331 $value = "${appclass}::Action::${value}";
234763d4 332
9287719b 333 return ( 'ActionClass', $value );
334}
234763d4 335
3361;
337
338__END__
339
340=head1 CONFIGURATION
341
a269e0c2 342Like any other L<Catalyst::Component>, controllers have a config hash,
343accessible through $self->config from the controller actions. Some
344settings are in use by the Catalyst framework:
234763d4 345
346=head2 namespace
347
a269e0c2 348This specifies the internal namespace the controller should be bound
349to. By default the controller is bound to the URI version of the
350controller name. For instance controller 'MyApp::Controller::Foo::Bar'
351will be bound to 'foo/bar'. The default Root controller is an example
352of setting namespace to '' (the null string).
234763d4 353
a7caa492 354=head2 path
234763d4 355
356Sets 'path_prefix', as described below.
357
358=head1 METHODS
359
360=head2 $class->new($app, @args)
361
362Proxies through to NEXT::new and stashes the application instance as
363$self->_application.
364
365=head2 $self->action_for('name')
366
a269e0c2 367Returns the Catalyst::Action object (if any) for a given method name
368in this component.
234763d4 369
370=head2 $self->register_actions($c)
371
a269e0c2 372Finds all applicable actions for this component, creates
373Catalyst::Action objects (using $self->create_action) for them and
374registers them with $c->dispatcher.
234763d4 375
376=head2 $self->action_namespace($c)
377
a269e0c2 378Returns the private namespace for actions in this component. Defaults
379to a value from the controller name (for
380e.g. MyApp::Controller::Foo::Bar becomes "foo/bar") or can be
381overridden from the "namespace" config key.
234763d4 382
383
384=head2 $self->path_prefix($c)
385
a269e0c2 386Returns the default path prefix for :Local, :LocalRegex and relative
387:Path actions in this component. Defaults to the action_namespace or
388can be overridden from the "path" config key.
234763d4 389
390=head2 $self->create_action(%args)
391
a269e0c2 392Called with a hash of data to be use for construction of a new
393Catalyst::Action (or appropriate sub/alternative class) object.
234763d4 394
395Primarily designed for the use of register_actions.
396
a269e0c2 397=head2 $self->_application
234763d4 398
399=head2 $self->_app
400
401Returns the application instance stored by C<new()>
5ee249f2 402
403=head1 AUTHOR
404
405Sebastian Riedel, C<sri@oook.de>
234763d4 406Marcus Ramberg C<mramberg@cpan.org>
5ee249f2 407
408=head1 COPYRIGHT
409
a269e0c2 410This program is free software, you can redistribute it and/or modify
411it under the same terms as Perl itself.
5ee249f2 412
413=cut